A node's chain key is now its tapkey: its NIP-06 npub with the BIP-86 tweak, so one secret signs its nostr events and its chain spends, and its address follows from its npub. Its leaves hang off the same tree, byte for byte as on lineage's key tree page: an agent's ssh, git, dkim and wg keys from its nostr secret, an org's ssh-ca, mtls and noise keys from its seed. Inspect shows the tapkey, npub, derivation and leaves, with the public lines to paste. Labs keyed by v1.1 keep their separate m/86' chain keys until they are reset. PROVENANCE.md and public/licenses now carry @scure/base, bip32 and bip39, which came in with v1.1. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01H4upF5LVwmPDDqrhdUfnXh
133 lines
7.5 KiB
JavaScript
133 lines
7.5 KiB
JavaScript
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');
|
||
}
|
||
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 birth(page, 'bot', 'agent');
|
||
await expect(say(page)).toHaveText('bot is born to studio, at beat 4.');
|
||
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 5.');
|
||
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 6.');
|
||
await verb(page, 'graduate');
|
||
await page.locator('#graduate-go').click();
|
||
await expect(say(page)).toHaveText('studio graduates from acme-holdings, at beat 7. 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 8.');
|
||
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.');
|
||
await birth(page, 'c');
|
||
await expect(say(page)).toHaveText('Beat 4: 1 cued act landed.', { 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('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(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 · m/44'/1237'/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/44'/1237'/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}$`) }) });
|
||
await expect(fact('Tapkey')).toContainText('bcrt1p');
|
||
await expect(fact('npub')).toContainText('npub1');
|
||
await expect(fact('Derived')).toContainText("yolopreneur root › 1' · m/44'/1237'/2'/0/0");
|
||
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);
|
||
}
|
||
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([]);
|
||
});
|