/** * anti-fork.test.ts — behavioral tests for the reconnection fix in the * polyfill-era shim (src/store-registry.ts), redesigned around the * pointer → doc-shim indirection. Two groups, one file: * * (1) DETERMINISTIC RESOLUTION — a doc-shim whose account subject carries * DUPLICATE scope-doc values (fork residue: several `shim:docPublic`) must * resolve to the SAME canonical doc every time (lexicographically-smallest * NURI), so the session that WROTE an entity and a fresh page that RESOLVES * the doc never disagree. Robustness against PAST fork residue. * * (2) BARRIER-AUTHORITATIVE RECONNECT (the core fix) — a fresh page over a * persistent wallet resolves the SAME account through the doc-shim's * first-`State` BARRIER, with NO account-level retry. The account records * live in a subscribable doc-shim (`did:ng:o:...`) reached via a write-once * POINTER triple in the store-root; opening the doc-shim makes a cold read * authoritative, so a genuinely-present account is found on the first read * and never re-provisioned (no fork). This replaced the deleted * `resolveAccountReliably` / `provisionRetry` account retry. */ import { describe, it, expect, mock, beforeEach, afterAll } from "bun:test"; import { ensureAccount, resolveAccount, loadShim, resetRegistryCache, } from "../src/store-registry"; import type { RegistrySession } from "../src/store-registry"; import { configure, configureStoreRegistry, resetStoreRegistry, resetConfig, } from "../src/polyfill"; import { resetOpenedRepos } from "../src/open-repo"; afterAll(() => { resetConfig(); resetStoreRegistry(); resetRegistryCache(); resetOpenedRepos(); }); const SESSION: RegistrySession = { sessionId: "sid-af", privateStoreId: "PRIV-AF" }; const ROOT = "did:ng:PRIV-AF"; // --------------------------------------------------------------------------- // Fake ng — in-memory quad store modelling the pointer → doc-shim indirection. // // - The POINTER (` `) lives in the store-root // graph (keyed by GRAPH ). // - AccountRecords live in the doc-shim (anchored default graph, keyed by the // anchor arg = the doc-shim NURI). // - doc_subscribe pushes an initial `State` so ensureRepoOpen resolves at once // (the barrier). // --------------------------------------------------------------------------- interface Quad { g: string; s: string; p: string; o: string } function unescapeLiteral(s: string): string { let out = ""; for (let i = 0; i < s.length; i++) { if (s[i] === "\\" && i + 1 < s.length) { const next = s[++i]; out += next === "n" ? "\n" : next === "r" ? "\r" : next === "t" ? "\t" : next; } else { out += s[i]; } } return out; } function makeSparqlUpdate(quads: Quad[]) { return mock(async (...a: unknown[]) => { const query = a[1] as string; const anchor = a[2] as string | undefined; const gm = query.match(/GRAPH <([^>]+)>\s*\{([\s\S]*)\}/); let g: string; let body: string; if (gm) { g = gm[1]!; body = gm[2]!; } else { if (!anchor) return undefined; g = anchor; body = query.replace(/^\s*INSERT DATA\s*\{/, "").replace(/\}\s*$/, ""); } const sm = body.match(/<([^>]+)>/); if (!sm) return undefined; const s = sm[1]!; const pairRe = /(?:a|<([^>]+)>)\s+(?:"((?:[^"\\]|\\.)*)"|<([^>]+)>)/g; let m: RegExpExecArray | null; const after = body.slice(body.indexOf(sm[0]) + sm[0].length); while ((m = pairRe.exec(after)) !== null) { const p = m[1] ?? "urn:ng-eventually:shim:Account"; const o = m[2] !== undefined ? unescapeLiteral(m[2]) : (m[3] ?? ""); quads.push({ g, s, p, o }); } return undefined; }); } /** Build the account-SELECT bindings from the quads in ONE graph (the doc-shim), * grouped per subject. A subject with DUPLICATE scope docs yields a cross-product * of bindings — a corrupted shim. */ function accountBindings(quads: Quad[], anchor: string | undefined, onlySubject: string | null) { const bySubject = new Map(); for (const q of quads) { if (q.g !== anchor) continue; if (onlySubject !== null && q.s !== onlySubject) continue; const rec = bySubject.get(q.s) ?? { id: "", pub: [], prot: [], priv: [] }; if (q.p === "urn:ng-eventually:shim:id") rec.id = q.o; if (q.p === "urn:ng-eventually:shim:docPublic") rec.pub.push(q.o); if (q.p === "urn:ng-eventually:shim:docProtected") rec.prot.push(q.o); if (q.p === "urn:ng-eventually:shim:docPrivate") rec.priv.push(q.o); bySubject.set(q.s, rec); } const bindings: Array> = []; for (const rec of bySubject.values()) { if (!rec.id) continue; const pubs = rec.pub.length ? rec.pub : [""]; const prots = rec.prot.length ? rec.prot : [""]; const privs = rec.priv.length ? rec.priv : [""]; for (const pub of pubs) for (const prot of prots) for (const priv of privs) bindings.push({ id: { value: rec.id }, docPublic: { value: pub }, docProtected: { value: prot }, docPrivate: { value: priv }, }); } return bindings; } /** Reactive fake ng modelling the pointer → doc-shim indirection. `doc_subscribe` * pushes a first `State` so the doc-shim barrier resolves synchronously. */ function makeFakeNg() { const quads: Quad[] = []; let docCounter = 0; // Count account SELECTs per anchor graph. The AUTHORITATIVE account read is anchored // to the doc-shim (a did:ng:o: NURI). Counting per-anchor lets a test assert "exactly // one doc-shim account read" (no account RETRY). const accountReadsByAnchor = new Map(); const doc_create = mock(async (..._a: unknown[]) => `did:ng:o:doc${++docCounter}`); const sparql_update = makeSparqlUpdate(quads); const sparql_query = mock(async (...a: unknown[]) => { const query = a[1] as string; const anchor = a[3] as string | undefined; // Pointer SELECT (store-root). if (query.includes("")) { const bindings = quads .filter((q) => q.g === anchor && q.p === "urn:ng-eventually:shim:shimDoc") .map((q) => ({ shimDoc: { value: q.o } })); return { results: { bindings } }; } // Account SELECT — anchored to the doc-shim (the authoritative read). if (query.includes("")) { accountReadsByAnchor.set(anchor ?? "", (accountReadsByAnchor.get(anchor ?? "") ?? 0) + 1); const subjM = query.match(/<([^>]+)>\s+a\s+/); return { results: { bindings: accountBindings(quads, anchor, subjM ? subjM[1]! : null) } }; } const bindings = quads .filter((q) => q.g === anchor && q.p === "urn:ng-eventually:shim:contains") .map((q) => ({ e: { value: q.o } })); return { results: { bindings } }; }); // Push a `State` on subscribe (the sync barrier) so ensureRepoOpen resolves at once. const doc_subscribe = mock(async (_repo: unknown, _sid: unknown, cb: Function) => { if (typeof cb === "function") cb({ V0: { State: {} } }); return () => {}; }); return { doc_create, sparql_update, sparql_query, doc_subscribe, _quads: quads, // Total account reads across all anchors. getAccountQueryCount: () => [...accountReadsByAnchor.values()].reduce((a, b) => a + b, 0), // Account reads anchored to a did:ng:o: doc-shim. getDocShimAccountReads: () => [...accountReadsByAnchor.entries()] .filter(([g]) => g.startsWith("did:ng:o:")) .reduce((a, [, n]) => a + n, 0), }; } function inject( fakeNg: unknown, pointerGuard?: { attempts?: number; baseMs?: number; maxStepMs?: number }, ) { configure({ ng: fakeNg as any, useShape: (() => {}) as any }); configureStoreRegistry({ getSession: async () => SESSION, normalizeId: (u) => u.trim().toLowerCase(), pointerGuard, }); resetRegistryCache(); resetOpenedRepos(); } // --------------------------------------------------------------------------- // (1) Deterministic resolution over fork residue (in the doc-shim) // --------------------------------------------------------------------------- describe("deterministic resolution over a doc-shim corrupted by fork residue", () => { beforeEach(() => { resetRegistryCache(); resetOpenedRepos(); }); it("(1a) a subject with MULTIPLE docPublic values always resolves the SAME canonical (lexicographically-smallest)", async () => { const fakeNg = makeFakeNg(); inject(fakeNg); const docShim = "did:ng:o:shimdoc"; // Seed the pointer (store-root → doc-shim) and the corrupted record IN the doc-shim. fakeNg._quads.push({ g: ROOT, s: "urn:ng-eventually:shim:root", p: "urn:ng-eventually:shim:shimDoc", o: docShim }); const subj = "urn:ng-eventually:shim:account:dupuser"; const dupPublics = [ "did:ng:o:pub-m", "did:ng:o:pub-a", "did:ng:o:pub-z", "did:ng:o:pub-c", "did:ng:o:pub-a", ]; fakeNg._quads.push({ g: docShim, s: subj, p: "urn:ng-eventually:shim:id", o: "dupuser" }); for (const p of dupPublics) fakeNg._quads.push({ g: docShim, s: subj, p: "urn:ng-eventually:shim:docPublic", o: p }); fakeNg._quads.push({ g: docShim, s: subj, p: "urn:ng-eventually:shim:docProtected", o: "did:ng:o:prot-1" }); fakeNg._quads.push({ g: docShim, s: subj, p: "urn:ng-eventually:shim:docPrivate", o: "did:ng:o:priv-1" }); const r1 = await resolveAccount("dupuser"); resetRegistryCache(); const r2 = await resolveAccount("dupuser"); const viaShim = (await loadShim()).get("dupuser"); // Canonical = lexicographically smallest → "did:ng:o:pub-a". expect(r1?.docPublic).toBe("did:ng:o:pub-a"); expect(r2?.docPublic).toBe(r1?.docPublic); expect(viaShim?.docPublic).toBe(r1?.docPublic); expect(viaShim?.docProtected).toBe("did:ng:o:prot-1"); expect(viaShim?.docPrivate).toBe("did:ng:o:priv-1"); }); }); // --------------------------------------------------------------------------- // (2) Barrier-authoritative reconnect — the core fix (no account retry) // --------------------------------------------------------------------------- describe("reconnect resolves the SAME account through the doc-shim barrier (no fork, no account retry)", () => { beforeEach(() => { resetRegistryCache(); resetOpenedRepos(); }); it("(2a) NO-FORK: account already in the doc-shim → reused, 0 new scope docs", async () => { const fakeNg = makeFakeNg(); inject(fakeNg); // First login: provisions the account (1 doc-shim + 3 scope docs). const first = await ensureAccount("LauraBarrier"); expect(fakeNg.doc_create).toHaveBeenCalledTimes(4); // Fresh page over the SAME persistent quads: reset all in-memory caches, keep the // quads. Reconnect must find the SAME account via the doc-shim barrier, NO new docs. resetRegistryCache(); resetOpenedRepos(); const second = await ensureAccount("LauraBarrier"); expect(second.docPublic).toBe(first.docPublic); expect(second.docProtected).toBe(first.docProtected); expect(second.docPrivate).toBe(first.docPrivate); // Still 4 — no doc-shim re-created (pointer reused), no scope docs re-created. expect(fakeNg.doc_create).toHaveBeenCalledTimes(4); }); it("(2b) barrier-authoritative: a fresh session finds the persisted account on the FIRST read — single doc-shim read, NO retry", async () => { // Seed session 1; capture the account NURIs + the persistent quads. const seed = makeFakeNg(); inject(seed); const orig = await ensureAccount("BarrierUser"); expect(seed.doc_create).toHaveBeenCalledTimes(4); // Fresh reactive session over the SAME persistent quads. The doc-shim pushes a // `State` on subscribe → resolveShimDoc opens the barrier → the account read is // authoritative on the FIRST attempt. Give a MULTI-attempt pointer guard to prove // it is NOT used for the account. const reconnect = makeFakeNg(); reconnect._quads.push(...seed._quads); inject(reconnect, { attempts: 8, baseMs: 1, maxStepMs: 2 }); const resolved = await ensureAccount("BarrierUser"); expect(resolved.docPublic).toBe(orig.docPublic); expect(resolved.docProtected).toBe(orig.docProtected); expect(resolved.docPrivate).toBe(orig.docPrivate); expect(reconnect.doc_create).toHaveBeenCalledTimes(0); // no new docs → no fork // Barrier-authoritative: found on the FIRST doc-shim read. expect(reconnect.getDocShimAccountReads()).toBe(1); }); it("(2c) GENUINELY NEW: a cold doc-shim reads 0 → provisioned exactly once, no retry", async () => { // Pointer + doc-shim exist but the doc-shim holds NO record for this account. const fakeNg = makeFakeNg(); inject(fakeNg, { attempts: 5, baseMs: 1, maxStepMs: 2 }); const docShim = "did:ng:o:preexisting-shim"; fakeNg._quads.push({ g: ROOT, s: "urn:ng-eventually:shim:root", p: "urn:ng-eventually:shim:shimDoc", o: docShim }); const rec = await ensureAccount("BrandNewUser"); // Provisioned exactly ONE set of 3 scope docs (the doc-shim already existed). expect(fakeNg.doc_create).toHaveBeenCalledTimes(3); expect(rec.docPublic).toMatch(/^did:ng:o:doc/); expect(rec.docProtected).not.toBe(rec.docPublic); // Barrier-authoritative: exactly ONE doc-shim account read (the 0 is definitive). expect(fakeNg.getDocShimAccountReads()).toBe(1); }); it("(2d) default budget (unset pointer guard): genuinely-new account → single doc-shim account read", async () => { const fakeNg = makeFakeNg(); inject(fakeNg); // pointerGuard unset → attempts:1 (synchronous default) const rec = await ensureAccount("SyncUser"); // 1 doc-shim + 3 scope docs. expect(fakeNg.doc_create).toHaveBeenCalledTimes(4); expect(fakeNg.getDocShimAccountReads()).toBe(1); expect(rec.docPublic).toMatch(/^did:ng:o:doc/); }); it("(2e) idempotence within a session: ensureAccount twice never creates 2 sets", async () => { const fakeNg = makeFakeNg(); inject(fakeNg); const a = await ensureAccount("SameUser"); const b = await ensureAccount("SameUser"); expect(b).toEqual(a); expect(fakeNg.doc_create).toHaveBeenCalledTimes(4); // 1 doc-shim + 3 scope docs }); });