import assert from 'node:assert/strict' import * as A from '@automerge/automerge' import { chromium } from 'playwright' /** Independent browser/cache evidence; exports contain document bytes, never OAuth state. */ async function snapshots(page, urls) { return page.evaluate(async selected => { const repo = window.__reamsApp.reamsDocumentManager.repo const output = [] for (const url of selected ?? repo.list()) { if (await repo.isDeleted(url)) continue await repo.load(url) const bytes = await repo.export(url) let binary = '' for (let offset = 0; offset < bytes.length; offset += 8192) binary += String.fromCharCode(...bytes.subarray(offset, offset + 8192)) output.push({ url, bytes: btoa(binary) }) } return output }, urls ?? null) } export async function verifyIndependentBackgroundReadback({ page, other, origin, account, launchOptions, approvePdsPopup, backupPath }) { const started = performance.now() const phase = label => console.log(JSON.stringify({ syncReadbackPhase: label, elapsedMs: Math.round(performance.now() - started) })) const requests = { writes: 0, recordGets: 0, lists: 0 } page.context().on('request', request => { const url = request.url() if (url.includes('/com.atproto.space.applyWrites')) requests.writes++ if (url.includes('/com.atproto.space.getRecord')) requests.recordGets++ if (url.includes('/com.atproto.space.list')) requests.lists++ }) if (backupPath) { phase('opening-real-import') await page.goto(`${origin}/import`, { waitUntil: 'domcontentloaded', timeout: 30_000 }) phase('selecting-real-archive') await page.getByLabel('Classic backup JSON').setInputFiles(backupPath) await page.getByRole('button', { name: 'Start import' }).click() await page.getByText(/Import complete ยท \d+ documents, \d+ folders/).waitFor({ timeout: 180_000 }) phase('real-import-locally-complete') await page.getByRole('button', { name: 'Open editor' }).click() await page.locator('.cm-editor').waitFor({ timeout: 120_000 }) } phase('capturing-local-frontiers') await page.evaluate(async () => { await window.__reamsApp.reamsDocumentManager.repo.flushLocal() }) const expected = await snapshots(page) phase(`captured-${expected.length}-documents`) let pending for (let pass = 0; pass < 30; pass++) { phase(`publication-pass-${pass}`) // This phase proves a captured publication barrier, not ordinary packing age. const result = await page.evaluate(async () => { try { const repo = window.__reamsApp.reamsDocumentManager.repo; await repo.syncNow(); await repo.flush(); return { ok: true } } catch (error) { if (!['PUBLICATION_PENDING', 'DOCUMENT_DEFERRED'].includes(error.code)) throw error; return { ok: false, code: error.code, message: error.message } } }) console.log(JSON.stringify({ publicationResult: result, requests })) pending = await page.evaluate(() => window.__reamsApp.reamsDocumentManager.repo.runtime.getStatus().pendingDocuments) if (pending === 0) break console.log(JSON.stringify({ publicationPass: pass, pendingDocuments: pending })) } assert.equal(pending, 0, 'available archive must become remotely durable, not merely locally imported') await other.evaluate(async () => { await window.__reamsApp.reamsDocumentManager.repo.syncNow() }) phase('mounted-peer-caught-up') const fresh = await chromium.launch(launchOptions) try { const target = await fresh.newPage({ ignoreHTTPSErrors: true }) await target.goto(origin) phase('fresh-browser-signing-in') await target.getByRole('button', { name: 'Open ATProto account' }).click() await target.getByRole('combobox', { name: 'Handle' }).fill(account.did) await target.waitForFunction(() => [...document.querySelectorAll('button')].some(button => button.textContent?.trim() === 'Sign in' && !button.disabled), undefined, { timeout: 60_000 }) const popup = target.waitForEvent('popup', { timeout: 60_000 }) void popup.catch(() => {}) await target.getByRole('button', { name: 'Sign in', exact: true }).click() const reload = target.waitForEvent('load', { timeout: 120_000 }) void reload.catch(() => {}) await approvePdsPopup(await popup.catch(async cause => { throw new Error(`Fresh-reader popup failed: ${await target.locator('.atproto-account-card').innerText().catch(() => '(card absent)')}; ${cause.message}`) }), account, /Lichen Wiki/i) await reload await target.locator('.cm-editor').waitFor({ timeout: 120_000 }) await target.evaluate(async () => { await window.__reamsApp.reamsDocumentManager.repo.syncNow() }) phase('fresh-browser-caught-up') for (const [label, browser] of [['already-mounted', other], ['fresh-browser', target]]) { const recovered = new Map((await snapshots(browser, expected.map(entry => entry.url))).map(entry => [entry.url, entry.bytes])) let hashes = 0 for (const original of expected) { const source = A.load(new Uint8Array(Buffer.from(original.bytes, 'base64'))) const remote = A.load(new Uint8Array(Buffer.from(recovered.get(original.url), 'base64'))) try { assert.equal(A.getMissingDeps(remote, []).length, 0) const heads = A.getHeads(source) assert(heads.every(head => A.getBackend(remote).getChangeByHash(head)), `${label} lost captured history of ${original.url}`) assert.deepEqual(A.view(remote, heads), source) hashes += A.getChangesMetaSince(source, []).length } finally { A.free(source); A.free(remote) } } console.log(JSON.stringify({ readback: label, documents: expected.length, capturedHistoryChanges: hashes, actualArchive: !!backupPath, elapsedMs: Math.round(performance.now() - started) })) } } finally { await fresh.close() } }