Orgchain v1.1: lineage's keys, from the tree branch
Folds in the tree branch's key layer (2ea7884): lineage-keys.js and
handoff.js come over as written, with their spec vectors; the off-chain
keyring and the old UI stay behind, because v1's tree comes from the chain.
- Each holding gets its own 24-word lab root. Every node has lineage's
keys at its place in the tree: BIP-85 org seeds, agents as accounts
1..n of their org's seed, BIP-86 wallets and NIP-06 npubs.
- Every act is signed by whichever key holds the authorizing inscription;
a birth hands the child its own; the lab wallet is the faucet.
- Graduation is a claim by the org's fresh root, countersigned by its
derived key, and the derived subtree re-keys under the new root, so the
parent's line can no longer sign for it. Adopted descendants keep theirs.
- Inspect shows each node's key path, npub, address and current holder,
and which key signed each event; the act panel names the signing key.
- Labs saved before keys still load; their holdings sign with the lab
wallet.
- 49 unit tests (6 new), Playwright 6/6 with key checks.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01H4upF5LVwmPDDqrhdUfnXh
This commit is contained in:
1 parent
6acc319ee7
commit
744c5946aa
14 files changed
+467
-19
No files matched your search
+23
-1
@@ -5,6 +5,8 @@ import { MetadataIndex } from './adapters/metadata-index.js';
|
||||
import { census, lineageEvents, reduce, rows, slug, summary, trail } from './orgchain/lineage.js';
|
||||
import { LAB_KEY, beat, decodeLab, emptyLab, encodeLab, mutate, readLab, setTempo, tick } from './orgchain/store.js';
|
||||
import { SAMPLE, act, seedSample } from './orgchain/actions.js';
|
||||
import { holderOf, keyOf } from './orgchain/keys.js';
|
||||
import { readEnvelope } from './adapters/inscription.js';
|
||||
|
||||
const $ = id => document.getElementById(id);
|
||||
function h(tag, attrs = {}, ...kids) {
|
||||
@@ -180,6 +182,8 @@ function renderAct() {
|
||||
const sig = list => list.map(o => `${o.value}=${o.textContent}`).join('|');
|
||||
if (sig(options) !== sig([...to.options])) { to.replaceChildren(...options); if (options.some(o => o.value === keep)) to.value = keep; }
|
||||
$('graduate-go').textContent = `Graduate ${n.name}`;
|
||||
const signer = ui.verb === 'graduate' ? parent : n, k = signer && keyOf(lab.keys, live, signer);
|
||||
$('act-key').textContent = !signer || why[ui.verb] ? '' : k ? `Signs as ${signer.name}: ${k.path}` : 'Signs with the lab wallet: this holding predates lab keys.';
|
||||
const below = derivedBelow(n), arming = ui.closing === n.id;
|
||||
$('close-note').textContent = below
|
||||
? `Closing ${n.name} spends it for the last time, and its ${plural(below, 'derived descendant')} close with it. Graduated children hold their own roots and stay open.`
|
||||
@@ -207,6 +211,7 @@ function nodeView(n) {
|
||||
parent && ['Edge', n.edge === 'adopted' ? `adopted at ${at(n.graduated)}: it holds its own root` : `derived: ${parent.name} holds its seed`],
|
||||
n.closed !== null && ['Closed', at(n.closed)],
|
||||
['Children', n.children.length ? n.children.map(id => node(id, view)?.name).join(', ') : 'none yet'],
|
||||
...keyFacts(n),
|
||||
['Inscription', n.id],
|
||||
].filter(Boolean);
|
||||
return [
|
||||
@@ -220,12 +225,29 @@ function nodeView(n) {
|
||||
h('li', {}, h('b', { text: d.title }), d.why && ` · ${d.why}`, h('span', { class: 'when', text: ` (${at(d.beat)})` })))))),
|
||||
];
|
||||
}
|
||||
// Lineage's keys for a node: its derivation, npub and address as of the viewed beat, and who holds its inscription now.
|
||||
const HELD = { own: 'its own key', lab: 'the lab wallet', parent: "its parent's key (its handoff is cued)", other: 'a key outside this lab' };
|
||||
function keyFacts(n) {
|
||||
const k = keyOf(lab.keys, view, n), holder = holderOf(lab, live, n.id);
|
||||
return [['Key', k ? k.path : 'the lab wallet: this holding predates lab keys'], k && ['npub', k.npub], k && ['Address', k.wallet.address],
|
||||
['Held now by', holder ? HELD[holder.who] : 'nobody: its inscription has been spent']];
|
||||
}
|
||||
// The key that signed an event's reveal, named when it belongs to the node that authorized it.
|
||||
function signerOf(e) {
|
||||
const witness = lab.chain.transactions.get(e.id.slice(0, -2))?.tx.vin.find(i => i.witness?.length === 3)?.witness;
|
||||
if (!witness) return '?';
|
||||
const pubkey = readEnvelope(witness).pubkey, by = node(e.parent), own = e.op === 'found' ? node(e.node) : null;
|
||||
const named = [[own && keyOf(lab.keys, live, own), `${own?.name}'s root`], [by && keyOf(lab.keys, live, by), `${by?.name}'s key`],
|
||||
[by?.edge === 'adopted' && keyOf(lab.keys, live, { ...by, edge: 'derived' }), `${by?.name}'s derived key, from before it graduated`]];
|
||||
for (const [k, label] of named) if (k && k.wallet.pubkey === pubkey) return label;
|
||||
return lab.wallet?.pubkey === pubkey ? 'the lab wallet' : `a key outside this lab (${pubkey.slice(0, 12)}…)`;
|
||||
}
|
||||
function eventView(e) {
|
||||
const raw = index.events.find(x => x.id === e.id), body = lab.chain.transactions.get(e.id.slice(0, -2))?.inscription?.text;
|
||||
const facts = [
|
||||
['When', e.pending ? 'cued for the next beat' : `beat ${e.beat}`],
|
||||
['Authorized by', e.parent ? `${node(e.parent)?.name ?? 'an unknown inscription'}, whose inscription this event spent` : 'nothing: a founding has no parent'],
|
||||
['Inscription', e.id], body && ['Body', body], raw?.block && ['Block', raw.block],
|
||||
['Signed by', signerOf(e)], ['Inscription', e.id], body && ['Body', body], raw?.block && ['Block', raw.block],
|
||||
].filter(Boolean);
|
||||
const entry = raw && { id: raw.id, status: raw.status, height: raw.height, block: raw.block, parent: raw.parent ?? null,
|
||||
metaprotocol: raw.metaprotocol ?? null, contentType: raw.contentType, sat: raw.sat, location: raw.location };
|
||||
|
||||
Reference in new issue
Block a user