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
124 lines
7.1 KiB
JavaScript
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);
|
|
});
|