From 2406afec8b1bbc70934b7bfe86360fcea58e3b85 Mon Sep 17 00:00:00 2001 From: Sylvain Duchesne Date: Mon, 3 Aug 2026 17:55:46 +0200 Subject: [PATCH] =?UTF-8?q?docs:=20l'inbox=20par=20document=20n'est=20pas?= =?UTF-8?q?=20une=20anticipation,=20c'est=20une=20capacit=C3=A9=20du=20mot?= =?UTF-8?q?eur?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Glissement à corriger : de « le moteur ne le FAIT pas » (vrai) j'ai conclu « c'est notre anticipation » (faux). Le moteur le PERMET, et de façon générique par conception : - `inbox: Option` est un champ de TOUT `Repo` (`repo.rs:126`), pas d'une structure de store ; - `AddInboxCapV0` est clé par `repo_id` — « Repo the Inbox is opened for » ; - `update_inbox_cap_v0` l'applique via `self.repos.get_mut(repo_id)` sans AUCUNE vérification `is_store` (`verifier.rs:1920`) ; - et à tout moment, `AddInboxCap` étant un commit de branche User dont le type documente le cas de mise à jour. Ce qui est vrai est plus étroit : aucun chemin de code n'en CRÉE une pour un document. « Ne fait pas » n'est pas « ne peut pas » — c'est précisément l'interdit que ma propre règle pose, et je l'ai enfreint en le formulant. Donc l'inbox par document s'aligne sur le modèle du moteur (niveau 1) ; ce qui est de nous est la surface JS, aucune n'étant exposée aux niveaux 2 et 3. Corrigé dans store-registry.ts, nextgraph-current-state.md et les deux briefs. --- .../2026-07-30-virtual-wallet-boundary.md | 2 +- .../2026-08-03-document-inbox-addressing.md | 2 +- docs/nextgraph-current-state.md | 18 +++++++--- packages/client/src/store-registry.ts | 35 ++++++++----------- 4 files changed, 29 insertions(+), 28 deletions(-) diff --git a/docs/briefs/2026-07-30-virtual-wallet-boundary.md b/docs/briefs/2026-07-30-virtual-wallet-boundary.md index ae92e9e..98679a8 100644 --- a/docs/briefs/2026-07-30-virtual-wallet-boundary.md +++ b/docs/briefs/2026-07-30-virtual-wallet-boundary.md @@ -192,7 +192,7 @@ So the store-root pointer, the doc-shim and the account records go through the m The asymmetry holds at both levels, and a test walks it: **anyone deposits** into a document's inbox (that is how a third party reaches its owner at all), **only the owner reads** it. - **Correction 2026-08-03 — this step ANTICIPATES, it does not mirror.** The paragraph above overstated the upstream fact, and an adversarial doc review caught it. Verified: `new_store_default` attaches an inbox only `if !private` (`engine/verifier/src/verifier.rs:2994`), and `doc_create` goes through `new_repo_default`, which leaves `inbox: None` (`engine/repo/src/repo.rs:574`). The **only** two `AddInboxCap` commits in the engine are for the public and protected STORE repos (`engine/verifier/src/site.rs:128,149`). So today **no document has an inbox, and neither does the private store**. What is genuinely verified is the *shape*: `AddInboxCapV0` is keyed by `repo_id` (`engine/repo/src/types.rs:1973`), so the record accommodates an inbox on any repo. The step stands as delivered — a per-document inbox is what the consumer needs and what the record's shape supports — but it must be read as a bet on where upstream goes, not as an emulation of what it already does. + **Correction 2026-08-03, twice.** *First:* the paragraph above overstated the upstream fact — an adversarial doc review caught it. *Then the correction itself overshot*, calling the step an ANTICIPATION: the engine **supports** an inbox on any repo (`inbox` is a field of every `Repo`, `AddInboxCapV0` is keyed by `repo_id`, and `update_inbox_cap_v0` applies it with no `is_store` check — `engine/verifier/src/verifier.rs:1920`). What no code path does is CREATE one for a document. Verified: `new_store_default` attaches an inbox only `if !private` (`engine/verifier/src/verifier.rs:2994`), and `doc_create` goes through `new_repo_default`, which leaves `inbox: None` (`engine/repo/src/repo.rs:574`). The **only** two `AddInboxCap` commits in the engine are for the public and protected STORE repos (`engine/verifier/src/site.rs:128,149`). So today **no document is GIVEN an inbox, and neither is the private store** — which says nothing about what may have one. The step stands as delivered, and it is aligned on the engine's model rather than betting past it. ## Relation to P1b diff --git a/docs/briefs/2026-08-03-document-inbox-addressing.md b/docs/briefs/2026-08-03-document-inbox-addressing.md index c380e11..145c043 100644 --- a/docs/briefs/2026-08-03-document-inbox-addressing.md +++ b/docs/briefs/2026-08-03-document-inbox-addressing.md @@ -16,7 +16,7 @@ > > **Where the address lives.** On the document's emulated **Header branch** (`urn:ng-eventually:shim:headerBranch`), beside the content rather than in it — the same subject-as-compartment shape already used for the Store and User branches. `read-model` now drops every subject under the reserved `urn:ng-eventually:` namespace (`src/machinery.ts`), so the address cannot surface as one of the entity's properties. That filter is by SUBJECT, so it covers every emulated compartment present and future. > -> **The open question, answered.** *"Is 'a document has an inbox addressable by any holder' upstream, or this library's invention?"* — **the library's**, and it is now written down as such. Verified: no document has an inbox upstream, and neither does the private store; the only two `AddInboxCap` commits in the engine are for the public and protected STORE repos (`engine/verifier/src/site.rs:128,149`), `new_store_default` attaches one only `if !private` (`engine/verifier/src/verifier.rs:2994`), and `doc_create` leaves `inbox: None` (`engine/repo/src/repo.rs:574`). What IS upstream is the shape that makes this a defensible anticipation rather than a fiction: `AddInboxCapV0` is keyed by `repo_id` (`engine/repo/src/types.rs:1973`), so the record accommodates an inbox on any repo. The half-split is upstream's too — a depositor seals with the inbox PUBLIC key (`engine/net/src/types.rs:4299`) and only the owner holds the private half — which is why an address is public by nature and belongs on the document, not on the owner's User branch. +> **The open question, answered.** *"Is 'a document has an inbox addressable by any holder' upstream, or this library's invention?"* — **upstream, as a capability**; ours only as a JS surface. The engine lets any repo have an inbox: `inbox: Option` is a field of every `Repo` (`engine/repo/src/repo.rs:126`), `AddInboxCapV0` is keyed by `repo_id` (`engine/repo/src/types.rs:1973`), and `update_inbox_cap_v0` applies it with **no `is_store` check** (`engine/verifier/src/verifier.rs:1920`), at any time (User-branch commit, `commit.rs:1043-1050`, whose type documents the update case). What no code path does is CREATE one for a document: `new_store_default` attaches one only `if !private` (`verifier.rs:2994`), `doc_create` leaves `inbox: None` (`repo.rs:574`), and the engine's only two `AddInboxCap` commits are for the public and protected STORE repos (`site.rs:128,149`). "Does not" is not "cannot" — an earlier version of this note called the feature an anticipation on exactly that confusion. The half-split is upstream's too — a depositor seals with the inbox PUBLIC key (`engine/net/src/types.rs:4299`) and only the owner holds the private half — which is why an address is public by nature and belongs on the document, not on the owner's User branch. > > **Also fixed, and it was the root of the reported symptom.** `openDocumentInbox` (formerly `documentInbox`) called by a non-owner used to mint a parallel inbox and record it for the caller — no error, deposits lost. It now refuses, on OWNERSHIP (read from the Store branches), not on cap possession: a cap can be received, and a recipient must not be able to redirect the owner's deposits to itself. diff --git a/docs/nextgraph-current-state.md b/docs/nextgraph-current-state.md index 2ca2459..0d0a2c4 100644 --- a/docs/nextgraph-current-state.md +++ b/docs/nextgraph-current-state.md @@ -121,11 +121,19 @@ documents, and not the private store. `new_store_default` attaches one solely `i `AddInboxCap` commits in the whole engine are the two in `engine/verifier/src/site.rs:128,149` — one for the public store repo, one for the protected one. -The *register* is nonetheless per-repo: `AddInboxCapV0 { repo_id, overlay, priv_key }` -(`engine/repo/src/types.rs:1973`) records **which repo** an inbox is opened for, so the -shape accommodates an inbox on any repo. Nothing creates one, which is a different -statement from the shape forbidding it — and it is why this lib's per-document inbox is -an ANTICIPATION of that shape, not an emulation of something upstream already does. +**But the engine SUPPORTS an inbox on any repo — "does not" and "cannot" are different +statements.** `inbox: Option` is a field of EVERY `Repo` +(`engine/repo/src/repo.rs:126`), not of a store structure. `AddInboxCapV0` is keyed by +`repo_id` (`engine/repo/src/types.rs:1973`) — *"Repo the Inbox is opened for"*. And +`update_inbox_cap_v0` applies it via `self.repos.get_mut(repo_id)` with **no `is_store` +check of any kind** (`engine/verifier/src/verifier.rs:1920`). It is generic by +construction, and available at any time: `AddInboxCap` is a User-branch commit +(`engine/repo/src/commit.rs:1043-1050`) whose type documents the late case — *"DEPS to +the previous AddInboxCap commit(s) if it is an update"*. + +So a per-document inbox is **not an anticipation**: it is an engine capability that no +code path exercises automatically and that no level-2 or level-3 API exposes. This lib +implements it aligned on the engine's model. A non-editor can deposit into an inbox without being invited as an editor; the owner moderates. NURI: `did:ng:d:`. Content: the `InboxMsgContent` enum (`ContactDetails`, diff --git a/packages/client/src/store-registry.ts b/packages/client/src/store-registry.ts index 7db89a7..e8172fa 100644 --- a/packages/client/src/store-registry.ts +++ b/packages/client/src/store-registry.ts @@ -1099,28 +1099,21 @@ export async function userStoreDoc(id: string, scope: Scope): Promise { * same branch that carries `AddLink`. So "which inboxes may I read" is answered by the * User branch, and that is what this emulates. * - * **This ANTICIPATES: no document has an inbox upstream today.** `new_store_default` - * attaches one only `if !private` (`engine/verifier/src/verifier.rs:2994`), and - * `doc_create` goes through `new_repo_default`, which leaves `inbox: None` - * (`engine/repo/src/repo.rs:574`) — the only two `AddInboxCap` commits in the engine - * are for the public and protected STORE repos (`engine/verifier/src/site.rs:128,149`). - * What is verified is the SHAPE: the record is keyed by `repo_id`, so it accommodates - * an inbox on any repo. What is not verified is that anything upstream will create one - * per document. At migration this either becomes native or stays emulated here; either - * way the consumer-facing act is unchanged. + * **The engine SUPPORTS this; nothing exercises it automatically.** Those are two + * different statements, and conflating them is what made an earlier version of this + * comment call the feature an "anticipation". It is not. `inbox: Option` is a + * field of EVERY `Repo` (`engine/repo/src/repo.rs:126`), not of a store structure; + * `AddInboxCapV0` is keyed by `repo_id` (`engine/repo/src/types.rs:1973`); and + * `update_inbox_cap_v0` applies it with `self.repos.get_mut(repo_id)` and **no + * `is_store` check of any kind** (`engine/verifier/src/verifier.rs:1920`). Generic by + * construction, and at any time (see the User-branch note above). * - * **Callable at ANY time, not only at creation — and that is the faithful shape.** - * `AddInboxCap` is a User-branch commit (`CommitBody::on_user_branch`, - * `engine/repo/src/commit.rs:1043-1050`), i.e. an entry in a stream that stays open over - * time, and its type documents the late case explicitly: *"DEPS to the previous - * AddInboxCap commit(s) if it is an **update**. in this case, repo_id should match"* - * (`engine/repo/src/types.rs:1969-1971`). The verifier applies it to an already-loaded - * repo (`update_inbox_cap_v0` → `repo.inbox = Some(...)`, `verifier.rs:1920`). That - * `new_store_default` attaches the two STORE inboxes at site creation is how those two - * happen to be made — not a constraint that an inbox may only exist from birth. So do - * NOT "simplify" this into a `createEntityDoc(…, { withInbox })` flag: it would narrow - * the model, and an owner deciding later that a document should receive is a case the - * engine supports. + * What is true is narrower: no code path CREATES one for a document — `new_store_default` + * attaches one only `if !private` (`verifier.rs:2994`), `doc_create` leaves `inbox: None` + * (`repo.rs:574`), and the only two `AddInboxCap` commits in the engine are for the + * public and protected STORE repos (`engine/verifier/src/site.rs:128,149`). So the + * capability exists and is simply unexposed above level 1: this function is aligned on + * the engine's model, it does not bet past it. * * Lazy on purpose, for the same reason: creating an inbox document for every entity up * front would double every `createEntityDoc` for inboxes most documents never receive