// FIREHOSE — consecutive finalized heads become atproto #commit frames (the DIFF: ops + minimal
// proof blocks + prevData), byte-exact via atproto-store's codec (what the bridge consumes), and
// DETERMINISTIC across co-owners. Covers create/update/delete and a genesis.
import test from 'brittle'
import { deriveAtprotoKey, createAtprotoIdentity } from 'atproto-identity'
import { readCar } from 'atproto-repo/lib/car.js'
import { commitFrame, decodeCommitFrame } from 'atproto-store/lib/firehose.js'
import { buildCommitWrite } from '../lib/firehose.js'
import { nextRev } from '../lib/rev.js'
const MNEMONIC = 'test test test test test test test test test test test junk'
const ID = createAtprotoIdentity(MNEMONIC, { handle: 'x.7rnx.net', pdsUrl: 'https://pds.7rnx.net' })
const KEY = deriveAtprotoKey(MNEMONIC)
const rec = (t) => ({ $type: 'app.bsky.feed.post', text: t, createdAt: '2026-07-23T00:00:00.000Z', langs: ['en'] })
const R = (k, v) => ({ key: 'app.bsky.feed.post/' + k, value: rec(v) })
const opFor = (w, path) => w.ops.find((o) => o.path === path)
test('firehose #commit: create/update/delete diffs, decodable frames, deterministic', async (t) => {
const did = ID.did; const pk = KEY.privateKey
// genesis: create 3a
const rev1 = nextRev(null)
const w1 = await buildCommitWrite({ oldRecords: [], newRecords: [R('3a', 'one')], did, rev: rev1, prev: null, privateKey: pk })
t.is(w1.ops.length, 1); t.is(opFor(w1, 'app.bsky.feed.post/3a').action, 'create')
// add 3b
const rev2 = nextRev(rev1)
const w2 = await buildCommitWrite({ oldRecords: [R('3a', 'one')], newRecords: [R('3a', 'one'), R('3b', 'two')], did, rev: rev2, since: rev1, prev: w1.commitCid, privateKey: pk })
t.is(opFor(w2, 'app.bsky.feed.post/3b').action, 'create')
t.absent(opFor(w2, 'app.bsky.feed.post/3a'), 'unchanged record produces no op')
// update 3a
const rev3 = nextRev(rev2)
const w3 = await buildCommitWrite({ oldRecords: [R('3a', 'one'), R('3b', 'two')], newRecords: [R('3a', 'one-edited'), R('3b', 'two')], did, rev: rev3, since: rev2, prev: w2.commitCid, privateKey: pk })
const up = opFor(w3, 'app.bsky.feed.post/3a')
t.is(up.action, 'update'); t.ok(up.prev, 'update carries prev CID for inductive proof')
// delete 3b
const rev4 = nextRev(rev3)
const w4 = await buildCommitWrite({ oldRecords: [R('3a', 'one-edited'), R('3b', 'two')], newRecords: [R('3a', 'one-edited')], did, rev: rev4, since: rev3, prev: w3.commitCid, privateKey: pk })
const del = opFor(w4, 'app.bsky.feed.post/3b')
t.is(del.action, 'delete'); t.ok(del.prev, 'delete carries prev CID for exclusion proof')
// frame + decode round-trip
const frame = await commitFrame(w2, { seq: 2, time: '2026-07-23T00:00:00.000Z' })
const dec = decodeCommitFrame(frame)
t.is(dec.header.t, '#commit', 'decodes as #commit')
t.is(dec.body.commit.toString(), w2.commitCid.toString(), 'frame commit CID matches')
t.is(dec.body.rev, rev2, 'frame rev matches')
t.is(dec.body.repo, did, 'frame repo = did')
t.is(dec.body.ops.length, 1, 'frame carries the diff ops')
const car = await readCar(dec.body.blocks)
t.is(car.roots[0].toString(), w2.commitCid.toString(), 'frame CAR root = the commit')
t.ok(car.blocks.has(opFor(w2, 'app.bsky.feed.post/3b').cid.toString()), 'the new record block travels in the frame')
// DETERMINISM: a co-owner rebuilding the same transition gets the identical commit + ops.
const w2b = await buildCommitWrite({ oldRecords: [R('3a', 'one')], newRecords: [R('3a', 'one'), R('3b', 'two')], did, rev: rev2, since: rev1, prev: w1.commitCid, privateKey: pk })
t.is(w2.commitCid.toString(), w2b.commitCid.toString(), 'firehose commit deterministic across co-owners')
t.is(Buffer.compare(Buffer.from(w2.signedCommit.sig), Buffer.from(w2b.signedCommit.sig)), 0, 'identical signature')
})