// com.atproto.sync.getRepo?since over the REAL membrane HTTP surface (org #34).
//
// Until 0.9.45 `since` was accepted and thrown away: server.js called front.getRepo() with NO arguments,
// so the parameter never reached the peer and the identical full CAR came back — measured byte for byte,
// 45,563 bytes for wj62. The hazard was never the wasted bytes. It is that a caller passing `since` got
// correct results BY ACCIDENT, and would have become silently wrong the day partial CARs landed.
//
// So the contract under test is not "a smaller CAR comes back". It is:
// 1. the response STATES which kind of CAR it is, in headers, on every path
// 2. a partial CAR is genuinely partial — not self-contained, blocks deliberately absent
// 3. an unanswerable `since` degrades to the FULL CAR and says it was not honoured
// 4. no `since` is byte-identical to before, because every existing caller passes nothing
// (2) is what makes (1) load-bearing: a consumer that guesses wrong on a partial CAR gets a
// missing-block error, so guessing must never be necessary.
import test from 'brittle'
import { mkdtempSync, rmSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import c from 'compact-encoding'
import Hyperswarm from 'hyperswarm'
import createTestnet from '@hyperswarm/testnet'
import ProtomuxRPC from 'protomux-rpc'
import { pushAnnounce } from './serve-announce.js'
import { openCorestore } from 'atproto-store/lib/corestore.js'
import { RepoStore } from 'atproto-store/lib/repo-store.js'
import { RepoManager } from 'atproto-store/lib/repo-manager.js'
import { FirehoseSequencer } from 'atproto-store/lib/sequencer.js'
import { keyFromPrivate } from 'atproto-identity'
import { discoveryTopic } from 'atproto-identity/lib/discovery.js'
import { readCar, listRecords, loadCommit } from 'atproto-repo/lib/repo.js'
import { createPdsServer } from '../lib/server.js'
import { DidPeerRouter } from 'atproto-did-peer'
process.env.PGH_SIDECAR_SOCK = join(tmpdir(), `no-sidecar-since-${process.pid}.sock`)
const DID = 'did:plc:sincetest'
const HANDLE = 'sincetest.7rnx.net'
const PRIV = new Uint8Array(32).fill(7)
const SERVER_DID = 'did:web:pds.7rnx.net'
async function waitFor (fn, timeout = 15000) {
const end = Date.now() + timeout
while (Date.now() < end) { if (await fn()) return true; await new Promise((r) => setTimeout(r, 100)) }
return false
}
test('getRepo?since serves a real partial CAR and declares it in headers', async (t) => {
const testnet = await createTestnet(3)
// --- a self-custody peer with THREE commits, so there is a real range to diff over ---
const peerDir = mkdtempSync(join(tmpdir(), 'since-peer-'))
const peerStore = openCorestore(peerDir)
const repo = await new RepoStore(peerStore, { name: `atproto-repo:${DID}` }).ready()
let micros = 1_700_000_000_000_000n
const mgr = await new RepoManager(repo, { did: DID, privateKey: PRIV, clock: () => (micros += 1_000_000n) }).init()
const seq = await new FirehoseSequencer(peerStore.get({ name: 'firehose' })).ready()
let t0 = Date.parse('2026-08-07T00:00:00.000Z')
const write = async (rkey, text) => {
const w = await mgr.applyWrite({ action: 'create', collection: 'app.bsky.feed.post', rkey, record: { $type: 'app.bsky.feed.post', text, createdAt: new Date(t0 += 1000).toISOString() } })
await seq.sequenceCommit(w, { time: new Date(t0).toISOString() })
return w
}
const w1 = await write('a1', 'first')
await write('a2', 'second')
const w3 = await write('a3', 'third')
const peerSwarm = new Hyperswarm({ bootstrap: testnet.bootstrap })
peerSwarm.on('connection', (conn) => {
peerStore.replicate(conn)
// eslint-disable-next-line no-new
new ProtomuxRPC(conn, { protocol: 'hyper-proto-peer/rpc/v1', valueEncoding: c.json })
pushAnnounce(conn, { did: DID, handle: HANDLE, repo, seq })
})
await peerSwarm.join(discoveryTopic(DID), { server: true, client: false }).flushed()
// --- the membrane, constructed exactly as pds-serve.js does ---
const routerStore = openCorestore(mkdtempSync(join(tmpdir(), 'since-router-')))
const routerSwarm = new Hyperswarm({ bootstrap: testnet.bootstrap })
const router = new DidPeerRouter(routerStore, {
swarm: routerSwarm,
join: (did) => { try { routerSwarm.join(discoveryTopic(did), { server: true, client: true }); routerSwarm.flush().catch(() => {}) } catch {} },
fronted: [DID],
resolveVerifyKey: async () => keyFromPrivate(PRIV).publicKey,
rpc: true
})
const server = createPdsServer({ peers: router, serverDid: SERVER_DID, availableUserDomains: ['.7rnx.net'] })
await new Promise((r) => server.listen(0, '127.0.0.1', r))
const xrpc = (m) => `http://127.0.0.1:${server.address().port}/xrpc/${m}`
t.teardown(async () => {
server.close(); await router.close()
await peerSwarm.destroy(); await routerSwarm.destroy()
await seq.close(); await repo.close(); await peerStore.close(); await routerStore.close()
await testnet.destroy()
rmSync(peerDir, { recursive: true, force: true })
})
const get = (qs) => fetch(xrpc('com.atproto.sync.getRepo') + qs)
// The diff is assembled FROM the replicated frames, so a front holding only the head frame would
// legitimately refuse and every assertion below would prove nothing. Wait for all three.
t.ok(await waitFor(async () => {
const r = await get('?did=' + DID)
if (!r.ok) return false
try { return listRecords(await readCar(new Uint8Array(await r.arrayBuffer()))).length === 3 } catch { return false }
}), 'the membrane serves the complete 3-record repo (peer fully replicated)')
// --- 1. NO since: unchanged, and no since-headers at all ---
// Every existing caller is on this path; a header appearing here would be a new, unrequested contract.
const plain = await get('?did=' + DID)
const plainCar = await readCar(new Uint8Array(await plain.arrayBuffer()))
t.is(listRecords(plainCar).length, 3, 'no-since getRepo still returns the complete repo')
t.is(plain.headers.get('atproto-repo-partial'), null, 'and sets NO partial header — nothing was asked')
t.is(plain.headers.get('atproto-since-honoured'), null, 'nor a honoured header')
// --- 2. since=<rev>: a genuine partial CAR, self-declared ---
const part = await get('?did=' + DID + '&since=' + w1.rev)
t.is(part.status, 200, 'since is answered 200, not an error')
t.is(part.headers.get('atproto-repo-partial'), 'true', 'declared PARTIAL in the response headers')
t.is(part.headers.get('atproto-since-honoured'), 'true', 'and declared honoured')
t.is(part.headers.get('atproto-since-from'), w1.rev, 'reporting the rev it diffed from')
t.is(part.headers.get('atproto-since-to'), w3.rev, 'and the rev it diffed to')
const partCar = await readCar(new Uint8Array(await part.arrayBuffer()))
t.ok(loadCommit(partCar).cid.equals(loadCommit(plainCar).cid), 'rooted at the same head as the full CAR')
// The property that makes the headers load-bearing. If this ever stops throwing we have silently
// regressed to serving a full CAR while claiming a diff.
t.exception(() => listRecords(partCar), 'the partial CAR is NOT self-contained — walking it alone throws')
// --- 3. an unanswerable since: FULL CAR + an explicit not-honoured, never a silent full CAR ---
// '2222222222222' is below every real rev: the TID alphabet starts at '2', not 'a'.
const fb = await get('?did=' + DID + '&since=2222222222222')
t.is(fb.status, 200, 'still 200 — a since we cannot honour is not a client error')
t.is(fb.headers.get('atproto-repo-partial'), 'false', 'declared NOT partial')
t.is(fb.headers.get('atproto-since-honoured'), 'false', 'and explicitly NOT honoured — the anti-silence property')
t.ok((fb.headers.get('atproto-since-reason') || '').length > 0, 'with a reason: ' + fb.headers.get('atproto-since-reason'))
t.is(listRecords(await readCar(new Uint8Array(await fb.arrayBuffer()))).length, 3, 'and the fallback CAR is genuinely complete')
// --- 4. since == head: an empty diff is a real answer ---
const empty = await get('?did=' + DID + '&since=' + w3.rev)
t.is(empty.headers.get('atproto-repo-partial'), 'true', 'since == head is still a partial answer')
t.is((await readCar(new Uint8Array(await empty.arrayBuffer()))).blocks.size, 1, 'carrying the commit block alone')
})