feat(inbox): un utilisateur a DEUX inboxes, comme en amont

Tranché par la cascade plutôt qu'en attendant une réponse : le niveau 3 (ORM) ne
dit rien des inbox, le niveau 2 non plus — `@ng-org/web` n'expose aucune méthode
contenant « inbox » et la session n'en publie aucune. C'est donc le modèle du
moteur qui décide, et il dit DEUX : un site porte une inbox sur son repo de store
public et une autre sur son protégé (`engine/verifier/src/site.rs:127-152`), les
seuls `AddInboxCap` du moteur, `new_store_default` n'en posant une que
`if !private`. Elles sont adressées séparément jusque dans les enregistrements de
contact, qui choisissent leur prédicat selon le profil visé — `ng:site_inbox` pour
un profil public, `ng:protected_inbox` sinon
(`engine/verifier/src/inbox_processor.rs:787,823-824`).

`userInbox(id)` en exposait une : une cardinalité que cette bibliothèque avait
inventée, et que le nom `walletInbox` avait contribué à masquer. Elle prend
désormais le scope, et le store PRIVÉ n'en a pas — d'où `InboxScope` plutôt que
`Scope` : demander l'inbox privée n'est pas une recherche qui ne rend rien, c'est
une question sans référent dans le modèle, et le type l'interdit.

`myInboxes` énumère les deux, `isOwnInbox` reconnaît les deux. Le shim garde un
triple par (user, scope).

160 tests unitaires, typecheck src/test/e2e vert, e2e 40/40 contre le broker.
This commit is contained in:
Sylvain Duchesne
2026-08-04 16:19:49 +02:00
parent b62bfe1e63
commit 3257afe8c0
16 changed files with 103 additions and 45 deletions
+2 -2
View File
@@ -95,7 +95,7 @@ multi-store. Do not read `Scope` (`types.ts`) as a physical store — it is the
logical label the registry attaches.
> Why `undefined` and not a real store? Because `doc_create` **cannot target a
> non-private native store** today: `StoreRepo` is not JS-constructible (verified
> non-private native store** today from the WEB build: `StoreRepo` is not constructible there (verified
> — see the parked `getNativeStore` note in
> [`migration-guide.md`](./migration-guide.md)). The private store is reachable
> because it opens without `RepoNotFound`.
@@ -135,7 +135,7 @@ public/protected/private stores — on top of one shared wallet.
user, and the only listing there is: the cross-account fan-out
(`listEntityDocs` / `resolveReadGraphs` / `allAccounts` / `loadShim`) was
**removed on 2026-07-30**, being cross-user enumeration by construction. The real read path is
`readModel.readUnion(docs)`, which reads the by-need doc set with one per-doc
`readUnion(docs)`, which reads the by-need doc set with one per-doc
anchored `sparql_query`, never an anchorless union-scan of the physical
wallet (see [`read-model.md`](./read-model.md)). The consumer application resolves
the by-need doc set from the current wallet's own scope index and