Commit Graph

5 Commits

Author SHA1 Message Date
Sylvain Duchesne 3046ead08f fix(client): durcir le cold-start du shim — ensureRepoOpen(anchor) avant lecture/écriture
Défensif : loadShim/resolveAccount/ensureAccount ouvrent l'anchor (private-store-root)
avant de lire/écrire le compte shim, comme le fait déjà readScopeIndex. Robustesse
same-session si l'anchor est là-mais-pas-encore-souscrit.

NB (vérifié nextgraph-rs) : sur le login broker normal, le bootstrap charge le
private-store dans self.repos AVANT de rendre la session à JS → un wallet FRAIS
retourne 0 rows (pas RepoNotFound) et provisionne. Il n'existe AUCUN primitif JS
pour ouvrir un repo *inconnu* : ce heal n'est pas un remède à un store non
bootstrappé (limite NextGraph), juste une robustesse d'ouverture same-session.

Tests : cold-start-anchor.test.ts (rouge-avant/vert-après unit) ; harness e2e
repro-fresh-wallet (mint un wallet neuf par run — comble le trou "aucun test de
démarrage à froid sur wallet vierge"). Fakes anti-fork/watch-shape honorent
désormais la barrière first-State dont dépend le heal. e2e réel 42/42.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 15:06:30 +02:00
Sylvain Duchesne bd48b16e31 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>
2026-07-10 10:24:58 +02:00
Sylvain Duchesne 38b152136b refactor(client): retirer anti-fork — gap non exhibé, resolveAccount simple
Preuve e2e (wallet frais, broker RÉEL rapide : 1er State 1-2 ms) : le private
store est synchronisé au login, la lecture du shim réussit à froid — le « fork sur
lag » que anti-fork compensait n'est PAS exhibé. Par le principe du polyfill
(compenser un gap RÉEL, jamais du poids mort), et par la règle no-polling :
- la version retry = polling (bannie) ;
- la version barrière `ensureRepoOpen(privateStore)` = CASSÉE (un store n'émet pas
  de `State`, la barrière timeout systématiquement → CONTRAT 2 e2e échouait) ;
- le gap = non exhibé.
→ `ensureAccount` fait un `resolveAccount(id)` SIMPLE (une lecture, provision si 0).
`resolveAccountReliably`, `_forceOpenedSyncState` retirés ; `provisionRetry` gardé
optionnel @deprecated (ignoré) pour ne pas casser les 8 tests qui le passent.
`ensureRepoOpen`/`getSyncState` inchangés (chemin de lecture open-repo).

gate : tsc 0 ; bun test 116 ; test:e2e 39 passed, CONTRAT 2 VERT (« same account,
no second provisioning »). Le ~10s du re-resolve public est du scaling anchorless,
pas de la lenteur broker (broker mesuré à 1-2 ms).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 13:47:07 +02:00
Sylvain Duchesne 45dbd9a33a refactor(client): dé-poller resolveAccountReliably — barrière au lieu de retry
Le polling est un anti-pattern NextGraph (par abonnement). La résolution de compte
retentait ×8 la lecture du shim tant qu'elle rendait 0 (lag de sync) — c'est du
polling. Remplacé par la BARRIÈRE d'abonnement, déjà le mécanisme de open-repo :

- `resolveAccountReliably` : `await ensureRepoOpen(did🆖${privateStoreId})`
  (subscribe + attendre le 1er State — le shim vit dans le graphe du private store),
  PUIS lecture UNIQUE. Après la barrière, 0 ligne = compte réellement inexistant →
  provision 1×, lignes présentes = réutilisé (garantie NO-FORK préservée). Plus de
  boucle de re-lecture.
- timed-out (barrière expirée) : throw explicite, NE provisionne PAS (un provision
  sur sync incomplète re-forkerait). Le « trop long » est un signal, pas un feu vert.
- fake ng sans doc_subscribe : ensureRepoOpen no-op → lecture immédiate (unit intact).
- `_forceOpenedSyncState` : helper test-only (underscore, non ré-exporté).

anti-fork.test.ts réécrit (5 tests : no-fork, neuf→1 provision, idempotence, fake
no-op, timed-out→throw) ; plus aucun test de comptage de retry.

gate : tsc propre ; bun test 117. e2e À RE-VALIDER quand le broker répond (dégradé
ce jour : crash Chromium post-connexion) — la barrière ensureRepoOpen est déjà
validée e2e (CONTRAT 3 + reconnexion) en broker sain. provisionRetry devient un
champ mort de StoreRegistryDeps (nettoyage ultérieur).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-09 13:28:56 +02:00
Sylvain Duchesne 95010ddd66 test(client): couverture comportementale des 3 changements SDK (fake déterministe)
Les fix récents (anti-fork, open-repo, access-log) n'avaient AUCUN test de
comportement dans le domaine de la lib. Ajout de tests DÉTERMINISTES à base de `ng`
factice modélisant la condition d'échec (pas de broker réel — le harness e2e réel
réhydrate trop vite et ne reproduit pas ces cas).

- anti-fork.test.ts : fake avec lag de sync (0 lignes les K premières lectures du
  record de compte, puis les vraies). Asserte le cœur du fix : compte retrouvé au
  retry → réutilisé, 0 doc_create (pas de fork) ; compte réellement neuf → 1 seul
  jeu de docs provisionné, budget de retry prouvé consommé ; idempotence de session ;
  cas limite « trouvé à la dernière tentative ».
- open-repo.test.ts : fake où une requête ancrée rend vide tant que doc_subscribe
  n'a pas ouvert le repo. Asserte subscribe-avant-read + idempotence (Set des repos
  ouverts) + no-op si le ng n'a pas doc_subscribe.
- access-log.test.ts : off par défaut (silencieux), on via config ET via env
  NG_EVENTUALLY_ACCESS_LOG, préfixe = identité active (suit setCurrentUser),
  row-count sur READ. Restaure console.log/env fidèlement.

bun test : 91 → 117 pass, 0 fail. tsc --noEmit propre. src/ non touché.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-08 14:37:43 +02:00