// commitLine — which frames form the chain, which are forks. Pure; no I/O.
import '../lib/bare-globals.js'
import test from 'brittle'
import { commitLine, frameKey, replayCids, healPlan } from '../lib/commit-line.js'
// Marked frames (chain: 1) — what the fork-aware signer writes. seq follows rev unless a test sets it.
const f = (rev, cid, prev, extra = {}) => ({ rev, commitCid: cid, prev, seq: parseInt(rev.slice(1), 36), chain: 1, ops: [], ...extra })
const cids = (frames) => frames.map((x) => x.commitCid)
test('a single chain has no losers and replays in rev order', async (t) => {
const log = [f('3b', 'c2', 'c1'), f('3a', 'c1', null), f('3c', 'c3', 'c2')]
const r = commitLine(log)
t.is(r.head.commitCid, 'c3')
t.alike(cids(r.line), ['c1', 'c2', 'c3'])
t.is(r.losers.length, 0)
t.is(r.unhealed.length, 0)
})
test('legacy frames without commitCid/prev stay on the line', async (t) => {
const r = commitLine([{ rev: 'b', seq: 2 }, { rev: 'a', seq: 1 }])
t.is(r.line.length, 2, 'nothing dropped')
t.is(r.losers.length, 0)
})
test('an equal-rev fork: the lower CID is the head, the other is a loser', async (t) => {
const log = [f('3a', 'c1', null), f('3b', 'cB', 'c1'), f('3b', 'cA', 'c1')]
const r = commitLine(log)
t.is(r.head.commitCid, 'cA', 'tie broken by the lower CID — the same on every device')
t.alike(cids(r.line), ['c1', 'cA'])
t.alike(cids(r.losers), ['cB'])
t.alike(cids(r.unhealed), ['cB'])
})
test('a losing branch longer than one frame: every frame on it is a loser', async (t) => {
// c1 -> x1 -> x2 (rev 3b, 3c) vs c1 -> y1 (rev 3d): y1 is the head, x1 and x2 lost.
const log = [f('3a', 'c1', null), f('3b', 'x1', 'c1'), f('3c', 'x2', 'x1'), f('3d', 'y1', 'c1')]
const r = commitLine(log)
t.is(r.head.commitCid, 'y1')
t.alike(cids(r.line), ['c1', 'y1'])
t.alike(cids(r.losers), ['x1', 'x2'])
})
test('a line frame naming a loser in `heals` marks it healed', async (t) => {
const log = [f('3a', 'c1', null), f('3b', 'cA', 'c1'), f('3b', 'cB', 'c1'), f('3c', 'h', 'cA', { heals: ['cB'] })]
const r = commitLine(log)
t.alike(cids(r.losers), ['cB'])
t.is(r.unhealed.length, 0)
})
test('a genesis fork (two frames with no prev)', async (t) => {
const r = commitLine([f('3a', 'g2', null), f('3a', 'g1', null)])
t.is(r.head.commitCid, 'g1')
t.alike(cids(r.losers), ['g2'])
})
test('frameKey keeps rev order and is unique per commit', async (t) => {
t.is(frameKey('3abc', 'bafy1'), '__commit__/3abc/bafy1')
t.not(frameKey('3abc', 'bafy1'), frameKey('3abc', 'bafy2'))
})
test('replayCids applies create/update/delete by CID', async (t) => {
const m = replayCids([
{ ops: [{ action: 'create', path: 'p/a', cid: 'x' }, { action: 'create', path: 'p/b', cid: 'y' }] },
{ ops: [{ action: 'update', path: 'p/a', cid: 'z' }, { action: 'delete', path: 'p/b', cid: null }] }
])
t.alike([...m], [['p/a', 'z']])
})
test('UNMARKED legacy history is never a fork and keeps seq order (restarted chains, falling revs)', async (t) => {
// The shapes measured on our real served history: a second genesis (prev=null) after a re-author, a
// rev that falls in seq order, a prev link that skips.
const legacy = [
{ rev: '3a', commitCid: 'g1', prev: null, seq: 1 },
{ rev: '3b', commitCid: 'c2', prev: 'g1', seq: 2 },
{ rev: '3d', commitCid: 'g2', prev: null, seq: 3 },
{ rev: '3c', commitCid: 'c4', prev: 'missing', seq: 4 },
{ rev: '3e', commitCid: 'c5', prev: 'g2', seq: 5 }
]
const r = commitLine(legacy)
t.is(r.losers.length, 0, 'nothing classified as a fork')
t.alike(cids(r.line), ['g1', 'c2', 'g2', 'c4', 'c5'], 'exactly the old seq order')
})
test('a marked fork on top of legacy history: only the marked sibling loses', async (t) => {
const log = [
{ rev: '3a', commitCid: 'g1', prev: null, seq: 1 },
{ rev: '3c', commitCid: 'g2', prev: null, seq: 2 }, // legacy re-author, never a loser
f('3d', 'mB', 'g2', { seq: 3 }),
f('3d', 'mA', 'g2', { seq: 3 })
]
const r = commitLine(log)
t.alike(cids(r.losers), ['mB'])
t.alike(cids(r.line), ['g1', 'g2', 'mA'])
})
// healPlan — f(rev, cid, prev, {ops, heals, asserts}); revs are single letters after '3' for readability.
const op = (path) => ({ action: 'create', path, cid: 'x' })
test('healPlan: no forks, nothing to heal', async (t) => {
t.is(healPlan(commitLine([f('3a', 'c1', null), f('3b', 'c2', 'c1')])), null)
})
test('healPlan: a fork opens a window at its fork point covering BOTH sides', async (t) => {
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')] })]
const plan = healPlan(commitLine(log))
t.is(plan.from, '3a', 'window starts after the fork point')
t.alike([...plan.paths].sort(), ['p/a', 'p/a2', 'p/b'], 'both branches, not the common history')
t.alike(plan.heals, ['cB'])
})
test('healPlan: an effective heal closes the window', async (t) => {
const log = [f('3a', 'c1', null), f('3b', 'cA', 'c1', { ops: [op('p/a')] }), f('3b', 'cB', 'c1', { ops: [op('p/b')] }),
f('3c', 'h', 'cA', { ops: [op('p/b')], heals: ['cB'], asserts: '3a' })]
t.is(healPlan(commitLine(log)), null)
})
test('healPlan: a fork AT OR ABOVE the heal makes it skippable — the window reopens from the earliest fork', async (t) => {
// cX forked from cA and reached rev 3c = the heal's rev: a consumer that took cX dropped the heal.
const log = [f('3a', 'c1', null), f('3b', 'cA', 'c1', { ops: [op('p/a')] }), f('3b', 'cB', 'c1', { ops: [op('p/b')] }),
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')] })]
const plan = healPlan(commitLine(log))
t.ok(plan, 'not covered')
t.is(plan.from, '3a', 'back to the first fork point, because the consumer may have skipped the heal')
t.ok(plan.paths.has('p/b') && plan.paths.has('p/x') && plan.paths.has('p/n'), 'restates every path since then')
})
test('healPlan: a fork the heal never KNEW (arrived late, below it) reopens the window', async (t) => {
const log = [f('3a', 'c1', null), f('3b', 'cA', 'c1', { ops: [op('p/a')] }), f('3b', 'cB', 'c1', { ops: [op('p/b')] }),
f('3c', 'h', 'cA', { ops: [op('p/b')], heals: ['cB'], asserts: '3a' }), f('3b', 'cLate', 'c1', { ops: [op('p/late')] })]
const plan = healPlan(commitLine(log))
t.ok(plan && plan.paths.has('p/late'), 'the late fork\'s paths are restated')
t.alike(plan.heals.sort(), ['cB', 'cLate'])
})
const upd = (path) => ({ action: 'update', path, cid: 'y' })
test('healPlan: a partial heal\'s update/delete paths are done; its CREATE paths are still owed', async (t) => {
const log = [f('3a', 'c1', null), f('3b', 'cA', 'c1', { ops: [op('p/a'), op('p/c')] }), f('3b', 'cB', 'c1', { ops: [op('p/b')] }),
f('3c', 'part1', 'cA', { ops: [upd('p/a'), op('p/c')], restates: '3a' })]
const plan = healPlan(commitLine(log))
t.alike([...plan.paths].sort(), ['p/b', 'p/c'], 'p/a restated by update; p/c only by a create, which a stale consumer ignores')
t.is(plan.partsFrom, '3c')
t.ok(plan.window.has('p/a'), 'the window still names every path')
})
test('healPlan: a fork reaching a partial means it may have been skipped — its paths are owed again', async (t) => {
const log = [f('3a', 'c1', null), f('3b', 'cA', 'c1', { ops: [op('p/a')] }), f('3b', 'cB', 'c1', { ops: [op('p/b')] }),
f('3c', 'part1', 'cA', { ops: [upd('p/a')], restates: '3a' }), f('3c', 'zSib', 'cA', { ops: [op('p/z')] })]
const plan = healPlan(commitLine(log))
t.ok(plan.paths.has('p/a'), 'restated again')
t.is(plan.partsFrom, null)
})
test('healPlan: a split heal is effective only if no fork reaches its FIRST part', async (t) => {
const base = [f('3a', 'c1', null), f('3b', 'cA', 'c1', { ops: [op('p/a')] }), f('3b', 'cB', 'c1', { ops: [op('p/b')] }),
f('3c', 'part1', 'cA', { ops: [upd('p/a')], restates: '3a' }),
f('3d', 'final', 'part1', { ops: [upd('p/b')], asserts: '3a', heals: ['cB'], partsFrom: '3c' })]
t.is(healPlan(commitLine(base)), null, 'closed')
// A fork at the first part's rev, known to the final: a consumer may have skipped part1.
const forked = base.concat([f('3c', 'zFork', 'cA', { ops: [op('p/z')] })])
forked[4] = f('3d', 'final', 'part1', { ops: [upd('p/b')], asserts: '3a', heals: ['cB', 'zFork'], partsFrom: '3c' })
t.ok(healPlan(commitLine(forked)), 'reopened')
})