OverviewFilesCommitsIssuesDocsReleasesPeers

atproto-room commits / 266768e

0.2.24: healPlan — a heal that converges every relay-fed consumer, not just one fork's
0.2.23's heal assumed a consumer sits on ONE fork. Behind a relay that accepts whatever arrives with a higher rev, a consumer hops between branches: the autobee signer spike's fuzz traced one that took two commits of one losing branch, two of another, dropped both heals of the first fork (an equal rev arrived first), then a line commit. Its state matched no branch, and the fork counted as healed only because a commit NAMED it. healPlan: a heal restates every path any frame touched since the earliest uncovered fork point and records `asserts` (that rev) and `heals` (forks it knew). A heal is effective — cannot have been skipped and covered all below it — only if no fork frame has a rev at or above it and every fork below it was known to it or covered by an earlier effective heal. Tests: 5 healPlan cases (covered, outranked heal reopens, late unknown fork reopens). Suite 78/78. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
266768e131bdc056bce3ca5d3231cd10d5d73315
by plankatron <fleeky@gmail.com> · 2026-10-13T15:34:59.000Z

Changed files (3) +93 -2

lib/commit-line.js +52 -0
……@@ -100,6 +100,58 @@ export function commitLine (frames) {
100100 return { head, line, losers: loserFrames, unhealed }
101101}
102102
103// healPlan — what the next commit must restate so that EVERY consumer converges, whatever it received.
104//
105// A consumer behind a relay accepts whatever arrives with a higher rev, so after repeated partitions it
106// hops between branches and holds a mix no branch ever had (found by seeded fuzz, 2026-10-13: the
107// consumer took two commits of one losing branch, two of another, dropped both heals of the first fork
108// because an equal rev had arrived first). The signer cannot know what any consumer received, so a heal
109// must not depend on it: it restates the merged truth for every path any frame touched since the fork
110// point — `update` for a record that exists, `delete` for one that does not — and records the window it
111// asserted (`asserts`, the fork-point rev) and the forks it knew (`heals`).
112//
113// A heal is EFFECTIVE — no consumer can have skipped it, and it covered everything below it — when no fork
114// frame has a rev at or above it, and every fork frame below it is either one it knew or one an earlier
115// effective heal covered. Forks covered by an effective heal are done; any other fork reopens a window.
116//
117// Returns null (nothing to heal) or { from, paths: Set, heals: [commitCid] }.
118export function healPlan ({ line, losers }) {
119 if (!losers || losers.length === 0) return null
120 const all = line.concat(losers)
121 const byCid = new Map()
122 for (const f of all) if (f.commitCid) byCid.set(String(f.commitCid), f)
123 const loserCids = new Set(losers.map((f) => String(f.commitCid)))
124 const forkPointRev = (f) => {
125 let r = f
126 while (r.prev && loserCids.has(String(r.prev))) r = byCid.get(String(r.prev))
127 const p = r.prev ? byCid.get(String(r.prev)) : null
128 return p ? p.rev : ''
129 }
130
131 // Walk the line's asserting heals in order; each effective one covers the forks it knew.
132 const covered = new Set()
133 for (const h of line) {
134 if (h.asserts === undefined) continue
135 const knew = new Set((h.heals || []).map(String))
136 let effective = true
137 for (const l of losers) {
138 const c = String(l.commitCid)
139 if (l.rev >= h.rev) { effective = false; break }
140 if (!covered.has(c) && !knew.has(c)) { effective = false; break }
141 }
142 if (effective) for (const c of knew) covered.add(c)
143 }
144
145 const open = losers.filter((l) => !covered.has(String(l.commitCid)))
146 if (open.length === 0) return null
147 let from = null
148 for (const l of open) { const r = forkPointRev(l); if (from === null || r < from) from = r }
149 const paths = new Set()
150 for (const f of all) if (f.rev > from) for (const op of (f.ops || [])) paths.add(op.path)
151 const heals = losers.filter((l) => l.rev > from).map((l) => String(l.commitCid))
152 return { from, paths, heals }
153}
154
103155// path -> record CID string after replaying `frames` in order onto `start` (a Map, copied). Uses only the
104156// CIDs the ops carry, so it needs no record bytes.
105157export function replayCids (frames, start = new Map()) {
package.json +1 -1
……@@ -1,6 +1,6 @@
11{
22 "name": "atproto-room",
3 "version": "0.2.23",
3 "version": "0.2.24",
44 "description": "Attestation-gated, encrypted multi-writer room engine — StateBase (keet-model admission) + Lease + signer + RepoBase + LabelBase/LabelRoom + serve-membrane. Extracted from hyper-proto-peer; consumed by the atproto peer (repo devices) and the co-authored labeler.",
55 "type": "module",
66 "main": "index.js",
test/commit-line.test.mjs +40 -1
……@@ -1,7 +1,7 @@
11// commitLine — which frames form the chain, which are forks. Pure; no I/O.
22import '../lib/bare-globals.js'
33import test from 'brittle'
4import { commitLine, frameKey, replayCids } from '../lib/commit-line.js'
4import { commitLine, frameKey, replayCids, healPlan } from '../lib/commit-line.js'
55
66// Marked frames (chain: 1) — what the fork-aware signer writes. seq follows rev unless a test sets it.
77const f = (rev, cid, prev, extra = {}) => ({ rev, commitCid: cid, prev, seq: parseInt(rev.slice(1), 36), chain: 1, ops: [], ...extra })
……@@ -92,3 +92,42 @@ test('a marked fork on top of legacy history: only the marked sibling loses', as
9292 t.alike(cids(r.losers), ['mB'])
9393 t.alike(cids(r.line), ['g1', 'g2', 'mA'])
9494})
95
96// healPlan — f(rev, cid, prev, {ops, heals, asserts}); revs are single letters after '3' for readability.
97const op = (path) => ({ action: 'create', path, cid: 'x' })
98
99test('healPlan: no forks, nothing to heal', async (t) => {
100 t.is(healPlan(commitLine([f('3a', 'c1', null), f('3b', 'c2', 'c1')])), null)
101})
102
103test('healPlan: a fork opens a window at its fork point covering BOTH sides', async (t) => {
104 const log = [f('3a', 'c1', null, { ops: [op('p/base')] }), f('3b', 'cA', 'c1', { ops: [op('p/a')] }), f('3b', 'cB', 'c1', { ops: [op('p/b')] }), f('3c', 'cA2', 'cA', { ops: [op('p/a2')] })]
105 const plan = healPlan(commitLine(log))
106 t.is(plan.from, '3a', 'window starts after the fork point')
107 t.alike([...plan.paths].sort(), ['p/a', 'p/a2', 'p/b'], 'both branches, not the common history')
108 t.alike(plan.heals, ['cB'])
109})
110
111test('healPlan: an effective heal closes the window', async (t) => {
112 const log = [f('3a', 'c1', null), f('3b', 'cA', 'c1', { ops: [op('p/a')] }), f('3b', 'cB', 'c1', { ops: [op('p/b')] }),
113 f('3c', 'h', 'cA', { ops: [op('p/b')], heals: ['cB'], asserts: '3a' })]
114 t.is(healPlan(commitLine(log)), null)
115})
116
117test('healPlan: a fork AT OR ABOVE the heal makes it skippable — the window reopens from the earliest fork', async (t) => {
118 // cX forked from cA and reached rev 3c = the heal's rev: a consumer that took cX dropped the heal.
119 const log = [f('3a', 'c1', null), f('3b', 'cA', 'c1', { ops: [op('p/a')] }), f('3b', 'cB', 'c1', { ops: [op('p/b')] }),
120 f('3c', 'h', 'cA', { ops: [op('p/b')], heals: ['cB'], asserts: '3a' }), f('3c', 'hX', 'cA', { ops: [op('p/x')] }), f('3d', 'n', 'h', { ops: [op('p/n')] })]
121 const plan = healPlan(commitLine(log))
122 t.ok(plan, 'not covered')
123 t.is(plan.from, '3a', 'back to the first fork point, because the consumer may have skipped the heal')
124 t.ok(plan.paths.has('p/b') && plan.paths.has('p/x') && plan.paths.has('p/n'), 'restates every path since then')
125})
126
127test('healPlan: a fork the heal never KNEW (arrived late, below it) reopens the window', async (t) => {
128 const log = [f('3a', 'c1', null), f('3b', 'cA', 'c1', { ops: [op('p/a')] }), f('3b', 'cB', 'c1', { ops: [op('p/b')] }),
129 f('3c', 'h', 'cA', { ops: [op('p/b')], heals: ['cB'], asserts: '3a' }), f('3b', 'cLate', 'c1', { ops: [op('p/late')] })]
130 const plan = healPlan(commitLine(log))
131 t.ok(plan && plan.paths.has('p/late'), 'the late fork\'s paths are restated')
132 t.alike(plan.heals.sort(), ['cB', 'cLate'])
133})