OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-proto-peer / test / derive-vs-project.test.mjs

105 lines · javascript ref:
// 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()
})