Files
orgchain/tests/orgchain.spec.js
T
teahubxyzandClaude Opus 5.5 7d981341ff Orgchain v1.8: belts cross holdings, and a graduate becomes its own holding
From the operator's notes on trying v1.6.1. An award's recipient may sit in
any holding, so a registry like well-known can certify an agent it doesn't
hold (say, after the agent pays it). A graduated org already held its own
root; now it is a holding of its own too: it and its subtree re-root under
it, it joins the Holdings list with its own tree and timeline, mines its
share and can be funded, and its parent's tree keeps a row for it that opens
it. Events that touch two holdings (a belt across them, a graduation) show in
both timelines, and the holding that acted gets the last-act credit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H4upF5LVwmPDDqrhdUfnXh
2026-10-15 00:44:59 -07:00

336 lines
20 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { test, expect } from '@playwright/test';
async function open(page) {
const problems = [];
page.on('console', m => { if (m.type() === 'error') problems.push(m.text()); });
page.on('pageerror', e => problems.push(String(e)));
await page.goto('./');
await expect(page.getByRole('heading', { name: 'Orgchain' })).toBeVisible();
return problems;
}
const say = page => page.locator('#say');
const row = (page, name) => page.locator('#tree-rows .sel').filter({ has: page.locator('.l', { hasText: new RegExp(`^${name}$`) }) });
const verb = (page, name) => page.locator(`[data-verb="${name}"]`).click();
const submit = (page, form) => page.locator(`#f-${form} [type="submit"]`).click();
async function found(page, name) { await page.locator('#found-name').fill(name); await submit(page, 'found'); }
async function birth(page, name, kind = 'org') {
await verb(page, 'birth');
await page.locator('#birth-name').fill(name);
await page.locator(`#f-birth input[value="${kind}"]`).check();
await submit(page, 'birth');
}
async function pay(page, to, amount) {
await verb(page, 'send');
await page.locator('#send-to').selectOption({ label: to });
await page.locator('#send-amount').fill(amount);
await submit(page, 'send');
}
const slide = (page, beat) => page.locator('#beat').evaluate((el, v) => { el.value = v; el.dispatchEvent(new Event('input', { bubbles: true })); }, String(beat));
test('found a holding, then birth, award, decide, graduate and close at the default tempo', async ({ page }) => {
const problems = await open(page);
await found(page, 'Acme Holdings');
await expect(say(page)).toHaveText('acme-holdings is founded, at beat 2.');
await birth(page, 'studio');
await expect(say(page)).toHaveText('studio is born to acme-holdings, at beat 3.');
await row(page, 'studio').click();
await verb(page, 'birth');
await expect(page.locator('#act-why')).toHaveText('studio has no test BTC for gas. acme-holdings can send some.');
await row(page, 'acme-holdings').click();
await pay(page, 'acme-holdings / studio', '5');
await expect(say(page)).toHaveText('acme-holdings sent 5 tBTC to studio, at beat 4.');
await row(page, 'studio').click();
await birth(page, 'bot', 'agent');
await expect(say(page)).toHaveText('bot is born to studio, at beat 5.');
await verb(page, 'award');
await page.locator('#award-to').selectOption({ label: 'acme-holdings / studio / bot (agent)' });
await page.locator('#award-skill').fill('launch');
await page.locator('#award-belt').selectOption('green');
await submit(page, 'award');
await expect(say(page)).toHaveText('bot holds a green belt in launch, at beat 6.');
await verb(page, 'decide');
await page.locator('#decide-title').fill('Ship weekly');
await submit(page, 'decide');
await expect(say(page)).toHaveText('studio decided “Ship weekly”, at beat 7.');
await verb(page, 'graduate');
await page.locator('#graduate-go').click();
await expect(say(page)).toHaveText("studio graduates from acme-holdings, at beat 8. It's a holding of its own now.");
await expect(row(page, 'studio')).toContainText('graduated');
await expect(page.locator('#holdings-list')).toContainText('from acme-holdings at beat 8');
await expect(row(page, 'bot')).toHaveCount(0); // its seats went with it
await row(page, 'acme-holdings').click();
await verb(page, 'close');
await page.locator('#close-go').click();
await expect(page.locator('#close-go')).toHaveText('Yes, close acme-holdings');
await page.locator('#close-go').click();
await expect(say(page)).toHaveText('acme-holdings is closed, at beat 9.');
await expect(row(page, 'acme-holdings')).toContainText('closed');
await expect(row(page, 'studio')).not.toContainText('closed'); // graduated: it holds its own root
await page.locator('#timeline-list .ev', { hasText: 'Ship weekly' }).click();
await expect(page.locator('#inspect-body')).toContainText('"op": "decide"');
await expect(page.locator('#inspect-body')).toContainText('applied');
await row(page, 'studio').click();
await page.locator('#inspect-body').getByRole('button', { name: 'Open studio' }).click();
await expect(page.locator('#tree-sub')).toContainText('studio');
await expect(page.locator('#timeline-list')).toContainText('graduate studio from acme-holdings');
await row(page, 'bot').click();
await verb(page, 'birth');
await expect(page.locator('#act-why')).toHaveText('Agents do not give birth.');
expect(problems).toEqual([]);
});
test('a timed tempo cues acts; Beat now and the clock land them', async ({ page }) => {
const problems = await open(page);
await found(page, 'acme');
await page.locator('[data-tempo="10"]').click();
await birth(page, 'a');
await expect(say(page)).toHaveText('a is born to acme, cued for the next beat.');
await birth(page, 'b');
await expect(page.locator('#clock-cued')).toContainText('2 cued · next beat in');
await expect(row(page, 'b')).toContainText('cued');
await page.locator('#beat-now').click();
await expect(say(page)).toHaveText('Beat 3: 2 cued acts landed, and acme earned 50 tBTC.');
await birth(page, 'c');
await expect(say(page)).toHaveText('Beat 4: 1 cued act landed, and acme earned 50 tBTC.', { timeout: 15_000 });
await page.locator('[data-tempo="manual"]').click();
await birth(page, 'd');
await expect(page.locator('#clock-cued')).toHaveText('1 cued · waiting for Beat now');
await page.locator('[data-tempo="each"]').click();
await expect(page.locator('#clock-cued')).toHaveText('nothing cued');
await expect(page.locator('#clock-beat')).toHaveText('5');
expect(problems).toEqual([]);
});
test('beats pay the holdings, every act pays its gas, and test BTC moves across holdings', async ({ page }) => {
const problems = await open(page), inspect = page.locator('#inspect-body'), why = page.locator('#act-why');
await found(page, 'acme');
await expect(row(page, 'acme')).toContainText('50 tBTC');
await expect(page.locator('#clock-pays')).toHaveText('beats pay acme');
await birth(page, 'ops');
await row(page, 'ops').click();
await verb(page, 'birth');
await expect(why).toHaveText('ops has no test BTC for gas. acme can send some.');
await verb(page, 'send');
await expect(why).toHaveText('ops has no test BTC to send yet. acme can send some.');
await row(page, 'acme').click();
await verb(page, 'send');
await expect(page.locator('#send-has')).toHaveText('99.999895 tBTC');
await expect(page.locator('#act-key')).toHaveText("Signs as acme: acme root · m/84'/1'/0'/0/0");
await expect(page.locator('#act-gas')).toHaveText("Fee: 500 sats, from acme's balance.");
await pay(page, 'acme / ops', '12.5');
await expect(say(page)).toHaveText('acme sent 12.5 tBTC to ops, at beat 4.');
await expect(row(page, 'ops')).toContainText('12.5 tBTC');
await row(page, 'ops').click();
await verb(page, 'birth');
await expect(page.locator('#act-gas')).toHaveText("Gas: 2,500 sats from ops's tank (0 tBTC), which tops up from its balance.");
await birth(page, 'bot', 'agent');
await expect(say(page)).toHaveText('bot is born to ops, at beat 5.');
await expect(row(page, 'ops')).toContainText('≈12.5 tBTC');
await verb(page, 'send');
await page.locator('#send-to').selectOption({ label: 'acme / ops / bot (agent)' });
await page.locator('#send-max').click();
await expect(page.locator('#send-amount')).toHaveValue('12.49989');
await page.locator('#send-amount').fill('1');
await submit(page, 'send');
await expect(say(page)).toHaveText('ops sent 1 tBTC to bot, at beat 6.');
await expect(row(page, 'bot')).toContainText('1 tBTC');
await page.locator('#timeline-list .ev', { hasText: 'ops → bot' }).click();
for (const text of ['500 sats, retired', '11.49989 tBTC back to ops', "ops's SegWit key (BIP-143, SIGHASH_ALL)"]) await expect(inspect).toContainText(text);
await page.locator('#timeline-list .ev', { hasText: "ops's gas tank" }).click();
await expect(inspect).toContainText("ops's gas tank, at its Taproot key");
await page.locator('#beat-now').click();
await expect(say(page)).toHaveText('Beat 7: acme earned 50 tBTC.');
await found(page, 'beta');
await expect(say(page)).toHaveText('beta is founded, at beat 8.');
await expect(page.locator('#clock-pays')).toHaveText('beats pay 2 holdings, split by hashrate');
await expect(page.locator('#holdings-list')).toContainText('50% of each beat');
await page.getByRole('button', { name: "Raise acme's hashrate" }).click();
await expect(page.locator('#holdings-list')).toContainText('67% of each beat');
await page.locator('#beat-now').click();
await expect(say(page)).toHaveText('Beat 9: acme earned ≈33.33 tBTC, beta ≈16.67 tBTC.');
await page.locator('[data-rule="last"]').click();
await expect(page.locator('#clock-pays')).toHaveText('beats pay the holding that acts');
await page.locator('#beat-now').click();
await expect(say(page)).toHaveText('Beat 10: beta earned 50 tBTC.');
await pay(page, 'acme / ops', '2');
await expect(say(page)).toHaveText('beta sent 2 tBTC to acme / ops, at beat 11.');
await page.locator('#holdings-list .hold', { hasText: 'acme' }).click();
await expect(row(page, 'ops')).toContainText('≈13.5 tBTC');
await expect(page.locator('#timeline-list')).toContainText('2 tBTC beta → ops');
await page.locator('[data-tempo="manual"]').click();
await pay(page, 'acme / ops / bot (agent)', '0.25');
await expect(say(page)).toHaveText('acme sent 0.25 tBTC to bot, cued for the next beat.');
await expect(page.locator('#clock-cued')).toHaveText('1 cued · waiting for Beat now');
await page.locator('#beat-now').click();
await expect(say(page)).toHaveText('Beat 12: 1 cued send landed, and acme earned 50 tBTC.');
await expect(row(page, 'bot')).toContainText('1.25 tBTC');
await slide(page, 3);
await expect(row(page, 'ops')).not.toContainText('tBTC');
await expect(row(page, 'acme')).toContainText('≈100 tBTC');
await row(page, 'acme').click();
await expect(inspect).toContainText('as of beat 3');
await page.locator('#to-now').click();
expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true);
expect(problems).toEqual([]);
});
test('the sample replays, any beat can be inspected, and the lab survives a reload', async ({ page }) => {
const problems = await open(page);
await page.locator('#sample').click();
await expect(say(page)).toContainText('Loaded the yolopreneur sample', { timeout: 20_000 });
await expect(page.locator('#tree-rows .sel')).toHaveCount(12);
await expect(page.locator('#beat-label')).toHaveText('Now · beat 7');
await expect(row(page, 'lineage')).toContainText('graduated');
await expect(row(page, 'time')).toContainText('≈38.5 tBTC');
await expect(page.locator('#timeline-list')).toContainText('40 tBTC yolopreneur → time');
await expect(page.locator('#sample')).toBeDisabled();
await slide(page, 3);
await expect(page.locator('#beat-label')).toHaveText('Beat 3 of 7');
await expect(page.locator('#tree-rows .sel')).toHaveCount(3);
await expect(page.locator('#act-past')).toBeVisible();
await page.locator('#replay').click();
await expect(page.locator('#beat-label')).toHaveText('Now · beat 7', { timeout: 15_000 });
await page.locator('#timeline-list .ev', { hasText: 'graduate lineage from well-known' }).click();
await expect(page.locator('#inspect-body')).toContainText('"op": "graduate"');
await expect(page.locator('#inspect-body')).toContainText("well-known's key");
await row(page, 'lineage').click();
await expect(page.locator('#inspect-body')).toContainText('lineage root · account 0');
await expect(page.locator('#inspect-body')).toContainText("BIP-86 at m/86'/1'/0'/0/0");
await expect(page.locator('#inspect-body')).toContainText('its own key');
await row(page, 'time').click();
await expect(page.locator('#act-key')).toHaveText("Signs as time: yolopreneur root › 1' · m/86'/1'/0'/0/0");
const inspect = page.locator('#inspect-body');
for (const text of ['ssh-ca', 'lineage-ca@time.yolopreneur', 'mkey:']) await expect(inspect).toContainText(text);
await row(page, 'sol').click();
const fact = name => inspect.locator('.f').filter({ has: page.locator('dt', { hasText: new RegExp(`^${name}$`) }) });
for (const [name, text] of [['Taproot', 'bcrt1p'], ['Taproot', "BIP-86 at m/86'/1'/2'/0/0"], ['SegWit', 'bcrt1q'], ['SegWit', "BIP-84 at m/84'/1'/2'/0/0"],
['npub', 'npub1'], ['npub', "NIP-06 at m/44'/1237'/2'/0/0"], ['From', "yolopreneur root › 1' · account 2"]]) await expect(fact(name)).toContainText(text);
for (const text of ['ssh-ed25519 AAAAC3NzaC1lZDI1NTE5', 'sol@time.yolopreneur namespaces="git"', 'sol._domainkey.time.yolopreneur. IN TXT "v=DKIM1; k=ed25519; p=', 'PublicKey = ']) {
await expect(inspect.locator('pre')).toContainText(text);
}
await expect(fact('Tapkey')).toContainText('bcrt1p');
await expect(page.locator('#reveal')).toHaveAttribute('aria-expanded', 'false');
await page.locator('#reveal').click();
const secrets = inspect.locator('.sb');
for (const text of ['nsec1', 'BEGIN OPENSSH PRIVATE KEY', 'PrivateKey = ', 'account 2 of time']) await expect(secrets).toContainText(text);
await expect(secrets).toContainText(/tr\(c[1-9A-HJ-NP-Za-km-z]{51}\)#[a-z0-9]{8}/);
await secrets.locator('.cp').first().click();
await expect(secrets.locator('.cp').first()).toHaveText(/Copied|Selected/);
await row(page, 'time').click();
await expect(page.locator('#reveal')).toHaveAttribute('aria-expanded', 'false');
await page.locator('#reveal').click();
await expect(secrets).toContainText("Seed · yolopreneur root › 1'");
await expect(secrets).toContainText('BEGIN PRIVATE KEY');
expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true);
await page.locator('#reveal').click();
await expect(secrets).toHaveCount(0);
expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true);
await page.reload();
await expect(page.locator('#tree-rows .sel')).toHaveCount(12);
await page.locator('#theme').click();
await expect(page.locator('html')).toHaveAttribute('data-theme', 'light');
await page.locator('#theme').click();
await page.reload();
await expect(page.locator('html')).toHaveAttribute('data-theme', 'dark');
await expect(page.locator('#theme')).toHaveText('Theme: dark');
await page.locator('#reset').click();
await page.locator('#confirm button', { hasText: 'Erase' }).click();
await expect(say(page)).toHaveText('Erased. This is a fresh lab.');
await expect(page.locator('#holdings-list li')).toHaveCount(0);
expect(problems).toEqual([]);
});
test('a node makes an item, another holding bids on it, the holder accepts; on mainnet holdings are funded', async ({ page }) => {
const problems = await open(page), inspect = page.locator('#inspect-body'), why = page.locator('#act-why');
const hold = name => page.locator('#holdings-list .hold', { has: page.locator('.hn', { hasText: new RegExp(`^${name}$`) }) });
await found(page, 'acme');
await expect(say(page)).toHaveText('acme is founded, at beat 2.');
await found(page, 'beta');
await expect(say(page)).toHaveText('beta is founded, at beat 3.');
await verb(page, 'offer');
await expect(why).toHaveText('Nobody else holds an item yet. Items come from Make.');
await hold('acme').click();
await verb(page, 'make');
await expect(page.locator('#f-make .hint')).toContainText('public and permanent');
await page.locator('#make-name').fill('Kit');
await page.locator('#make-note').fill('A starter kit.');
await submit(page, 'make');
await expect(say(page)).toHaveText('acme made kit, at beat 4.');
await expect(page.locator('#timeline-list')).toContainText('make kit · acme');
await expect(inspect).toContainText('kit · A starter kit. (made at beat 4)');
await hold('beta').click();
await verb(page, 'offer');
await expect(page.locator('#act-gas')).toHaveText("Fee: 500 sats, from beta's balance, paid only if the holder accepts.");
await page.locator('#offer-item').selectOption({ label: 'kit · held by acme' });
await page.locator('#offer-price').fill('2');
await submit(page, 'offer');
await expect(say(page)).toHaveText('beta offers 2 tBTC for kit. The offer stands until acme accepts it.');
await expect(page.locator('#clock-beat')).toHaveText('4'); // an offer is signatures, not a transaction
await expect(inspect).toContainText('2 tBTC for kit (open until acme accepts)');
await hold('acme').click();
await expect(inspect).toContainText('beta offers 2 tBTC');
await inspect.getByRole('button', { name: "Accept beta's offer for kit" }).click();
await expect(say(page)).toHaveText('acme accepted: kit goes to beta for 2 tBTC, at beat 5.');
await expect(inspect).toContainText('kit · A starter kit. (now held by beta)');
await page.locator('#timeline-list .ev', { hasText: 'kit acme → beta, for 2 tBTC' }).click();
for (const text of ['Price', '2 tBTC', "beta's SegWit key (SIGHASH_ALL) when it made the offer, then acme's Taproot key when it accepted"]) {
await expect(inspect).toContainText(text);
}
await page.locator('[data-rule="none"]').click();
await expect(say(page)).toHaveText('Mainnet: no holding mines. Beats pay the lab wallet, and you fund holdings from it.');
await expect(page.locator('#clock-pays')).toHaveText('beats pay the lab wallet');
await page.locator('#fund-to').selectOption({ label: 'beta' });
await page.locator('#fund-amount').fill('3');
await page.locator('#f-fund [type="submit"]').click();
await expect(say(page)).toHaveText('The lab wallet funded beta with 3 tBTC, at beat 6.');
await hold('beta').click();
await expect(page.locator('#timeline-list')).toContainText('3 tBTC lab wallet → beta');
await expect(page.locator('#holdings-list')).not.toContainText('of each beat');
expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true);
expect(problems).toEqual([]);
});
test('the sample ends on a standing offer, and its holder accepts it', async ({ page }) => {
const problems = await open(page), inspect = page.locator('#inspect-body');
await page.locator('#sample').click();
await expect(say(page)).toContainText('Loaded the yolopreneur sample', { timeout: 20_000 });
await row(page, 'lineage').click();
await expect(inspect).toContainText('0.1 tBTC for launch-kit (open until sol accepts)');
await row(page, 'sol').click();
await expect(inspect).toContainText('launch-kit · The page, the post and the checklist every venture launches with.');
await inspect.getByRole('button', { name: "Accept lineage's offer for launch-kit" }).click();
await expect(say(page)).toHaveText('sol accepted: launch-kit goes to lineage for 0.1 tBTC, at beat 8.');
await expect(page.locator('#timeline-list')).toContainText('launch-kit sol → lineage, for 0.1 tBTC');
await row(page, 'lineage').click();
await expect(inspect).toContainText('launch-kit · The page, the post and the checklist every venture launches with. (made by sol at beat 6)');
expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true);
expect(problems).toEqual([]);
});
test('an agent pays a registry in another holding, and the registry awards it a belt', async ({ page }) => {
const problems = await open(page);
const hold = name => page.locator('#holdings-list .hold', { has: page.locator('.hn', { hasText: new RegExp(`^${name}$`) }) });
await found(page, 'well-known');
await expect(say(page)).toHaveText('well-known is founded, at beat 2.');
await found(page, 'acme');
await expect(say(page)).toHaveText('acme is founded, at beat 3.');
await birth(page, 'scout', 'agent');
await expect(say(page)).toHaveText('scout is born to acme, at beat 4.');
await pay(page, 'acme / scout (agent)', '1');
await expect(say(page)).toHaveText('acme sent 1 tBTC to scout, at beat 5.');
await row(page, 'scout').click();
await pay(page, 'well-known', '0.1');
await expect(say(page)).toHaveText('scout sent 0.1 tBTC to well-known, at beat 6.');
await hold('well-known').click();
await verb(page, 'award');
await page.locator('#award-to').selectOption({ label: 'acme / scout (agent)' });
await page.locator('#award-skill').fill('listen');
await submit(page, 'award');
await expect(say(page)).toHaveText('scout holds a white belt in listen, at beat 7.');
await expect(page.locator('#timeline-list')).toContainText('award listen · white → scout, from well-known');
await hold('acme').click();
await expect(page.locator('#timeline-list')).toContainText('award listen · white → scout, from well-known');
await expect(row(page, 'scout')).toContainText('listen');
expect(problems).toEqual([]);
});