| … | … | @@ -0,0 +1,46 @@ |
| 1 | // PHASE 0 #5 — MIGRATION READER. importRepo() imports an existing atproto repo (a CAR — the live DID's |
| 2 | // getRepo, or a backup) into a fresh atproto-base and ADOPTS its head, so the atproto chain CONTINUES |
| 3 | // unbroken: the next commit prev-links to the adopted head and its rev strictly increases past it (the |
| 4 | // relay's forward-only cursor rejects a regression — federation-wall). CIDs are preserved (canonical DAG-CBOR). |
| 5 | |
| 6 | import test from 'brittle' |
| 7 | import { mkdtempSync, rmSync } from 'node:fs' |
| 8 | import { tmpdir } from 'node:os' |
| 9 | import { join } from 'node:path' |
| 10 | import { deriveAtprotoKey, createAtprotoIdentity } from 'atproto-identity' |
| 11 | import { readCar } from 'atproto-repo/lib/car.js' |
| 12 | import { verifyRepo } from 'atproto-repo' |
| 13 | import { AtprotoBase } from '../lib/base.js' |
| 14 | import { projectCommit, nextRev } from '../lib/project.js' |
| 15 | |
| 16 | const MNEMONIC = 'test test test test test test test test test test test junk' |
| 17 | const ID = createAtprotoIdentity(MNEMONIC, { handle: 'x.7rnx.net', pdsUrl: 'https://pds.7rnx.net' }) |
| 18 | const KEY = deriveAtprotoKey(MNEMONIC) |
| 19 | const post = (text) => ({ $type: 'app.bsky.feed.post', text, createdAt: '2026-07-23T00:00:00.000Z', langs: ['en'] }) |
| 20 | const R = (k, v) => ({ key: 'app.bsky.feed.post/' + k, value: post(v) }) |
| 21 | function mkBase () { const dir = mkdtempSync(join(tmpdir(), 'mig-')); const base = new AtprotoBase({ path: dir, did: ID.did, privateKey: KEY.privateKey, ackInterval: 100 }); return { base, dir, close: async () => { await base.close(); rmSync(dir, { recursive: true, force: true }) } } } |
| 22 | const recCount = async (car) => (await verifyRepo(await readCar(car))).records |
| 23 | |
| 24 | test('migration: importRepo adopts an existing repo CAR and continues the chain unbroken', async (t) => { |
| 25 | // A "legacy" repo, as if fetched from the live DID's getRepo: 2 records at legacyRev. |
| 26 | const legacyRev = nextRev(null) |
| 27 | const legacy = await projectCommit({ records: [R('3a', 'one'), R('3b', 'two')], did: ID.did, rev: legacyRev, prev: null, privateKey: KEY.privateKey }) |
| 28 | |
| 29 | const A = mkBase(); await A.base.ready() |
| 30 | t.teardown(() => A.close()) |
| 31 | |
| 32 | const adopted = await A.base.importRepo(legacy.carBytes) |
| 33 | t.is(adopted.records, 2, 'imported 2 records') |
| 34 | t.is(adopted.commitCid.toString(), legacy.commitCid.toString(), 'adopted the legacy head commit (CID preserved)') |
| 35 | |
| 36 | const served = await A.base.getRepo() |
| 37 | t.is(await recCount(served.carBytes), 2, 'getRepo serves the 2 imported records') |
| 38 | t.is(served.commitCid.toString(), legacy.commitCid.toString(), 'getRepo commit == the adopted head') |
| 39 | |
| 40 | // A continuation post: the chain must extend from the adopted head, not restart. |
| 41 | await A.base.putRecord('app.bsky.feed.post', '3c', post('after migration')) |
| 42 | const w = await A.base.emitFirehose() |
| 43 | t.is(w.signedCommit.prev.toString(), legacy.commitCid.toString(), 'next commit prev-links to the adopted head (unbroken chain)') |
| 44 | t.ok(w.rev > legacyRev, 'rev strictly increases past the adopted rev (relay forward-only cursor accepts)') |
| 45 | t.is(await recCount((await A.base.getRepo()).carBytes), 3, '3 records after the continuation post') |
| 46 | }) |