A new lineage/v0 op, charter, records a node's legal form: sole proprietor, LLC, series LLC, series or corporation, with a jurisdiction (ISO 3166), a legal name, who formed it and a SHA-256 of the filings. Legal form is an attribute, so a node keeps its inscription, keys and history whatever form it takes, and the newest charter counts. A node charters itself; a series is chartered by its master, an ancestor chartered as a series LLC. Whether a charter stands is derived from the tree, never stored: a series lapses if it graduates or its master closes or stops holding series, so charters commute with graduations and closes in any block order. Agents can graduate: a seat goes independent as a holding of its own, with its own root and keys and its money along, and can then birth, award and incorporate. Charter is the tenth verb, and the tree tags each node with its form. Charter hashes a filing from a file in the tab. The sample charters yolopreneur as a Wyoming series LLC with its ventures as series, and lineage graduates into Lineage LLC. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H4upF5LVwmPDDqrhdUfnXh
103 lines
7.7 KiB
JavaScript
103 lines
7.7 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 give birth only once they graduate/], [ev('birth', root.id, { name: 'ops' }), /already has an active ops/],
|
|
[ev('award', bot.id, { to: ops.id, skill: 'launch', belt: 'green' }), /agents award belts only once they graduate/],
|
|
[ev('award', root.id, { to: 'f'.repeat(64) + 'i0', skill: 'launch', belt: 'green' }), /another node in this lineage/],
|
|
[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: root.id }), /only its parent/],
|
|
[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 give birth only once they graduate$/);
|
|
});
|
|
|
|
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');
|
|
// A graduate is a holding of its own: its parent's tree keeps a row for it, and its own tree starts at it.
|
|
assert.deepEqual(s.roots.map(r => r.id), [root.id, grad.id]);
|
|
assert.equal(s.nodes.get(grad.id).root, grad.id);
|
|
assert.deepEqual(s.log.find(e => e.id === up.id).roots, [root.id, grad.id]);
|
|
assert.deepEqual(s.log.find(e => e.id === still.id).roots, [grad.id]);
|
|
assert.deepEqual(rows(s, root.id).filter(r => r.away).map(r => r.node.name), ['grad']);
|
|
assert.deepEqual(draw(s, grad.id), ['● grad']);
|
|
assert.equal(trail(s, grad.id), 'grad');
|
|
assert.deepEqual(census(s, root.id), { orgs: 2, agents: 1, belts: 1, decisions: 0, closed: 3 });
|
|
assert.deepEqual(census(s, grad.id), { orgs: 0, agents: 0, belts: 0, decisions: 1, closed: 0 });
|
|
});
|
|
|
|
test('a belt can cross holdings: the event shows in both, and the issuer acted', () => {
|
|
const wk = ev('found', null, { name: 'well-known' }), other = ev('found', null, { name: 'acme' }), bot = ev('birth', other.id, { name: 'bot', kind: 'agent' });
|
|
const award = ev('award', wk.id, { to: bot.id, skill: 'listen', belt: 'white' }, 2);
|
|
const s = reduce([wk, other, bot, award]), entry = s.log.at(-1);
|
|
assert.equal(entry.ok, true);
|
|
assert.deepEqual([entry.root, entry.roots], [wk.id, [wk.id, other.id]]);
|
|
assert.deepEqual(s.nodes.get(bot.id).badges.map(b => [b.skill, b.belt, b.by]), [['listen', 'white', wk.id]]);
|
|
assert.equal(summary(entry, s), 'award listen · white → bot, from well-known');
|
|
});
|
|
|
|
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), '']);
|
|
});
|