Files
orgchain/tests/lineage.test.js
T
teahubxyzandClaude Opus 5.5 6acc319ee7 Orgchain v1: create and operate lineage holdings across time
Super Testnet's kitchen sink becomes lineage's app. Lineage gives the
structure; the chain adds time.

- Holdings home, a tree drawn like docs/lineage.md, act and inspect panels,
  and a timeline grouped by beat.
- Verbs: found, birth, award, decide, graduate, close. Each is a lineage/v0
  inscription whose parent is the node that authorizes it.
- One reducer (src/orgchain/lineage.js) folds the events into trees and vets
  every act before it is signed. Closing cascades through derived children;
  graduated (adopted) ones stay open.
- Time: tempo each act / 10s / 30s / 60s / manual, Beat now, and a view
  slider with replay. A beat is a block; cued acts wait in the mempool.
- Its own lab (orgchain:lab:v1) with save, load and reset; a yolopreneur
  sample of six beats.
- Removed: the recursive preview, explorer UI, server, Docker and Fly files.
  The engine (src/adapters, src/upstream, storage, miner) is unchanged.
- Tests: reducer and lab (node --test), plus a Playwright spec for desktop
  and mobile that also runs against a deployed copy.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H4upF5LVwmPDDqrhdUfnXh
2026-10-14 17:28:33 -07:00

84 lines
6.3 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert/strict';
import { PROTOCOL, census, reduce, rows, slug, summary, trail } from '../src/orgchain/lineage.js';
let n = 0;
const ev = (op, parent = null, meta = {}, height = 1) => ({ id: `${String(++n).padStart(64, '0')}i0`, status: height === null ? 'pending' : 'confirmed',
height, metadata: { op, ...meta }, parent, protocol: PROTOCOL });
const draw = (s, root) => rows(s, root).map(r => r.guide + (r.type === 'group' ? r.label : `${r.path} ${r.node.name}`));
test('found and birth build the tree; orgs and agents are indexed separately', () => {
const root = ev('found', null, { name: 'yolopreneur' }), wk = ev('birth', root.id, { name: 'well-known' }, 2),
time = ev('birth', root.id, { name: 'time', kind: 'org' }, 2), sol = ev('birth', time.id, { name: 'sol', kind: 'agent' }, 3),
doc = ev('birth', time.id, { name: 'docscope' }, 3);
const s = reduce([root, wk, time, sol, doc]);
assert.ok(s.log.every(e => e.ok));
assert.deepEqual([time, sol, doc].map(e => s.nodes.get(e.id).index), [1, 0, 0]);
assert.equal(s.nodes.get(sol.id).edge, 'derived');
assert.equal(s.nodes.get(root.id).kind, 'holding');
assert.deepEqual(draw(s, root.id), ['● yolopreneur', '└─ orgs', " ├─ 0' well-known", " └─ 1' time",
' ├─ agents', " │ └─ 0' sol", ' └─ orgs', " └─ 0' docscope"]);
assert.equal(trail(s, sol.id), 'yolopreneur / time / sol');
assert.deepEqual(census(s, root.id), { orgs: 3, agents: 1, belts: 0, decisions: 0, closed: 0 });
assert.equal(summary(s.log[3], s), 'birth sol (agent) ← time');
});
test('every rule rejects with a reason, and rejected events change nothing', () => {
const root = ev('found', null, { name: 'acme' }), bot = ev('birth', root.id, { name: 'bot', kind: 'agent' }),
ops = ev('birth', root.id, { name: 'ops' }), other = ev('found', null, { name: 'other' });
const bad = [
[ev('found', root.id, { name: 'x' }), /no parent/], [ev('found', null, { name: 'Acme Co' }), /lowercase/],
[ev('found', null, { name: 'acme' }), /already named acme/], [ev('birth', 'f'.repeat(64) + 'i0', { name: 'x' }), /not in this lineage/],
[ev('birth', bot.id, { name: 'x' }), /agents do not give birth/], [ev('birth', root.id, { name: 'ops' }), /already has an active ops/],
[ev('award', bot.id, { to: ops.id, skill: 'launch', belt: 'green' }), /agents do not award/],
[ev('award', root.id, { to: other.id, skill: 'launch', belt: 'green' }), /another node in this holding/],
[ev('award', root.id, { to: root.id, skill: 'launch', belt: 'green' }), /another node/],
[ev('award', root.id, { to: bot.id, skill: 'launch', belt: 'gold' }), /belt/], [ev('decide', root.id, { title: ' ' }), /title/],
[ev('graduate', ops.id, { of: bot.id }), /only its parent/], [ev('graduate', root.id, { of: bot.id }), /only orgs graduate/],
[ev('close'), /spends the node/], [ev('nope', root.id), /not a lineage\/v0 op/],
];
const s = reduce([root, bot, ops, other, ...bad.map(([e]) => e)]);
bad.forEach(([e, why]) => { const entry = s.log.find(x => x.id === e.id); assert.equal(entry.ok, false, e.metadata.op); assert.match(entry.why, why); });
assert.equal(s.nodes.size, 4);
assert.match(summary(s.log.find(x => x.id === bad[4][0].id), s), /^birth: agents do not give birth$/);
});
test('graduation flips the edge; closing cascades through derived children only', () => {
const root = ev('found', null, { name: 'acme' }), lab = ev('birth', root.id, { name: 'lab' }), kid = ev('birth', lab.id, { name: 'kid' }),
seat = ev('birth', kid.id, { name: 'seat', kind: 'agent' }), grad = ev('birth', lab.id, { name: 'grad' }),
up = ev('graduate', lab.id, { of: grad.id }, 2), again = ev('graduate', lab.id, { of: grad.id }, 2),
award = ev('award', lab.id, { to: seat.id, skill: 'launch', belt: 'green' }, 2), close = ev('close', lab.id, {}, 3),
after = ev('birth', lab.id, { name: 'late' }, 4), still = ev('decide', grad.id, { title: 'Carry on', why: 'adopted' }, 4);
const s = reduce([root, lab, kid, seat, grad, up, again, award, close, after, still]);
assert.equal(s.nodes.get(grad.id).edge, 'adopted');
assert.equal(s.nodes.get(grad.id).graduated, 2);
assert.match(s.log.find(e => e.id === again.id).why, /already graduated/);
assert.deepEqual(s.nodes.get(seat.id).badges.map(b => `${b.skill}:${b.belt}`), ['launch:green']);
assert.deepEqual([lab, kid, seat, grad].map(e => s.nodes.get(e.id).status), ['closed', 'closed', 'closed', 'active']);
assert.deepEqual(s.log.find(e => e.id === close.id).also, [kid.id, seat.id]);
assert.equal(summary(s.log.find(e => e.id === close.id), s), 'close lab and 2 derived');
assert.match(s.log.find(e => e.id === after.id).why, /parent is closed/);
assert.equal(s.nodes.get(grad.id).decisions[0].title, 'Carry on');
assert.deepEqual(census(s, root.id), { orgs: 3, agents: 1, belts: 1, decisions: 1, closed: 3 });
});
test('upTo shows a past beat; pending adds cued events', () => {
const root = ev('found', null, { name: 'acme' }, 1), a = ev('birth', root.id, { name: 'a' }, 2), b = ev('birth', root.id, { name: 'b' }, null),
award = ev('award', root.id, { to: a.id, skill: 'post', belt: 'white' }, null);
assert.equal(reduce([root, a, b, award], { upTo: 1 }).nodes.size, 1);
assert.equal(reduce([root, a, b, award]).nodes.size, 2);
const live = reduce([root, a, b, award], { pending: true });
assert.equal(live.nodes.get(b.id).born, 'cued');
assert.equal(live.nodes.get(a.id).badges[0].beat, 'cued');
assert.equal(live.log.filter(e => e.pending).length, 2);
});
test('summaries and slugs', () => {
const root = ev('found', null, { name: 'acme' }), ops = ev('birth', root.id, { name: 'ops' }), seat = ev('birth', root.id, { name: 'sol', kind: 'agent' });
const more = [ev('award', root.id, { to: seat.id, skill: 'launch', belt: 'green' }), ev('decide', ops.id, { title: 'Ship weekly' }), ev('graduate', root.id, { of: ops.id })];
const s = reduce([root, ops, seat, ...more]);
assert.deepEqual(s.log.map(e => summary(e, s)), ['found acme', 'birth ops ← acme', 'birth sol (agent) ← acme',
'award launch · green → sol, from acme', 'decide “Ship weekly” · ops', 'graduate ops from acme']);
assert.deepEqual(['Acme Holdings LLC', ' --Zoë_lab-- ', 'a'.repeat(50), '!!!'].map(slug), ['acme-holdings-llc', 'zoe-lab', 'a'.repeat(40), '']);
});