feat(inbox): l'inbox d'un document est adressable par tout détenteur
Répond au brief 2026-08-03 remonté depuis le consommateur. `documentInbox(doc)` répondait « quelle inbox est-ce que MOI je connais pour ce document » et en créait une quand la réponse était « aucune » : un tiers n'atteignait jamais l'inbox du propriétaire, il en obtenait une à lui, que personne ne lit, et son dépôt disparaissait sans erreur. C'est l'acte central du consommateur — s'inscrire à l'événement d'un autre — qui était silencieusement perdu. Lire une inbox et savoir où y déposer sont deux actes opposés, avec des publics opposés. Ils sont désormais deux fonctions : - `openDocumentInbox(doc)` — le PROPRIÉTAIRE ouvre une inbox dédiée. Refuse sur la PROPRIÉTÉ (lue depuis les branches Store), pas sur la possession du cap : un cap se reçoit, et un destinataire ne doit pas pouvoir rediriger vers lui les dépôts destinés au propriétaire. - `documentInboxAddress(doc)` — n'importe quel détenteur trouve où déposer. Ne crée jamais rien. L'adresse est publiée dès la CRÉATION, sur la branche Header émulée du document — un sujet réservé à l'intérieur du document, donc lisible par qui détient le document. Publier seulement le jour où le propriétaire ouvre une inbox dédiée laisserait une fenêtre pendant laquelle un tiers lit le document, ne trouve aucune adresse, et ne peut pas joindre le propriétaire du tout. Sur le coût mesuré par le brief (9m37 → 21m30) : il venait de la création d'un DOCUMENT supplémentaire par document. L'adresse publiée pointe vers l'inbox propre du propriétaire, qui existe déjà et s'amortit sur tous ses documents ; la création grandit d'un triple, pas d'un document. Le dépôt porte le document concerné, donc le propriétaire matérialise toujours par document. La forme « dérivable » du brief n'était pas disponible : notre inbox est un document, et un NURI dérivé nommerait un repo que `doc_create` n'a jamais créé. Le tout reflète la séparation d'amont : un déposant scelle avec la clé PUBLIQUE de l'inbox et n'a besoin de rien d'autre, seul le propriétaire détient la moitié privée — une adresse est donc publique par nature. `src/machinery.ts` : l'espace de noms `urn:ng-eventually:` que la bibliothèque se réserve, et le prédicat que le chemin de lecture utilise. La branche Header est le premier compartiment logé dans un document que le consommateur lit ; `read-model` écarte désormais tout sujet de cet espace, par SUJET et non par prédicat — ce qui couvre toutes les branches émulées, présentes et futures. Question ouverte du brief, tranchée : « une inbox de document adressable par tout détenteur » est une invention de cette bibliothèque, pas de l'amont — aucun document n'y a d'inbox, ni le store privé. Ce qui EST vérifié, c'est la forme qui rend l'anticipation défendable : `AddInboxCapV0` est clé par `repo_id`. Tests : le test qui validait « n'importe qui dépose » passait le NURI d'inbox au déposant par une variable du test — chemin qu'aucune app n'a. Réécrit avec les deux acteurs cloisonnés : le déposant reçoit le lien du document, qui est la seule chose qui circule dans ce modèle, et doit trouver l'adresse lui-même. Le fake `ng` gagne le SELECT de la branche Header et le `DELETE WHERE` (sans quoi un remplacement devenait une accumulation, précisément le bug qu'il évite). 157 tests unitaires, e2e 40/40 contre le broker en ligne.
This commit is contained in:
+27
-10
@@ -200,16 +200,33 @@ store-id:
|
||||
blocker, [`migration-guide.md`](./migration-guide.md)). At migration each scope
|
||||
resolves to the user's real per-scope store — the change is in this function,
|
||||
and the consumer application is unchanged.
|
||||
- **`walletInbox(id)` / `documentInbox(doc)`** — an inbox BELONGS to someone. The
|
||||
first is a virtual user's own inbox (where Links arrive), the second the inbox of
|
||||
one of its documents, created on first ask. 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` 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.)* At migration these
|
||||
become native per-document inboxes.
|
||||
- **`walletInbox(id)` / `openDocumentInbox(doc)`** — an inbox BELONGS to someone. The
|
||||
first is a user's own inbox (where Links arrive), the second a DEDICATED inbox for
|
||||
one of its documents, opened on demand by its **owner only** (ownership read from the
|
||||
Store branches — a received cap is not ownership, and a recipient must not be able to
|
||||
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`
|
||||
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.)*
|
||||
- **`documentInboxAddress(doc)` — the DEPOSIT side, and the one a third party uses.**
|
||||
Reading an inbox and finding where to deposit into it are opposite acts with opposite
|
||||
audiences, and conflating them is what made per-document inboxes unusable at first:
|
||||
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.
|
||||
|
||||
Both resolve the native store ids from the injected session
|
||||
(`RegistrySession.protectedStoreId` / `publicStoreId`, alongside the existing
|
||||
|
||||
Reference in New Issue
Block a user