fix(client): dé-dupliquer les ensureAccount concurrents au cold-start (fork résolu)

Dernière couche du bug de reconnexion : au cold-start, `watchShape` public +
protected + l'effet owned-events appellent `ensureAccount(A)` quasi-simultanément
AVANT la sync du shim → chacun lit 0 → chacun provisionne un nouveau jeu de docs
(fork par-appelant) → la résolution déterministe canonique fait alors diverger
lecteur et écrivain sur le docProtected → `readScopeIndex` vide.

Fix : `ensureInFlight` (map de promesses) dé-duplique les provisions concurrentes
en UNE seule ; `discovery.readIndex` ouvre son repo au cold-start (`ensureRepoOpen`).
Avec la résolution canonique déjà committée, écrivain et lecteur convergent.
Mesuré (levier isSuccess) : la participation protected converge `isSuccess=true,
data=1` sur la page fraîche (plus « vide à 30s »).

gate : tsc 0 ; bun test 123 ; test:e2e 42/42.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Sylvain Duchesne
2026-07-10 11:29:59 +02:00
parent bd48b16e31
commit 9103996dbe
4 changed files with 127 additions and 40 deletions
+14
View File
@@ -39,6 +39,7 @@
import * as inbox from "./inbox";
import { subscribeDoc } from "./subscribe";
import { ensureRepoOpen } from "./open-repo";
import { ensureAccount, reservedAccount } from "./store-registry";
import { getCaps } from "./polyfill";
import type { Nuri, PrincipalId } from "./types";
@@ -154,6 +155,19 @@ export async function submitToIndex(ref: unknown, opts?: SubmitOptions): Promise
*/
export async function readIndex(): Promise<IndexEntry[]> {
const target = await indexInboxNuri();
// COLD-START heal (polyfill-era): on a FRESH session over a persistent wallet the
// discovery-index inbox repo is not yet in the verifier's `self.repos`, so the
// anchored `inbox.read` below would resolve an unopened repo and silently return 0
// deposits — the same self-inflicted cold-read gap `readScopeIndex`/`readUnion`
// heal. This is what made the PUBLIC read's discovery fold come back empty on a
// reconnect, so a fresh page's home stayed empty for tens of seconds while the doc
// slowly synced by other means. Open/subscribe the index repo ONCE and await its
// first `State` (the sync barrier) before the anchored read. Idempotent per session;
// no-op with the unit fake ng (no `doc_subscribe`). This is done HERE (a cold direct
// reader) rather than inside `inbox.read`, because `inbox.watch` already holds the
// repo open via its own subscription and must not spawn a second bootstrap open. See
// open-repo.ts.
await ensureRepoOpen(target);
const deposits = await inbox.read(target);
const seen = new Set<string>();
const entries: IndexEntry[] = [];