| … | … | @@ -10,6 +10,7 @@ import { tmpdir } from 'node:os' |
| 10 | 10 | import b4a from 'b4a' |
| 11 | 11 | import { RepoStore } from '../lib/repo-store.js' |
| 12 | 12 | import { OverlayRepoStore } from '../lib/overlay-repo-store.js' |
| 13 | import { RepoManager } from '../lib/repo-manager.js' |
| 13 | 14 | import { getRepo } from '../lib/read-view.js' |
| 14 | 15 | |
| 15 | 16 | const DID = 'did:plc:test123' |
| … | … | @@ -98,3 +99,38 @@ test('update overwrites in place (one file, not an append) and re-derives correc |
| 98 | 99 | t.is((await ov.records()).length, RECORDS.length, 'still exactly one file per record (no append)') |
| 99 | 100 | await ov.close() |
| 100 | 101 | }) |
| 102 | |
| 103 | test('RepoManager.applyWrite drives OverlayRepoStore identically to Hyperbee (write path + firehose proofs)', async (t) => { |
| 104 | const ov = await new OverlayRepoStore(tmp(t)).ready() |
| 105 | const hb = await new RepoStore(tmp(t)).ready() |
| 106 | const mo = await new RepoManager(ov, { did: DID, privateKey: KEY }).init() |
| 107 | const mh = await new RepoManager(hb, { did: DID, privateKey: KEY }).init() |
| 108 | |
| 109 | // create x2 -> update -> delete: exercises the incremental MST-inversion covering proof on files. |
| 110 | const ops = [ |
| 111 | { action: 'create', collection: 'app.bsky.feed.post', rkey: '3kaaa', record: { $type: 'app.bsky.feed.post', text: 'hello', createdAt: '2026-01-01T00:00:00.000Z' } }, |
| 112 | { action: 'create', collection: 'app.bsky.feed.post', rkey: '3kbbb', record: { $type: 'app.bsky.feed.post', text: 'world', createdAt: '2026-01-02T00:00:00.000Z' } }, |
| 113 | { action: 'update', collection: 'app.bsky.feed.post', rkey: '3kaaa', record: { $type: 'app.bsky.feed.post', text: 'edited', createdAt: '2026-01-01T00:00:00.000Z' } }, |
| 114 | { action: 'delete', collection: 'app.bsky.feed.post', rkey: '3kbbb' } |
| 115 | ] |
| 116 | for (const op of ops) { |
| 117 | // applyWrite asserts its own covering-proof invariants internally (proof.dataCid==signed data, |
| 118 | // proof.oldRoot==prevData) — so a non-throw here PROVES the firehose #commit proof is valid on files. |
| 119 | const ro = await mo.applyWrite(op) |
| 120 | const rh = await mh.applyWrite(op) |
| 121 | t.ok(ro.newBlocks && ro.newBlocks.length > 0, `overlay #commit carries proof blocks (${op.action} ${op.rkey})`) |
| 122 | t.is(ro.dataCid.toString(), rh.dataCid.toString(), `MST root matches Hyperbee after ${op.action} ${op.rkey}`) |
| 123 | } |
| 124 | |
| 125 | t.is((await ov.loadHead()).rev, mo.rev, 'overlay head persisted (loadHead rev == manager rev)') |
| 126 | |
| 127 | // final derived repo byte-identical at a fixed rev — the write sequence left both stores in the same state |
| 128 | const a = await getRepo(hb, META); const b = await getRepo(ov, META) |
| 129 | t.is(b.commitCid.toString(), a.commitCid.toString(), 'final commit CID identical after write sequence') |
| 130 | t.ok(b4a.equals(b.carBytes, a.carBytes), 'final CAR byte-identical') |
| 131 | |
| 132 | // O(current state): after create x2 + delete x1 the deleted record's file is GONE (not a tombstone) |
| 133 | t.is((await ov.records()).length, 1, 'only current records remain on disk') |
| 134 | |
| 135 | await ov.close(); await hb.close() |
| 136 | }) |