refactor(vocabulary): les noms publiés parlent la langue de la cible, et un test le tient

La correction de nomenclature du 2026-07-30 — en amont un *wallet* n'est qu'un
trousseau, ce qui possède des stores est un **user** (un *site*) — s'était faite
à la main. `walletInbox` y a échappé et a vécu des semaines, en faisant des
dégâts : le nom rendait « une inbox par wallet » évident, masquant qu'un user en
a **deux** en amont (repos de store public et protected, les deux seuls
`AddInboxCap` du moteur). Une discipline appliquée à la main en oublie un ; un
test non.

D'où `test/vocabulary.test.ts` : tout nom publié est bâti sur des mots que la
CIBLE emploie — vérifiés dans `nextgraph-rs` — ou porte un marqueur disant
POURQUOI il n'existe qu'ici (`virtual`, `physical`, `shim`, `emulated`,
`polyfill`), ce qui dit aussi quand il disparaît. Un échec n'est pas « renommer
pour faire passer le test », c'est une question : la cible a-t-elle un mot pour
ça ? la chose n'existe-t-elle qu'ici ? le mot est-il vraiment de la glue ?

Ce que le test a trouvé, et les réponses :

- `walletInbox` → `userInbox`, avec l'écart de cardinalité écrit noir sur blanc
  plutôt que caché par le nom.
- `accounts` / `AccountRecord` / `AccountStorage` → `virtualUsers` /
  `VirtualUserRecord` / `VirtualUserStorage`, module `accounts.ts` →
  `virtual-users.ts`. « account » n'est pas de la cible : c'est notre mot pour
  l'utilisateur virtuel, et le marqueur le dit désormais.
- `readModel` → la fonction `readUnion`, exposée directement. « model » n'était
  ni de la cible ni de la glue, et le namespace ne tenait qu'une fonction.
- Le reste était du vocabulaire légitime à déclarer (`subject`, `base`,
  `schema`, `connected`, le modèle réactif de l'ORM).

Corrigé au passage, sur signalement du contrat interne : l'en-tête d'`open-repo`
justifiait son correctif par un mécanisme que le source contredit. Un repo absent
de `self.repos` lève bien `RepoNotFound`
(`engine/verifier/src/request_processor.rs:264,269`). Les 0 lignes observées
viennent d'ailleurs — `Verifier::load` repeuple `self.repos` depuis le stockage
sur un profil persistant (`verifier.rs:535-560`), et notre propre `readDoc`
attrape toute erreur et rend `[]`. Le correctif est bon, le diagnostic écrit à
côté ne l'était pas.

159 tests unitaires, typecheck src/test/e2e vert, e2e 40/40 contre le broker.
This commit is contained in:
Sylvain Duchesne
2026-08-04 14:35:01 +02:00
parent e01a8dbab1
commit 107f9d1633
28 changed files with 297 additions and 135 deletions
@@ -11,7 +11,7 @@
> And four more, all confirmed:
>
> 4. **D4 would delete a working recovery path.** Inbox deposits are never removed (`packages/client/src/surface/inbox.ts`), so a second device/tab recovers its caps by re-reading. localStorage-without-re-reading loses them permanently, and contradicts P1a's delivered doctrine that per-process rebuild "is correct".
> 5. **D3 is false outside entity documents.** `capFor(scopeIndexDoc)` and `capFor(walletInbox)` are undefined before *and after* `listMyEntityDocs` — their caps can only ever be derived. Yet the boundary brief requires them reachable. Upstream that root comes from the wallet plus `AddSignerCap` on the User branch — a level the fact table omitted entirely.
> 5. **D3 is false outside entity documents.** `capFor(scopeIndexDoc)` and `capFor(userInbox)` are undefined before *and after* `listMyEntityDocs` — their caps can only ever be derived. Yet the boundary brief requires them reachable. Upstream that root comes from the wallet plus `AddSignerCap` on the User branch — a level the fact table omitted entirely.
> 6. **`doc_create` writes four times, not two** (+ the class quad on the Header branch, + `AddSignerCap` on the User branch).
> 7. **Ordering defect: D2 before the boundary guard opens cap harvesting.** Once caps are triples in `scopeIndexDoc(bob,…)`, and both `scopeIndexDoc` and `docs.sparqlQuery` are exported, `setCurrentUser("mallory")` reads Bob's caps. Today `mintCap` is unexported, so a NURI yields nothing. **The guard must land before the caps become triples.**
>
@@ -69,7 +69,7 @@ All read in `nextgraph-rs` (`git 213338f6`) on 2026-07-30, recorded in full in [
| scope index / scope container (`scopeIndexDoc`, `readScopeIndex`, `indexDocOf`, `INDEX_SUBJECT`) | **store** (`storeDoc`, `readStore`, …) | the thing that lists a user's documents IS a store |
| `shim:contains` | `ldp:contains` | NextGraph's own predicate for exactly this |
`docPublic` / `docProtected` / `docPrivate` on `AccountRecord` already read as stores; keep them, or rename to `publicStore` / `protectedStore` / `privateStore` for symmetry.
`docPublic` / `docProtected` / `docPrivate` on `VirtualUserRecord` already read as stores; keep them, or rename to `publicStore` / `protectedStore` / `privateStore` for symmetry.
### D2 — Emulate the Store branch as a distinct SUBJECT, not a distinct graph or document
@@ -93,7 +93,7 @@ The in-memory `CapRegistry` then stops being "the keyring" and becomes what it a
Verified: there is no received-caps register upstream, and inventing one would expose a shape the target does not have. What upstream does is persist the `read_cap` of every **opened** repo in local user storage.
So the emulation is a **local, per-virtual-user store** — the same nature as `shared-wallet/accounts.ts`'s existing `IdentityStore` (localStorage). This ends "re-read the inbox every session to recover caps", which the PO identified as the wrong model: an inbox is a queue you consume, not a store you re-read.
So the emulation is a **local, per-virtual-user store** — the same nature as `shared-wallet/virtualUsers.ts`'s existing `IdentityStore` (localStorage). This ends "re-read the inbox every session to recover caps", which the PO identified as the wrong model: an inbox is a queue you consume, not a store you re-read.
*Open*: whether to do D4 in this lot or after the boundary lot. It is the piece with the most design risk, and it is not needed for D1D3 to be correct.
@@ -106,7 +106,7 @@ So the emulation is a **local, per-virtual-user store** — the same nature as `
## What this breaks
`storeRegistry`'s exported names change (`scopeIndexDoc`, `listEntityDocs`, `AccountRecord` fields). `shim:contains` becomes `ldp:contains`, so **existing dev wallets stop resolving their documents** — acceptable for dev data, and consistent with how the pointer/doc-shim migration was handled before, but it must be stated rather than discovered.
`storeRegistry`'s exported names change (`scopeIndexDoc`, `listEntityDocs`, `VirtualUserRecord` fields). `shim:contains` becomes `ldp:contains`, so **existing dev wallets stop resolving their documents** — acceptable for dev data, and consistent with how the pointer/doc-shim migration was handled before, but it must be stated rather than discovered.
## Risks I want challenged