Files
orgchain/tests/lab.test.js
T
teahubxyzandClaude Opus 5.5 4ad75f0970 Orgchain v1.11: charters, a ladder of legal forms; agents graduate
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
2026-10-15 12:15:48 -07:00

124 lines
7.1 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert/strict';
import { MetadataIndex } from '../src/adapters/metadata-index.js';
import { lineageEvents, reduce } from '../src/orgchain/lineage.js';
import { LAB_KEY, decodeLab, emptyLab, encodeLab, mutate, setTempo, tick } from '../src/orgchain/store.js';
import { SAMPLE, act, seedSample, send } from '../src/orgchain/actions.js';
import { SAT } from '../src/orgchain/money.js';
const state = (lab, options = { pending: true }) => reduce(lineageEvents(new MetadataIndex(lab.chain)), options);
test('at the default tempo each act is its own beat, and the first act funds the lab', () => {
const lab = emptyLab();
const root = act(lab, 'found', null, { name: 'acme' }), ops = act(lab, 'birth', root, { name: 'ops' });
assert.equal(lab.chain.blocks.length, 3); // funding, founding, birth
const s = state(lab, {});
assert.deepEqual([root, ops].map(id => s.nodes.get(id).born), [2, 3]);
assert.equal(lab.chain.mempool.length, 0);
const record = lab.chain.transactions.get(ops.slice(0, -2)).inscription;
assert.equal(record.text, 'birth ops');
assert.equal(record.metaprotocol, 'lineage/v0');
assert.equal(record.parent, root);
});
test('a timed tempo cues acts, chains cued spends of one parent, and beats once when due', () => {
const lab = emptyLab();
const root = act(lab, 'found', null, { name: 'acme' });
setTempo(lab, 10, 1000);
const a = act(lab, 'birth', root, { name: 'a' }, 2000), b = act(lab, 'birth', root, { name: 'b' }, 3000);
send(lab, root, a, SAT, 3500); // a pays its own gas, out of money still cued
act(lab, 'birth', a, { name: 'c', kind: 'agent' }, 4000);
assert.deepEqual(lab.clock, { tempo: 10, nextAt: 12000 });
assert.equal(state(lab, {}).nodes.has(a), false);
assert.equal(state(lab).nodes.get(b).born, 'cued');
assert.equal(tick(lab, 11999), false);
assert.equal(tick(lab, 12000), true);
assert.deepEqual(lab.clock, { tempo: 10, nextAt: null });
assert.deepEqual([a, b].map(id => state(lab, {}).nodes.get(id).born), [3, 3]);
assert.equal(tick(lab, 99999), false); // nothing cued: no empty beats
});
test('manual waits for a beat; switching back to each lands the cue', () => {
const lab = emptyLab();
const root = act(lab, 'found', null, { name: 'acme' });
setTempo(lab, 'manual');
act(lab, 'decide', root, { title: 'Ship weekly', why: '' });
assert.equal(tick(lab, Date.now() + 1e9), false);
assert.equal(lab.chain.mempool.length, 3); // a gas top-up from acme's money, then the commit and the reveal
setTempo(lab, 'each');
assert.equal(lab.chain.mempool.length, 0);
assert.deepEqual(state(lab, {}).nodes.get(root).decisions.map(d => [d.title, d.why]), [['Ship weekly', '']]);
});
test('the reducer vets an act before anything is signed', () => {
const lab = emptyLab();
const root = act(lab, 'found', null, { name: 'acme' }), bot = act(lab, 'birth', root, { name: 'bot', kind: 'agent' });
const before = encodeLab(lab);
assert.throws(() => act(lab, 'birth', bot, { name: 'x' }), /^Error: Agents give birth only once they graduate\.$/);
assert.throws(() => act(lab, 'found', null, { name: 'acme' }), /already named acme/);
assert.throws(() => act(lab, 'award', root, { to: bot, skill: 'launch', belt: 'gold' }), /belt/);
assert.throws(() => act(lab, 'graduate', bot, { of: root }), /Only its parent can graduate a child/);
assert.throws(() => act(lab, 'sing', root, {}), /Unknown verb/);
assert.equal(encodeLab(lab), before);
});
test('closing spends the node; derived children close with it and graduated ones stay open', () => {
const lab = emptyLab();
const root = act(lab, 'found', null, { name: 'acme' }), studio = act(lab, 'birth', root, { name: 'studio' });
send(lab, root, studio, SAT);
const kid = act(lab, 'birth', studio, { name: 'kid' }), grad = act(lab, 'birth', studio, { name: 'grad' });
send(lab, studio, grad, 0.1 * SAT);
act(lab, 'graduate', studio, { of: grad });
const close = act(lab, 'close', studio, {});
const s = state(lab, {});
assert.deepEqual([studio, kid, grad].map(id => s.nodes.get(id).status), ['closed', 'closed', 'active']);
assert.equal(s.nodes.get(grad).edge, 'adopted');
assert.equal(lab.chain.transactions.get(close.slice(0, -2)).inscription.parent, studio);
assert.throws(() => act(lab, 'birth', studio, { name: 'again' }), /parent is closed/);
act(lab, 'decide', grad, { title: 'Carry on' });
});
test('the yolopreneur sample is six beats of the drawing in docs/lineage.md', () => {
const lab = emptyLab();
const root = seedSample(lab);
assert.equal(lab.chain.blocks.length, 7); // funding + six
const s = state(lab, {}), named = name => [...s.nodes.values()].find(x => x.name === name);
assert.ok(s.log.every(e => e.ok));
assert.equal(s.nodes.get(root).name, SAMPLE);
assert.deepEqual([...s.nodes.values()].filter(x => x.root === root).length, 11);
assert.equal(named('lineage').edge, 'adopted');
assert.deepEqual(s.roots.map(r => r.name), ['yolopreneur', 'lineage']); // graduated: a holding of its own
assert.deepEqual(named('sol').badges.map(b => `${b.skill}:${b.belt}`), ['launch:green']);
// Three keyrings; lineage's retires when it graduates, since its keys change with its root.
assert.deepEqual(['yolopreneur', 'sol', 'lineage'].map(x => [named(x).keyring.beat, named(x).keyring.retired?.cause ?? null]),
[[3, null], [6, null], [6, 'graduate']]);
assert.deepEqual(named('time').children.map(id => s.nodes.get(id).name), ['luna', 'sol', 'astra', 'docscope', 'dollhouse', 'doodleloop', 'hearthframe']);
assert.deepEqual([2, 3, 4, 5].map(upTo => state(lab, { upTo }).nodes.size), [1, 3, 8, 12]);
assert.throws(() => seedSample(lab), /already named yolopreneur/);
});
test('labs round-trip, refuse foreign backups, and share one lock across tabs', async () => {
const lab = emptyLab();
act(lab, 'found', null, { name: 'acme' });
setTempo(lab, 30, 1000);
const copy = decodeLab(encodeLab(lab));
assert.deepEqual(copy.clock, { tempo: 30, nextAt: null });
assert.equal(copy.chain.blocks.length, 2);
assert.equal(copy.wallet.secret, lab.wallet.secret);
assert.throws(() => decodeLab(JSON.stringify({ kind: 'test-lab', network: 'super-testnet-local-v0', version: 1 })), /not an Orgchain v1 lab/);
assert.throws(() => decodeLab('{bad'));
assert.throws(() => decodeLab(encodeLab(lab).replace('"tempo":30', '"tempo":15')), /Invalid clock/);
let stored = encodeLab(emptyLab()), tail = Promise.resolve(), writes = 0;
const names = new Set();
const io = { read: () => decodeLab(stored), save: l => { stored = encodeLab(l); writes++; },
locks: { request(name, fn) { names.add(name); const next = tail.then(fn); tail = next.catch(() => {}); return next; } } };
await Promise.all([mutate(l => act(l, 'found', null, { name: 'one' }), io), mutate(l => act(l, 'found', null, { name: 'two' }), io)]);
assert.deepEqual([...names], [LAB_KEY]);
assert.deepEqual(state(decodeLab(stored)).roots.map(r => r.name), ['one', 'two']);
const { result } = await mutate(l => tick(l), io);
assert.equal(result, false);
assert.equal(writes, 2);
await assert.rejects(mutate(l => act(l, 'found', null, { name: 'one' }), io), /already named one/);
assert.equal(writes, 2);
});