fix(inbox): une inbox appartient à un document, jamais à plusieurs
Retour sur l'adresse par défaut livrée en 8a382f2, qui faisait pointer tout
document vers l'inbox de son propriétaire. C'était acheter le coût au prix de
la forme — le mauvais arbitrage pour cette bibliothèque.
Vérifié en amont : le verifier route un message entrant par
`inboxes: PubKey → RepoId` (`engine/verifier/src/verifier.rs:1677,1928`) et le
déchiffre avec la moitié privée de CE repo. Et `InboxMsgBody`
(`engine/net/src/types.rs:4265`) ne porte aucun document cible — il n'en a pas
besoin : l'adresse EST l'identification. Une inbox appartient donc à exactement
un repo, et faire tenir plusieurs documents derrière une inbox émule une
relation que le modèle ne peut pas exprimer.
Conséquences :
- `createEntityDoc` ne publie plus rien. Un document neuf n'a pas d'inbox et
`documentInboxAddress` rend `undefined`.
- Une inbox s'ouvre par `openDocumentInbox(doc)`, sur décision du propriétaire.
C'est aussi ce qui règle le coût sans toucher à la forme : seuls les
documents destinés à RECEVOIR en paient une — l'app le sait, la bibliothèque
non.
- `inbox.postToDocument(doc, { payload })` : l'app nomme le DOCUMENT, jamais une
inbox. Lève quand le document n'en a pas, au lieu de rendre la main
silencieusement — un dépôt qui disparaît sans erreur est exactement le bug que
ce chemin traînait.
- Pas de champ « document cible » sur un dépôt. Ce serait une invention que les
apps devraient désapprendre à la migration.
README, principe de conception : les deux moitiés sont contraignantes, et c'est
la seconde qu'on brade. La surface doit être au plus près du futur SDK, mais
l'IMPLÉMENTATION aussi doit être au plus près de ce que NextGraph prévoit, sans
exception. Ce qui est connu vaut spécification. La pression à dévier ne se
présente jamais comme une déviation : elle arrive comme un coût, une latence,
une gêne d'ergonomie — bien réels. Deux cas déjà rencontrés sont consignés, avec
le signal commun : un choix qui ferait apprendre au consommateur quelque chose
qu'il devra DÉSAPPRENDRE.
157 tests unitaires, e2e 40/40 contre le broker en ligne.
This commit is contained in:
+17
-10
@@ -217,16 +217,23 @@ store-id:
|
||||
resolution answered *"which inbox do I know for this document"*, so a depositor got
|
||||
one of their own and their deposit vanished silently
|
||||
([`briefs/2026-08-03-document-inbox-addressing.md`](./briefs/2026-08-03-document-inbox-addressing.md)).
|
||||
Now every document carries its address **from creation**, 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`). It points at the owner's own inbox by default —
|
||||
one inbox per user, amortized, NOT one document per document — and
|
||||
`openDocumentInbox` replaces it when an owner wants a document's deposits kept apart.
|
||||
This mirrors upstream's split: a depositor seals with the inbox PUBLIC key and needs
|
||||
nothing else, only the owner holds the private half. At migration the address becomes
|
||||
the repo's native inbox pubkey and the resolution moves; the consumer-facing act
|
||||
(resolve, then `inbox.post`) is unchanged.
|
||||
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
|
||||
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`
|
||||
and unseals with that repo's key, `engine/verifier/src/verifier.rs:1677,1928`), which
|
||||
is also why a deposit carries no target document: the address identifies it. A fresh
|
||||
document therefore has NO inbox and `documentInboxAddress` returns `undefined` — its
|
||||
owner opens one when the document is meant to receive, which is what keeps the cost
|
||||
proportional. At migration the address becomes the repo's native inbox pubkey and the
|
||||
resolution moves; the consumer-facing act is unchanged.
|
||||
- **`inbox.postToDocument(doc, { payload })`** — the one call an app makes to reach a
|
||||
document's owner: it names the DOCUMENT, never an inbox. **Throws** when the document
|
||||
has no inbox, rather than returning quietly: a deposit that vanishes without an error
|
||||
is the exact bug this path shipped with.
|
||||
|
||||
Both resolve the native store ids from the injected session
|
||||
(`RegistrySession.protectedStoreId` / `publicStoreId`, alongside the existing
|
||||
|
||||
Reference in New Issue
Block a user