Files
orgchain/bin/lineage-index.mjs
teahubxyzandClaude Opus 5.5 5a7f11e49d Orgchain v1.14.1: a belt counts only on its seat's own sat
Placement is now a rule of lineage/v0, not just how Orgchain inscribes:
an award counts only if it is inscribed on its recipient's own sat, so any
indexer can check that a belt is soulbound. The reducer compares sats
(satOf): the lab's sat numbers, ord's, or, without ord's sat index, its
satpoints, which two inscriptions share exactly when they share a sat. ord
events now carry the sat number when ord has one. Belts inscribed anywhere
else, as before v1.14, are rejected.

A promotion is another reinscription on the same sat, over the belt before
it: each badge keeps its skill's earlier belts, and Inspect shows the
ladder ("re-inscribed over white (beat 5)"). The sample's sol now goes
white, then green. An award and its recipient's own close no longer
conflict within a block, since both spend the recipient's inscription.

Rehearsed again on Bitcoin Core regtest from height 178: sixteen beats and
133 transactions; ord shows sol's sat carrying sol, its white belt and its
green belt, in order, each belt charmed reinscription and vindicated, and
the compare matches. The fixture is re-recorded.

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

35 lines
2.9 KiB
JavaScript

#!/usr/bin/env node
// lineage-index: read lineage/v0 events from an ord server and print the trees they imply, with the same reducer the
// lab uses. --compare <lab.json> checks that ord and a lab backup tell the same story (tools/regtest/README.md).
// node bin/lineage-index.mjs [--ord http://127.0.0.1:8088] [--from <height>] [--compare <lab.json>]
import { readFileSync } from 'node:fs';
import { parseArgs } from 'node:util';
import { decodeLab } from '../src/orgchain/store.js';
import { census, rows, summary } from '../src/orgchain/lineage.js';
import { ordClient, ordEvents, ordState } from '../src/indexer/ord.js';
import { compareWithLab } from '../src/indexer/compare.js';
const { values } = parseArgs({ options: { ord: { type: 'string', default: 'http://127.0.0.1:8088' }, from: { type: 'string', default: '0' },
compare: { type: 'string' } } });
const events = await ordEvents(ordClient(values.ord), { from: Number(values.from) });
const state = ordState(events), plural = (n, w) => `${n} ${w}${n === 1 ? '' : 's'}`;
for (const root of state.roots) {
const c = census(state, root.id);
console.log(`\n${root.name} · ${plural(c.orgs, 'org')}, ${plural(c.agents, 'agent')}, ${plural(c.belts, 'belt')}, ${plural(c.decisions, 'decision')}`);
console.log(rows(state, root.id).map(r => (r.type === 'group' ? `${r.guide}${r.label}`
: `${r.guide}${r.path} ${r.node.name}${r.moved ? ` · moved to ${state.nodes.get(r.node.parent).name}` : r.away ? ' · graduated' : ''}`)).join('\n'));
}
const moves = [...state.nodes.values()].flatMap(n => n.moves.map(m => `${n.name} ${m.from ? `from ${state.nodes.get(m.from).name}` : 'whole'} to ${state.nodes.get(m.to).name}`));
if (moves.length) console.log(`\n${plural(moves.length, 'tree')} changed hands: ${moves.join('; ')}`);
const rejected = state.log.filter(e => !e.ok);
console.log(`\n${plural(events.length, 'lineage/v0 event')} on ord, ${plural(state.items.size, 'item')}${rejected.length ? `, ${rejected.length} rejected:` : ''}`);
for (const e of rejected) console.log(` ${summary(e, state)} (${e.id})`);
const rings = [...state.nodes.values()].filter(n => n.keyring && !n.keyring.retired);
if (rings.length) console.log(`${plural(rings.length, 'keyring')} bind ${plural(state.bound.size, 'key')}: ${rings.map(n => n.name).join(', ')}`);
if (values.compare) {
const { problems, ...n } = compareWithLab(decodeLab(readFileSync(values.compare, 'utf8')), events);
console.log(problems.length ? `\nord and the lab differ:\n${problems.map(p => ` ${p}`).join('\n')}`
: `\nSame story as ${values.compare}: ${plural(n.events, "event")}, ${plural(n.nodes, "node")} and ${plural(n.items, "item")}, each event in the same beat and every inscription on the same sat, with the same charms.${n.reordered ? ` The miner ordered ${n.reordered} independent events differently within their blocks, which changes nothing.` : ""}`);
process.exitCode = problems.length ? 1 : 0;
}