fix(client): résolution de compte DÉTERMINISTE + anti-fork restauré
Cause racine du bug de reconnexion (probe contrôlé répété) : le shim d'un compte accumule des `docPublic`/`docProtected` EN DOUBLE (forks passés), et resolveAccount/indexDocOf les choisissaient de façon NON-DÉTERMINISTE → l'écrivain et le lecteur (page fraîche) ancraient sur des docs d'index DIFFÉRENTS → lecture 0. Fix : - `canonicalDoc()`/`recordFromRows()` : parmi plusieurs valeurs d'un scope, choisir le NURI lexicographiquement le plus petit (les NURIs sont content-addressed → ordre total stable). Écrivain et lecteur résolvent TOUJOURS le même doc, même sur un shim corrompu par des doublons. - `resolveAccountReliably` RESTAURÉ (retry borné avant provision sur read shim 0 à froid) : j'avais retiré l'anti-fork à tort (`38b1521`) — le gap EST exhibé (fork non-déterministe au cold-read), et la barrière `user_connect` n'est PAS accessible côté JS → un retry borné est la compensation légitime (pas la barrière-store cassée de `45dbd9a`). Budget injecté `provisionRetry` ; défaut attempts:1 (fakes synchrones), app/e2e attempts:8. anti-fork.test.ts réécrit : docPublic dupliqué → même canonique ; retry sur lag → réutilise ; neuf → provision 1×. gate : tsc 0 ; bun test 122 ; test:e2e 42/42 (CONTRACT 2 non-fork vert). Portée : corrige la couche docPublic de la reconnexion. Une couche PROTECTED distincte (watchShape protected ne converge pas à froid) reste — diagnostic en cours. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -25,9 +25,15 @@ export interface StoreRegistryDeps {
|
||||
/** Normalize an identity id for shim keying. Default: trim (identity-ish). */
|
||||
normalizeId?: (id: string) => string;
|
||||
/**
|
||||
* @deprecated Removed. The anti-fork retry/barrier mechanism has been dropped
|
||||
* (the private store is synchronised at login; the barrier was both unnecessary
|
||||
* and broken for STORE NURIs). This field is accepted but silently ignored.
|
||||
* ANTI-FORK bounded retry budget. On a FRESH page over a persistent wallet the
|
||||
* shim (in the private store's graph) may not be synced when the first read
|
||||
* fires → 0 rows; treating that transient 0 as "account absent" makes the
|
||||
* registry PROVISION a new set of scope docs — an account FORK. Since the
|
||||
* deterministic Rust barrier (`user_connect`) is not reachable from JS, a
|
||||
* BOUNDED retry (capped backoff) is the compensation: wait out the sync-lag
|
||||
* window before concluding "genuinely new". Enable it where the REAL broker is
|
||||
* used (app + e2e). Left UNSET (the default) → `attempts: 1` = NO retry (a
|
||||
* single read), which keeps the synchronous unit fakes fast and unchanged.
|
||||
*/
|
||||
provisionRetry?: { attempts?: number; baseMs?: number; maxStepMs?: number };
|
||||
}
|
||||
@@ -56,8 +62,11 @@ export interface EventuallyConfig {
|
||||
|
||||
let cfg: EventuallyConfig | null = null;
|
||||
let currentUser: PrincipalId | null = null;
|
||||
/** Required fields of StoreRegistryDeps after defaults are applied (excludes deprecated provisionRetry). */
|
||||
type ResolvedRegistryDeps = Required<Pick<StoreRegistryDeps, "getSession" | "normalizeId">>;
|
||||
/** Required fields of StoreRegistryDeps after defaults are applied. `provisionRetry`
|
||||
* defaults to `{ attempts: 1 }` (no retry) when the consumer leaves it unset. */
|
||||
type ResolvedRegistryDeps = Required<
|
||||
Pick<StoreRegistryDeps, "getSession" | "normalizeId" | "provisionRetry">
|
||||
>;
|
||||
let registryDeps: ResolvedRegistryDeps | null = null;
|
||||
/** The emulated ReadCap/WriteCap registry. Empty until the app declares caps;
|
||||
* while it has no read policy the read filter passes through (no regression). */
|
||||
@@ -92,6 +101,9 @@ export function configureStoreRegistry(deps: StoreRegistryDeps): void {
|
||||
registryDeps = {
|
||||
getSession: deps.getSession,
|
||||
normalizeId: deps.normalizeId ?? ((id: string) => id.trim()),
|
||||
// Default: no retry (single read). Only the real-broker consumers (app + e2e)
|
||||
// opt into the bounded anti-fork retry; unit fakes stay synchronous.
|
||||
provisionRetry: deps.provisionRetry ?? { attempts: 1 },
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user