// EQUIVALENCE PROOF — deriving the repo CAR from the record FILES must be byte-identical to serving it
// from the projected membrane-repo Hyperbee.
//
// This is the safety argument for SPEC-atproto-overlay-storage §6.3 ("the virtual device stores nothing
// of its own — it derives"). Today `projectRepo` copies EVERY record into an append-only Hyperbee
// (membrane-repo:<did>), which a consumer replicates again — so record bytes exist THREE times (the
// files, this core, the consumer's replica) for data the files already hold in full. The core can only
// be deleted if what replaces it is bit-for-bit the same, which is exactly what this asserts.
//
// If these two CARs ever diverge, deleting the projection would change what bsky sees — so this test is
// the gate on that change, not a nicety.
//
// Run: bare test/derive-vs-project.test.mjs
import '../lib/bare-globals.js'
import test from 'brittle'
import fs from 'bare-fs'
import b4a from 'b4a'
import crypto from 'hypercore-crypto'
import { OverlayRepoStore } from 'atproto-store/lib/overlay-repo-store.js'
import { RepoStore } from 'atproto-store/lib/repo-store.js'
import { openCorestore } from 'atproto-store/lib/corestore.js'
import { getRepoFromCommit } from 'atproto-store/lib/read-view.js'
import { projectRepo } from 'atproto-room/lib/serve-membrane.js'
import { SwarmDevice } from '../lib/swarm-device.js'
const DID = 'did:plc:derivevsproject00000000'
const KEY = (() => { const k = b4a.alloc(32); k[31] = 11; return k })() // fixed scalar → deterministic sig
const tmp = () => { const d = '/tmp/hpp-dvp-' + Math.floor(Math.random() * 1e9); fs.mkdirSync(d, { recursive: true }); return d }
// A minimal substrate: this device is the signer, so sign() runs the full materialize path.
function memKV () {
const m = new Map()
return {
async set (k, v) { m.set(k, v) },
async del (k) { m.delete(k) },
async get (k) { return m.has(k) ? m.get(k) : null },
async list (prefix = '') { const o = {}; for (const [k, v] of m) if (k.startsWith(prefix)) o[k] = v; return o },
async listNow (prefix = '') { return this.list(prefix) },
async getNow (k) { return this.get(k) },
holdsSigner: () => true
}
}
const RECORDS = [
['app.bsky.actor.profile', 'self', { $type: 'app.bsky.actor.profile', displayName: 'Derive Test', description: 'files are the source of truth' }],
['app.bsky.feed.post', '3kaaa', { $type: 'app.bsky.feed.post', text: 'first', createdAt: '2026-07-26T00:00:00.000Z', langs: ['en'] }],
['app.bsky.feed.post', '3kbbb', { $type: 'app.bsky.feed.post', text: 'second', createdAt: '2026-07-26T00:00:01.000Z', langs: ['en'] }],
['app.bsky.graph.follow', '3kccc', { $type: 'app.bsky.graph.follow', subject: 'did:plc:other', createdAt: '2026-07-26T00:00:02.000Z' }]
]
async function mkSignedDevice (t) {
const store = await new OverlayRepoStore(tmp(), { did: DID }).ready() // files only — no overlay needed
const device = new SwarmDevice(memKV(), { did: DID, privateKey: KEY, self: 'signer', firehose: true, recordStore: store })
for (const [c, r, rec] of RECORDS) await device.putRecord(c, r, rec)
const head = await device.sign({ force: true })
t.ok(head && head.signedCommit, 'device signed a head')
return { device, store, head }
}
test('DERIVED CAR (from files) is byte-identical to the PROJECTED CAR (from membrane-repo)', async (t) => {
const { device, store, head } = await mkSignedDevice(t)
// A: derive straight from the record FILES — what get-repo-car serves, holding nothing.
const derived = await device.exportCar()
// B: project every record into the membrane-repo Hyperbee, then serve from THAT — today's path.
const projected = await new RepoStore(openCorestore(tmp()), { name: 'membrane-repo:' + DID }).ready()
await projectRepo(device, projected)
const fromCore = await getRepoFromCommit(projected, head.signedCommit)
t.is(derived.commitCid, fromCore.commitCid.toString(), 'same commit CID')
t.is(derived.dataCid, fromCore.dataCid.toString(), 'same MST data root')
t.ok(b4a.equals(derived.carBytes, fromCore.carBytes), 'CAR bytes are BYTE-IDENTICAL — the projection adds nothing')
t.is(derived.recordCount, RECORDS.length, 'all records present in the derived CAR')
await projected.close(); await store.close()
})
test('the derived CAR is self-verifying — it refuses to serve a repo that does not match its commit', async (t) => {
const { device, store } = await mkSignedDevice(t)
// Mutate a record WITHOUT re-signing: the files no longer reproduce the signed head's data root.
await store.put('app.bsky.feed.post', '3kaaa', { $type: 'app.bsky.feed.post', text: 'TAMPERED', createdAt: '2026-07-26T00:00:00.000Z', langs: ['en'] })
await t.exception(() => device.exportCar(), /repo\/commit mismatch/, 'derive REFUSES a record set that does not match the signature')
await store.close()
})
test('a record added after signing is not served until the next sign (no silent drift)', async (t) => {
const { device, store, head } = await mkSignedDevice(t)
await device.putRecord('app.bsky.feed.post', '3kddd', { $type: 'app.bsky.feed.post', text: 'unsigned', createdAt: '2026-07-26T00:00:03.000Z', langs: ['en'] })
// exportCar asserts the rebuilt root == the SIGNED head's root, so an unsigned addition must throw
// rather than be quietly published — the same guarantee the projected core gives.
await t.exception(() => device.exportCar(), /repo\/commit mismatch/, 'an unsigned record cannot leak into a served CAR')
const next = await device.sign({ force: true })
t.ok(next && next.rev > head.rev, 'signing advances the head')
const after = await device.exportCar()
t.is(after.recordCount, RECORDS.length + 1, 'and the new record is served once signed')
await store.close()
})