docs: reprendre les citations après le rangement, et retirer le membre login fabriqué

Les deux contrats et la doctrine portent des centaines de citations `file:line`
vers `src/`. Le rangement par destin les périmait en bloc — 208 reprises (45
chemins `src/x.ts`, 163 mentions nues). Sans ça la réorganisation pourrissait
l'instrument même qui tient la discipline qu'elle sert.

Et un défaut relevé par le contrat interne, vérifié : `ng-proxy` fabriquait un
membre `login`. `@ng-org/web` n'expose aucune méthode de ce nom — zéro
occurrence dans les déclarations installées comme dans `sdk/js/lib-wasm/src/lib.rs`
— mais le proxy répondait une fonction au lieu d'`undefined`, laquelle plantait
à l'appel. C'était le seul endroit où ce wrapper ajoutait à la surface du SDK,
contre son propre en-tête.

157 tests unitaires, typecheck src/test/e2e vert.
This commit is contained in:
Sylvain Duchesne
2026-08-04 12:56:02 +02:00
parent cd096de2b0
commit 0b37d17c2f
19 changed files with 151 additions and 145 deletions
+19 -19
View File
@@ -52,10 +52,10 @@ Because the emulation runs on ONE shared wallet, distinguish two levels:
- **A virtual user** — the library's emulation of one identity: the documents the
shim attributes to that account (its three store documents in
`store-registry.ts`). This is what "the user owns", and over it "list my
`shared-wallet/account-registry.ts`). This is what "the user owns", and over it "list my
documents" is meaningful and bounded.
**Which API you use follows that line, and it is enforced** (see `physical.ts`):
**Which API you use follows that line, and it is enforced** (see `shared-wallet/physical.ts`):
machinery operating on the *index of virtual users* — the store-root pointer, the
doc-shim, the account records — goes through unguarded primitives that are never
exported from the package. Everything touching a virtual user's own content goes
@@ -70,7 +70,7 @@ ones whose cap you were given.
At migration each virtual user becomes a real user with its own wallet; the
physical/virtual distinction, the "never enumerate the physical one" rule, and
`physical.ts` all dissolve into native per-user reads.
`shared-wallet/physical.ts` all dissolve into native per-user reads.
## Two axes, never conflate them (store ≠ document)
@@ -86,7 +86,7 @@ terminology historically fused:
`docCreate(sessionId, "Graph", "data:graph", "store", undefined)` targets the shared
wallet's private store. The trailing `store` arg left `undefined` targets the
private store (this is what `store-registry.ts`'s `createDoc()` does). So every
private store (this is what `shared-wallet/account-registry.ts`'s `createDoc()` does). So every
document the shim creates physically lives in one store (private), and the
`public|protected|private` scope is a logical label tracked in RDF by the
shim — not a NextGraph store. Therefore what a consumer application's "multi-store"
@@ -100,7 +100,7 @@ logical label the registry attaches.
> [`migration-guide.md`](./migration-guide.md)). The private store is reachable
> because it opens without `RepoNotFound`.
## The shared-wallet shim (`store-registry.ts`)
## The shared-wallet shim (`shared-wallet/account-registry.ts`)
Emulates the target infrastructure — where each user owns their own
public/protected/private stores — on top of one shared wallet.
@@ -207,7 +207,7 @@ store-id:
redirect the owner's deposits to itself). Both are dedicated documents (real repo
NURIs from `docCreate`), never the private-store root: routing deposits into the shim
graph would bloat the account→document trust root without bound. `myInboxes()`
enumerates both levels — what `connect.ts` drains at connection — and `isOwnInbox`
enumerates both levels — what `emulated-verifier/connect.ts` drains at connection — and `isOwnInbox`
answers from the same record. *(The former `resolveInboxAnchor`, a single inbox COMMON
to every user, was removed on 2026-07-30: nothing may be common but the mechanisms
that make the virtual users work.)*
@@ -220,7 +220,7 @@ store-id:
A document that has an inbox carries its address on its emulated **Header branch** —
a reserved subject inside the document, so any holder of the document reads it, and
`read-model` filters the whole `urn:ng-eventually:` namespace out of consumer data
(`src/machinery.ts`). This mirrors upstream's split: a depositor seals with the inbox
(`src/emulated-verifier/machinery.ts`). This mirrors upstream's split: a depositor seals with the inbox
PUBLIC key and needs nothing else, only the owner holds the private half.
**One inbox belongs to one document** — never several documents behind one inbox, a
relation upstream cannot express (the verifier routes by `inboxes: PubKey → RepoId`
@@ -265,7 +265,7 @@ is preserved in [`decisions/private-store-nuri-scope.md`](./decisions/private-st
A validated hard constraint, not a style choice: `docs.ts` calls the real
injected `ng` (`getConfig().ng`) directly, never the public `ng` proxy
(`makeNg` in `ng-proxy.ts`).
(`makeNg` in `surface/ng-proxy.ts`).
`@ng-org/web`'s `ng` is already an iframe-RPC proxy (postMessage marshaling,
see [`nextgraph-current-state.md`](./nextgraph-current-state.md) § integration).
@@ -283,13 +283,13 @@ verified: routing the shim's `doc_create`/SPARQL through the public proxy turned
`docs.ts` therefore imports **no** `@ng-org` package and must **not** import from
`./ng-proxy`.
## Emulated ReadCap — per document (`caps.ts` + `read-filter.ts`)
## Emulated ReadCap — per document (`emulated-verifier/caps.ts` + `emulated-verifier/read-filter.ts`)
In the target the broker only delivers documents the wallet holds a ReadCap
for, so `useShape` already returns an authorized subset. Here (single shared
wallet, everything readable) the lib reproduces that with a read-filtered view:
- **`CapRegistry` (`caps.ts`)** models a ReadCap as what it is: **the document's
- **`CapRegistry` (`emulated-verifier/caps.ts`)** models a ReadCap as what it is: **the document's
key**. The access unit is the document = repo NURI (an item's `@graph`), never
the item — because in `nextgraph-rs` a store is just a container repo and holding
its cap does not grant the repos it references (no store-level read inheritance;
@@ -305,13 +305,13 @@ wallet, everything readable) the lib reproduces that with a read-filtered view:
constant `OK` (see the module header): the only question the emulation answers is
*do I hold this cap or not*, so the value says exactly that and pretends nothing
more. P1b, not P1a, is the batch that turns the shape into a protection.
- **`read-filter.ts`** — `makeReadFilteredView` wraps the reactive set in a
- **`emulated-verifier/read-filter.ts`** — `makeReadFilteredView` wraps the reactive set in a
`Proxy`: iteration / `size` / `forEach` keep only items whose `@graph` the
current holder holds; everything else (`add`, `delete`, `has`, `getById`…) forwards to
the target, preserving writes and reactivity. An item with no `@graph` is kept (it
names no document, so there is no cap to hold). `filterReadable` is the pure
variant. Note the absence of a `user` parameter — that absence *is* the model.
- **`useShape` (`use-shape.ts`)** applies the view only once a cap exists at all
- **`useShape` (`surface/use-shape.ts`)** applies the view only once a cap exists at all
(`caps.isEnforcing()`) — before that it passes the real set through unchanged (no
regression for a consumer that never touches caps). Once ANY cap is issued the
regime is possession for **every** holder, including one who holds nothing:
@@ -428,7 +428,7 @@ cosmetic. Proven in `test/cross-user-access.test.ts`.
### Write-guard coverage (honest scope)
The emulated write guard (`ng-proxy.ts`, `sparql_update` override) enforces the
The emulated write guard (`surface/ng-proxy.ts`, `sparql_update` override) enforces the
per-document write cap on the public `ng` proxy only. In practice the
consumer application's write paths (`docs.sparqlUpdate`, ORM `ngSet`) call the real
injected `ng` directly — never the public proxy — for the validated `DataCloneError`
@@ -440,7 +440,7 @@ natively at migration); the read side is what makes isolation observably active.
### The per-document ReadCap is the isolation path (item-level filter retired)
Isolation is enforced by the per-document ReadCap (`caps.ts` + `read-filter.ts`)
Isolation is enforced by the per-document ReadCap (`emulated-verifier/caps.ts` + `emulated-verifier/read-filter.ts`)
alone: the access unit is the document (`@graph` = repo), and the only acts are
possession-shaped (`createEntityDoc` files a cap, `shareCap` delivers one,
`publishRepoLink` emits an openable link). Because the consumer application writes
@@ -515,7 +515,7 @@ The inbox + watcher is the one deposit/read mechanism a consumer reuses for its
purposes — a registration/deposit, a cap delivery (`shareCap`), a link handed to
someone — same `post` API, same watcher.
## The virtual user boundary (`reach.ts` + `physical.ts`)
## The virtual user boundary (`emulated-verifier/reach.ts` + `shared-wallet/physical.ts`)
Every access function is confined to the user currently connected: no cross-user
access, so the consumer is coded against a reach that will actually exist.
@@ -541,13 +541,13 @@ named primitive rather than a flag, because it is a different act — you hold n
you cannot read back, and you get nothing in return. It is the only channel by which
a link crosses between users, hence the bootstrap of the whole reachability graph.
The machinery lives in `physical.ts` (see *Physical user vs virtual user* above):
The machinery lives in `shared-wallet/physical.ts` (see *Physical user vs virtual user* above):
unguarded primitives, never exported from the package, used only for the index of
virtual users. Separating the FUNCTIONS is what replaced an earlier exemption list —
the machinery does not get waved through the guard, it calls something the guard
never saw.
## Connecting a user (`connect.ts`)
## Connecting a user (`emulated-verifier/connect.ts`)
Processing inboxes is the **library's** job, not the app's: a consumer must never
have to remember to drain a queue for documents shared with it to become readable —
@@ -587,7 +587,7 @@ first.
The ADR that specified it ([`decisions/discovery-model.md`](./decisions/discovery-model.md)) is marked superseded, and keeps the part that survives: the `discovery → synchronization → query` frame still holds, with stage 1 re-read as *"a link reached you"* rather than *"you consulted an index"*. Which makes the **inbox** the bootstrap of the whole reachability graph — see [`briefs/2026-07-30-virtual-wallet-boundary.md`](./briefs/2026-07-30-virtual-wallet-boundary.md).
## Emulated write guard (`ng-proxy.ts`)
## Emulated write guard (`surface/ng-proxy.ts`)
The public `ng` proxy overrides `sparql_update` to enforce an emulated write
cap: a write is refused unless the current user holds the target document's
@@ -596,7 +596,7 @@ specific document (the `anchor` arg) is governed by it — ungoverned docs (the
mono-store default, no cap declared) flow through unchanged. This mirrors the target
broker/verifier, which refuses a write without the document's write cap.
## Identity store (`accounts.ts`)
## Identity store (`shared-wallet/accounts.ts`)
The real NextGraph login (redirect to the broker, opening the single shared
wallet) is perceived as a technical access barrier (see the login