Files
orgchain/tests/orgchain.spec.js
T
teahubxyzandClaude Opus 5.5 2354947d16 Orgchain v1.6: holdings share the mining, every act pays its gas, money crosses holdings
Mining is a lab setting. Split (the default) pays every active holding a
share of each beat, pro rata by a hashrate dial on each holding (0 to 9,
1 unless set), one coinbase output per holding. Last act gives the whole
beat to the holding that acts. Every act is paid by the acting node's own
keys: fees and postage come out of its gas tank (the plain coins at its
Taproot key), which tops up from its own money 0.0001 test BTC at a time.
Each act is checked before anything is signed, so an unfunded node can't
act. A graduating org pays for its own claim, and the lab wallet pays only
for foundings and for holdings that predate lab keys. Sends cross
holdings, the ledger shows gas top-ups, and chain keys are derived once
per page.

The engine's second local change: a coinbase may split its reward across
up to 64 outputs (mineTo), in src/adapters/chain.js.

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

236 lines
14 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. Its edge is adopted now.');
await expect(row(page, 'studio')).toContainText('graduated');
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 row(page, 'bot').click();
await verb(page, 'birth');
await expect(page.locator('#act-why')).toHaveText('Agents do not give birth.');
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');
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([]);
});