/** * COLD-START repro — a genuinely FRESH wallet (never ran the app) driving the * shim's first account resolution/provision. * * Unlike `run.ts`, which reuses ONE dedicated wallet (always "hot" — its * private-store repo is already in the verifier's `self.repos`), this mints a * BRAND-NEW wallet + fresh profile per run and opens the SDK page over it as the * very first session. It then, in order: * 1) probes the RAW anchored shim SELECT on `did:ng:${private_store_id}` — the * cold-start bug surfaces here as `RepoNotFound` (the private-store repo not * yet open) rather than a silent 0 rows; * 2) runs `ensureAccount` (resetRegistryCache first) — the app's first-login * bootstrap — and asserts it provisions 3 scope docs WITHOUT throwing; * 3) re-resolves the SAME id from a fresh anchored read and asserts it returns * the SAME docs (real persistence in the shim). * * Expected BEFORE the fix: step 1 throws RepoNotFound; step 2/3 fail to persist. * Expected AFTER the fix: step 1 may still throw (raw, no open), but step 2/3 * succeed because ensureAccount opens the anchor repo before read/write. * * Run: `bun run e2e/repro-fresh-wallet.ts`. */ import * as fs from "node:fs"; import type { Frame, Page, BrowserContext } from "playwright"; import { buildBundle, serveHarness, createFreshWalletContext, setupBrokerPage } from "./broker"; type Check = { name: string; ok: boolean; detail?: string }; const results: Check[] = []; function check(name: string, ok: boolean, detail?: string): void { results.push({ name, ok, detail }); console.log(` [${ok ? "PASS" : "FAIL"}] ${name}${detail ? " — " + detail : ""}`); } function sdk(frame: Frame, method: string, ...args: unknown[]): Promise { return frame.evaluate( ([m, a]) => (window as any).__sdk[m as string](...(a as unknown[])), [method, args] as const, ) as Promise; } async function main(): Promise { console.log("[repro] building SDK page bundle..."); buildBundle(); const { url, close: closeServer } = await serveHarness(); console.log(`[repro] harness served at ${url}`); console.log("[repro] creating a BRAND-NEW wallet (fresh profile)..."); let ctx: BrowserContext | null = null; let dir: string | null = null; let page: Page | null = null; try { const fresh = await createFreshWalletContext(); ctx = fresh.ctx; dir = fresh.dir; console.log(`[repro] fresh wallet: ${fresh.name}`); page = await ctx.newPage(); page.on("pageerror", (e) => console.error("[iframe error]", e.message)); page.on("console", (m) => { if (m.type() === "error") console.error("[iframe console]", m.text()); }); console.log("[repro] opening SDK page over the FRESH wallet (first-ever app session)..."); const frame = await setupBrokerPage(page, url); await frame.waitForFunction(() => (window as any).__sdk !== undefined, { timeout: 30000 }); await frame.waitForFunction(() => (window as any).__sdk.status() === "connected", { timeout: 60000, }); console.log("[repro] connected. Driving the cold-start shim resolution..."); // 1) RAW anchored shim probe — the diagnostic. Reports RepoNotFound if the // private-store repo is not yet open in this fresh session. const probe = await sdk(frame, "shimAnchorProbe"); console.log( ` [DIAG] raw anchored shim read: threw=${probe.threw} error=${probe.error} rows=${probe.rows}`, ); // 2) ensureAccount — the real bootstrap. This MUST provision cleanly on a fresh // wallet (all 3 docs truthy, no throw). This is the load-bearing assertion. const ensured = await sdk(frame, "coldEnsureAccount", "@cold-user-1"); check( "fresh wallet: ensureAccount provisions the account without throwing", !ensured.threw && !!ensured.docPublic && !!ensured.docProtected && !!ensured.docPrivate, ensured.threw ? `THREW: ${ensured.error}` : `pub=${String(ensured.docPublic).slice(0, 22)}… prot=${String(ensured.docProtected).slice(0, 22)}…`, ); // 3) Re-resolve from a FRESH anchored read — proves the shim actually persisted. const verified = await sdk(frame, "verifyShimPersisted", "@cold-user-1"); check( "fresh wallet: the provisioned account persists (re-resolves the SAME docs, no RepoNotFound)", !verified.threw && verified.docPublic === ensured.docPublic && verified.docProtected === ensured.docProtected && verified.docPrivate === ensured.docPrivate, verified.threw ? `THREW: ${verified.error}` : `same=${verified.docPublic === ensured.docPublic && verified.docProtected === ensured.docProtected}`, ); } finally { try { if (page) await page.close(); } catch { /* ignore */ } try { if (ctx) await ctx.close(); } catch { /* ignore */ } try { if (dir) fs.rmSync(dir, { recursive: true, force: true }); } catch { /* ignore */ } closeServer(); } const passed = results.filter((r) => r.ok).length; const failed = results.length - passed; console.log(`\n══ cold-start repro: ${passed} passed, ${failed} failed ══`); if (failed > 0) { for (const r of results.filter((x) => !x.ok)) console.log(` - ${r.name}: ${r.detail ?? ""}`); process.exit(1); } } main().catch((e) => { console.error("[repro] fatal:", e); process.exit(1); });