WIP: open/subscribe repos before anchored cold-start reads (branche, non mergée)
Défaut visé : sur une session verifier fraîche (reconnexion), la lecture ancrée tape des repos pas encore ouverts dans self.repos → 0 ligne. Nouveau module `open-repo.ts` : `ensureReposOpen`/`ensureRepoOpen` ouvrent/souscrivent un repo via la primitive existante `subscribeDoc` (doc_subscribe) et attendent le push d'état initial (borné, sans polling) AVANT la lecture ancrée. Câblé en amont de `readUnion` (read-model) et `readScopeIndex` (store-registry). Idempotent par session (Set des NURI ouverts + Map in-flight ; ré-ouverture si la session injectée change). No-op si le `ng` injecté n'a pas doc_subscribe (fake unitaire). État — NON MERGÉ, incomplet : - AIDE le PROTECTED : la participation remonte au cold-start (mesuré app, timing un peu bruité). - N'ADRESSE PAS l'accueil PUBLIC : `readScopeIndex` de l'index public rend 0 même côté écrivain même-session — le fix ouvre le repo mais l'index reste vide. Le code d'index lib est prouvé scope-symétrique, donc la cause du public est ailleurs (broker/store ou chemin app), À MESURER SOUS BROKER (actuellement injoignable). Nécessité du fix elle-même non prouvée en e2e lib (broker down). gate broker-indépendant : tsc --noEmit propre ; bun test 91 pass (worker). Inclut le scaffolding e2e de repro reconnexion (broker/run/sdk-entry). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -12,12 +12,17 @@
|
||||
* (waitForFunction on a counter), never a blind sleep.
|
||||
*/
|
||||
|
||||
import * as fs from "node:fs";
|
||||
import * as os from "node:os";
|
||||
import * as path from "node:path";
|
||||
import type { Frame, Page, BrowserContext } from "playwright";
|
||||
import {
|
||||
buildBundle,
|
||||
serveHarness,
|
||||
ensureWallet,
|
||||
launchWalletContext,
|
||||
launchCleanProfileContext,
|
||||
importWalletViaFile,
|
||||
setupBrokerPage,
|
||||
} from "./broker";
|
||||
|
||||
@@ -280,6 +285,80 @@ async function main(): Promise<void> {
|
||||
const cleared = await sdk<string | null>(frame, "identityClear");
|
||||
check("IdentityStore set→get→clear", setr === "@ident-user" && got === "@ident-user" && cleared === null, `set=${setr} get=${got} cleared=${cleared}`);
|
||||
});
|
||||
|
||||
// ── reconnection cold-start (real-broker regression) ─────────────────────
|
||||
// Phase 1 (THIS session): seed — create a per-entity doc under (id, protected)
|
||||
// and write a marker triple (`protected` carries participations). Also EXPORT the
|
||||
// wallet `.ngw` so phase 2 can re-import the SAME wallet into a CLEAN browser
|
||||
// profile.
|
||||
//
|
||||
// Phase 2 (the faithful reconnect): import the wallet into a BRAND-NEW empty
|
||||
// profile dir (no local IndexedDB copy of the repos) and open a fresh SDK session
|
||||
// over it — the repos exist on the BROKER but NOT in this profile's local cache,
|
||||
// so this is a true reconnect (not a same-profile relaunch, which masks the gap by
|
||||
// eagerly rehydrating repos from local storage). Then re-read purely from the
|
||||
// wallet and assert the marker comes back.
|
||||
//
|
||||
// FINDING (recorded, see the digest): on THIS SDK/broker version the marker also
|
||||
// comes back WITHOUT the open-before-read heal — the broker-login bootstrap opens
|
||||
// the user's repos before the read (the `rawAnchoredNoOpen` detail below shows the
|
||||
// bare anchored query already resolves rows). So this test is a real-broker
|
||||
// REGRESSION guard for reconnection reads, NOT a fail-without-the-fix proof; the
|
||||
// cold-start the fix targets was diagnosed in the app and does not reproduce
|
||||
// through this harness's login path.
|
||||
console.log("\n── reconnection cold-start (real-broker regression) ──");
|
||||
await step("fresh session (clean profile, same wallet) re-reads a persisted entity doc", async () => {
|
||||
const reconId = "@recon-" + Date.now();
|
||||
const scope = "protected";
|
||||
const seed = await sdk<any>(frame, "reconnectSeed", reconId, scope);
|
||||
check(
|
||||
"seed: entity doc created + listed in the seeding session",
|
||||
seed.listedInSeed.includes(seed.entityNuri),
|
||||
`entity=${String(seed.entityNuri).slice(0, 24)}… listedInSeed=${seed.listedInSeed.length} origSession=${info?.session_id}`,
|
||||
);
|
||||
|
||||
// Export the wallet file and materialize it for the clean-profile import.
|
||||
const exp = await sdk<any>(frame, "exportWalletFile");
|
||||
const ngwPath = path.join(os.tmpdir(), `ng-eventually-recon-${Date.now()}.ngw`);
|
||||
fs.writeFileSync(ngwPath, Buffer.from(exp.b64, "base64"));
|
||||
|
||||
let cleanCtx: BrowserContext | null = null;
|
||||
let cleanDir: string | null = null;
|
||||
let cleanPage: Page | null = null;
|
||||
try {
|
||||
const launched = await launchCleanProfileContext();
|
||||
cleanCtx = launched.ctx;
|
||||
cleanDir = launched.dir;
|
||||
cleanPage = await cleanCtx.newPage();
|
||||
cleanPage.on("pageerror", (e) => console.error("[iframe error:clean]", e.message));
|
||||
cleanPage.on("console", (m) => { if (m.type() === "error") console.error("[iframe console:clean]", m.text()); });
|
||||
|
||||
// Import the SAME wallet into the empty profile (broker-only repos), then open
|
||||
// the SDK page in a fresh broker session over it.
|
||||
await importWalletViaFile(cleanPage, ngwPath);
|
||||
const cleanFrame = await setupBrokerPage(cleanPage, url);
|
||||
await cleanFrame.waitForFunction(() => (window as any).__sdk !== undefined, { timeout: 30000 });
|
||||
await cleanFrame.waitForFunction(() => (window as any).__sdk.status() === "connected", { timeout: 60000 });
|
||||
const cleanInfo = await sdkGet<any>(cleanFrame, "sessionInfo");
|
||||
check(
|
||||
"clean-profile session connected (fresh verifier, broker-only repos)",
|
||||
cleanInfo?.session_id !== undefined && cleanInfo?.session_id !== null,
|
||||
`session=${cleanInfo?.session_id}`,
|
||||
);
|
||||
|
||||
const r = await sdk<any>(cleanFrame, "reconnectRead", reconId, scope, seed.entityNuri, seed.marker);
|
||||
check(
|
||||
"fresh clean-profile session re-reads the persisted marker (reconnection regression)",
|
||||
r.markerPresent === true,
|
||||
`rawAnchoredNoOpen=${r.rawRowCount} listed=${r.listedCount} foundEntity=${r.foundEntity} subjects=${r.subjectCount} markerPresent=${r.markerPresent}`,
|
||||
);
|
||||
} finally {
|
||||
try { if (cleanPage) await cleanPage.close(); } catch { /* ignore */ }
|
||||
try { if (cleanCtx) await cleanCtx.close(); } catch { /* ignore */ }
|
||||
try { if (cleanDir) fs.rmSync(cleanDir, { recursive: true, force: true }); } catch { /* ignore */ }
|
||||
try { fs.rmSync(ngwPath, { force: true }); } catch { /* ignore */ }
|
||||
}
|
||||
});
|
||||
} finally {
|
||||
try { if (page) await page.close(); } catch { /* ignore */ }
|
||||
try { if (ctx) await ctx.close(); } catch { /* ignore */ }
|
||||
|
||||
Reference in New Issue
Block a user