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
+13 -13
View File
@@ -29,7 +29,7 @@ import {
docs,
subscribeDoc,
subscribeDocs,
readModel,
readUnion,
inbox,
storeRegistry,
useShape as libUseShape,
@@ -39,11 +39,11 @@ import {
// application must not — but through the internal path, never the published entry.
// `storeRegistry` above is the app-facing slice; these are the shim internals.
import * as registryInternals from "../src/shared-wallet/account-registry";
import * as accounts from "../src/shared-wallet/accounts";
import * as virtualUsers from "../src/shared-wallet/virtual-users";
import { isNuri } from "@ng-eventually/client";
import type { Nuri, ShapeObservable, ShapeQuery } from "@ng-eventually/client";
const { IdentityStore } = accounts;
const { IdentityStore } = virtualUsers;
/**
* The Playwright boundary. Every NURI reaching this harness crosses the bridge as
@@ -131,7 +131,7 @@ configureStoreRegistry({
const state: { status: string; error?: string } = { status: "connecting" };
// Identity store over the iframe's localStorage (the real AccountStorage).
// Identity store over the iframe's localStorage (the real VirtualUserStorage).
const identity = new IdentityStore(
typeof window !== "undefined" && window.localStorage ? window.localStorage : null,
);
@@ -293,7 +293,7 @@ const identity = new IdentityStore(
docNuris.push(d);
}
const toRead: Nuri[] = includeBad ? [...docNuris, "did:ng:o:definitely-not-a-real-doc-xyz"] : docNuris;
const subjects = await readModel.readUnion(toRead);
const subjects = await readUnion(toRead);
return { docNuris, subjectCount: subjects.length, subjects };
},
/**
@@ -309,9 +309,9 @@ const identity = new IdentityStore(
setCurrentUser("owner-O");
getCaps().open(doc, "protected");
setCurrentUser("someone-else");
const asStranger = await readModel.readUnion([doc]);
const asStranger = await readUnion([doc]);
setCurrentUser("owner-O");
const asOwner = await readModel.readUnion([doc]);
const asOwner = await readUnion([doc]);
resetCaps();
setCurrentUser(null);
return { strangerCount: asStranger.length, ownerCount: asOwner.length };
@@ -382,9 +382,9 @@ const identity = new IdentityStore(
async inboxPostRead(id: string, payloadA: unknown, payloadB: unknown) {
// The target must be that user's OWN inbox, not an arbitrary document: you may
// deposit into anyone's, you may only read your own. Establishing the identity
// FIRST is what makes `walletInbox` resolve (and file) that user's inbox.
// FIRST is what makes `userInbox` resolve (and file) that user's inbox.
setCurrentUser(id);
const target = await storeRegistry.walletInbox(id);
const target = await storeRegistry.userInbox(id);
await inbox.post(target, { payload: payloadA, from: null, ts: 1000 });
await inbox.post(target, { payload: payloadB, from: null, ts: 2000 });
const deposits = await inbox.read(target);
@@ -398,7 +398,7 @@ const identity = new IdentityStore(
// Watching an inbox is READING it continuously, so the watcher stays connected
// for the whole probe — including across `inboxWatchDeposit`.
setCurrentUser(id);
const target = await storeRegistry.walletInbox(id);
const target = await storeRegistry.userInbox(id);
const rec = { fires: 0, lastLen: -1, unsub: () => {}, target };
(window as any).__sdk._inboxWatch = rec;
rec.unsub = inbox.watch(target, (deposits) => {
@@ -556,7 +556,7 @@ const identity = new IdentityStore(
} catch (e: any) {
rawRowCount = -2; // threw (e.g. RepoNotFound / InvalidNuri)
}
const subjects = await readModel.readUnion(listed.length ? listed : [asNuri(entityNuri)]);
const subjects = await readUnion(listed.length ? listed : [asNuri(entityNuri)]);
const markers: string[] = [];
for (const subj of subjects) {
for (const vals of Object.values(subj.props)) {
@@ -840,7 +840,7 @@ const identity = new IdentityStore(
setCurrentUser(ownerId);
const deposits = await inbox.read(ownerInbox);
// The address is machinery: it must not surface among the document's properties.
const subjects = await readModel.readUnion([doc]);
const subjects = await readUnion([doc]);
const props = Object.keys(subjects[0]?.props ?? {});
setCurrentUser(null);
return {
@@ -859,7 +859,7 @@ const identity = new IdentityStore(
// the recipient's durable Links would grow run after run on a persistent wallet,
// making every later `connectedUser()` re-apply a longer and longer history.
setCurrentUser(friendId);
const friendInbox = await storeRegistry.walletInbox(friendId);
const friendInbox = await storeRegistry.userInbox(friendId);
setCurrentUser("owner-O");
const doc = await docs.docCreate(s.session_id, "Graph", "data:graph", "store", undefined);