Files
ng-eventually/docs/migration-guide.md
T
Sylvain Duchesne f378c71739 docs: sept affirmations sur NextGraph requalifiées à la source
Lot F de la revue adverse. Chaque affirmation relue dans `nextgraph-rs` par symbole avant
d'être réécrite ; aucune ne s'est révélée exacte.

- `AddLink` / `RemoveLink` / `RepoLinkV0` étaient présentés comme **implémentés**. Leurs
  arms de vérificateur sont des `Ok(())`, là où celui d'`AddRepo` fait un vrai travail, et
  rien ne les construit. Le tableau dit désormais « déclaré, stubbé », et la conclusion qui
  en déduisait « le registre existe, seule la livraison manque » est corrigée : **les deux
  bouts** sont déclarés-et-stubbés.
- La table `inboxes` était dite « reconstruite vide à chaque session » — elle est
  repeuplée au chargement, et la clé privée d'inbox est persistée par repo. L'argument de
  sécurité qui s'appuyait dessus repose maintenant sur le bon motif : la table est **par
  vérificateur**, pas éphémère.
- La citation de « `doc_create` laisse `inbox: None` » pointait un constructeur réservé aux
  tests ; re-ciblée sur le chemin de production.
- `ExtObjectGet` était dit « le seul » primitif accessible à un non-membre et exigeant les
  clés : il y en a trois, et sa structure n'a aucun champ de clé.
- L'en-tête de `public-store.ts` était marqué **VERIFIED** alors qu'il repose sur un
  commentaire de doc, et la condition qu'il citait (« si les brokers pairs l'autorisent »)
  disparaissait de la conclusion. Requalifié en **pari**, condition rétablie.
- Deux sur-restrictions corrigées (ce qu'écrit le traitement d'un `ContactDetails`, et le
  prétendu « miroir 1:1 » de `NuriV0`, qui a dix champs).

**Sur la grammaire du ReadCap, une correction de MA correction.** J'avais écrit que la
forme `{target}:r:{cap}` était notre invention. Faux : le segment `r:` et son encodage sont
ceux d'amont, et l'auteur de NextGraph l'a énoncé. Ce qui est établi est plus étroit —
aucun parseur amont n'accepte aujourd'hui un NURI de repo qui le porte, et le segment est
produit comme valeur de champ. J'avais conclu d'une implémentation absente à ce que la
cible ferait, ce que la doctrine du projet interdit nommément. Seule « P1b remplace la
valeur, pas la forme » est corrigée, requalifiée en **pari**.

**Et la surface ne publie plus de type que personne n'utilise.** `export * from
"./model/types"` publiait huit types en bloc ; c'est une liste nommée de six. `ReadCap`
sort — aucune signature publiée ne le prend ni ne le rend, seules deux fonctions privées
de `inbox.ts` s'en servent — et `InboxScope` aussi. Un type n'est publié que si une
signature publiée l'utilise.

197 tests, 0 échec ; les trois typechecks propres ; `lint` sans erreur.
2026-08-10 12:09:20 +02:00

8.0 KiB

Migration guide — when real NextGraph matures

The whole point of this library: the consumer application already writes SDK-shaped code, so when NextGraph ships cross-wallet reads, capabilities and inboxes, only this lib changes. The consumer application's code does not change. This is the checklist.

Guiding invariant

Every emulated piece has a 1:1 image in the real infra. Migration = swap the emulation for the real primitive, remove the scaffold. If a piece of the emulation has no clear target image, that is a drift signal (see simulation.md).

Checklist

1. Emulated ReadCaps → real capabilities

The shape is already the target's (P1a): a ReadCap is the document's key, a each identity holds a set of caps, and there is no read-ACL anywhere. So this step swaps the emulated key for the real one, not the model:

  • emulated-verifier/caps.ts's per-identity record becomes the verifier's own local user storage — it was always the cache, not the register. The two durable registers we emulate (readCap on the store's Store branch, link on its User branch) become the real AddRepo / AddLink commits. Remove the emulation; the wallet and the branches already hold them.
  • the stand-in cap value — the constant OK (STAND_IN_CAP, emulated-verifier/caps.ts) — becomes the real r:{base64url(serde_bare(ObjectRef))}. It is one function (mintCap, in that same module since the source layout was reorganised — this said nuri.ts until 2026-08-10), because every path now READS a stored cap instead of recomputing one. hasReadCap / targetOf stay meaningful: the r: discriminant is upstream grammar, not ours.
  • inbox.share(doc, toUser) becomes the native sealed delivery (whatever the SDK ends up naming it — see the note below and ContactDetails.read_cap), and inbox.read's inline absorption becomes the recipient's own verifier applying queued messages. The consumer's call does not change.
  • caps.markInPublicStore and the whole of emulated-verifier/public-store.ts disappear: which store a document sits in stops being a fact we record, and serving a public store's repos becomes the broker's job (expose_outer, the ReadCap downloaded from the outer overlay — PublicRepoLinkV0, engine/net/src/types.rs:5098). Nothing an application calls changes: it circulates bare references now, and will still.
  • assertMayWrite goes with it — refusing a write on a cap the public store served is a stand-in for the write cap this emulation does not have.
  • The read filter (emulated-verifier/read-filter.ts) and the possession gate in read-model.readUnion are then dead code — the broker only delivers documents whose cap the wallet holds. Remove them.
  • The write guard (surface/ng-proxy.ts sparql_update override) is a separate axis and is decorative today (every internal writer bypasses the proxy); it belongs to the P1b batch, not here.

The access unit is already the document (@graph), matching the native per-repo cap model, so this is a key-material step, not a reshape.

2. Place documents in real native stores

Today docCreate(..., undefined) writes every document into the shared wallet's private store, and the public|protected|private scope is a logical label in the shim (see the two-axes section in simulation.md).

  • doc_create cannot target a non-private native store today — verified: StoreRepo is not constructible from the WEB build of the SDK, so there is no way to pass a public/protected store as the create destination (docCreate's trailing store arg is left undefined → private store). The private store works only because it opens without RepoNotFound.
  • When the SDK lets you construct/target a native store, the migration adds a getNativeStore(scope)-style resolver returning the real store to pass as the docCreate destination, so the logical scope label becomes a real store placement. (No such helper exists yet — it is blocked on the SDK gap above.)
  • At that point shared-wallet/account-registry.ts maps (account, scope) to the user's real store NURI instead of a document in the shared wallet; the per-scope index document (the store-container emulation) is replaced by the store itself. The surface facing the consumer application (createEntityDoc, listMyEntityDocs, resolvers) is designed to survive that swap unchanged.

3. Drop the resolver / shim

The sharedWalletShim (account → 3 scope-document NURIs, held in a subscribable doc-shim reached via a write-once pointer in the store-root — see nextgraph-current-state.md § The pointer → doc-shim indirection) has no target equivalent — the target has no central directory. Remove it entirely: shared-wallet/account-registry.ts, configureStoreRegistry, the pointer + doc-shim resolution, and the pointerGuard dep. Cross-wallet reads replace the fan-out; per-user wallets replace the shared one.

4. Real inbox → drop the in-lib read emulation

Replace the emulated inbox.ts deposit (docs.sparqlUpdate into a shared-wallet document) with the native sealed deposit, once one is exposed to JS. Its name and shape are NOT known: no inbox method exists in @ng-org/web, the verifier has no InboxPost arm, and inbox_post_link is OUR proposed name (fork-inbox-fallback.md), not an announced API. On the read side the recipient's own verifier unseals each queued sealed message and applies it inline when it processes its inbox — there is no separate curator to build; the in-lib read emulation simply goes away. (There is no global index to replace the cross-account fan-out: both were removed on 2026-07-30 — you cannot discover in NextGraph, you follow links.)

5. Retire the identity store → real per-user login

Remove shared-wallet/virtualUsers.ts (the IdentityStore that persists the identity id in localStorage) and the app-level "Connexion" screen. The technical broker gate becomes the real per-user login (see decisions/shared-wallet-login-flow.md). The flow shape ("broker redirect → app") does not change.

6. Drop the isolation scaffold — already gone

isolation.ts (the old application-visibility filter) was deleted from the library; nothing remains to remove at migration. Kept as a numbered step so the following numbers stay stable across references.

7. Remove the build alias — the client becomes the real SDK

The consumer application imports @ng-org/web / @ng-org/orm resolved to this lib via a build alias during the polyfill period. Removing the alias makes those imports resolve to the real SDK — the ng/useShape/inbox surface is SDK-identical, so no consumer code changes. The one non-SDK call — configure(...) / the POLYFILL-ERA block of @ng-eventually/sdk — is deleted. The lib itself disappears.

The one break already taken: declareConnections

P1a broke the consumer once, deliberately and early, so that migration would not. The old surface was an ACL held in memory, which forced the consumer to re-declare every grant on every session (declareConnections). That call disappears: with delivered caps the grant moves to the moment a connection is accepted — one inbox.share(doc, toUser) per document shared — and it persists, because the delivery lives in the recipient's inbox rather than in a map that empties at reload. There is no analogue of protectedDocsOf + the re-derivation loop.

This is a consumer re-architecture, not an API swap, and it is the price of being coded against a model that will exist. Nothing else about the migration below touches consumer code.

What does not change

The consumer application's code. Shapes, screens, the acts of sharing access, entity→scope mapping, the relationship graph — all injected, all untouched. Migration is entirely inside this library plus removing the alias + the bootstrap call. That asymmetry — a mature SDK face outward, all compensation inward — is the library's reason to exist.