Files
ng-eventually/docs/migration-guide.md
T
Sylvain Duchesne 9d3e2d2bfe Fix documentation defects found by an adversarial review
Fifteen findings, all verified before acting. The ones that mattered:

- Corrections added without updating what they corrected. §5's table still
  said a cap-less NURI is one "without :k:", two hundred lines after §4
  established the discriminant is `r:`. Same shape of defect in the P1a
  report, which kept the sentence "it is the owner's keyring, upstream the
  keyring is the wallet" — the exact sentence §4quater declares wrong, and the
  one that produced a global in-memory keyring.

- A wrong source citation: RootCapRefresh/BranchCapRefresh live in
  verifier/src/commits/mod.rs, not repo/src/commit.rs, and are no-op stubs.

- Documentation describing deleted code: isolation.ts, discovery.readIndex,
  the global index, and an acceptance test that was dropped with discovery.

- The P1a implementation report had aged into being wrong in four places
  (caps not persisted, inbox processing not started, plain string types, the
  :k: segment). It is dated, so it now carries a header saying what later lots
  overtook, rather than being rewritten.

- vision.md stated "a document's data is stored encrypted" in the present
  tense. That is the target; here the cap value is the constant OK and nothing
  is encrypted. Said plainly now.

- Prose left mangled by an earlier mechanical find-and-replace, in four places
  I had claimed were repaired.

Also: reach.ts and connect.ts had no home in the permanent docs — the boundary
and the connection sequence are now described in simulation.md, not only in a
brief.
2026-08-03 11:34:24 +02:00

121 lines
7.0 KiB
Markdown

# 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`](./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:
- `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.
- `nuri.ts`'s stand-in cap value — the constant `OK` — becomes the real
`r:{base64url(serde_bare(ObjectRef))}`. It is **one function** (`mintCap`), because
every path now READS a stored cap instead of recomputing one. `hasReadCap` /
`targetOf` stay meaningful: the `r:` discriminant is upstream grammar, not ours.
- `shareCap(cap, toInbox)` becomes the native sealed delivery (`inbox_post_link`
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.**
- `publishRepoLink` becomes `RepoLinkV0`.
- The read filter (`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 (`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`](./simulation.md)).
- `doc_create` cannot target a non-private native store today — verified:
`StoreRepo` is not JS-constructible from 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 `store-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`](./nextgraph-current-state.md) § *The pointer → doc-shim
indirection*) has no target equivalent — the target has no central directory. Remove
it entirely: `store-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 `inbox_post_link` (proposed/future). 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 `accounts.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`](./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(...)` /
`@ng-eventually/client/polyfill` — 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
`shareCap(capFor(doc), theirInbox)` 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.