From 1de9be9167d0fbc5e9871e4cbde7eed35a6125c0 Mon Sep 17 00:00:00 2001 From: Sylvain Duchesne Date: Mon, 3 Aug 2026 17:19:19 +0200 Subject: [PATCH] =?UTF-8?q?docs:=20la=20cascade=20des=20trois=20r=C3=A9f?= =?UTF-8?q?=C3=A9rences,=20et=20l'inbox=20s'ouvre=20=C3=A0=20tout=20moment?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit « NextGraph » n'est pas une couche. Trois niveaux, à parcourir de haut en bas en s'arrêtant au premier qui répond : (1) le SDK JS / ORM — s'il couvre le besoin, ne rien implémenter ici, passthrough ; (2) le binding wasm — si le niveau 1 manque ou ne convient pas, s'appuyer sur l'appel équivalent ; (3) le moteur Rust — si rien au-dessus ne répond, se tenir à son MODÈLE : cardinalités, unités d'adressage, ce qu'une structure peut ou ne peut pas exprimer. Et dire de quel niveau vient un choix. Un passthrough de niveau 1 est un fait ; une forme de niveau 3 est un pari contraint par le moteur. Les présenter à l'identique est ce qui fabrique les fausses certitudes — `inbox_post_link` a été cité dans huit fichiers comme une API NextGraph prévue alors que c'était un nom proposé dans notre propre plan de fork. Sur l'ouverture d'une inbox, le moteur tranche contre ce que j'avançais : elle n'est pas réservée à la création. `AddInboxCap` est un commit de branche User (`commit.rs:1043-1050`), donc une entrée dans un flux ouvert dans le temps, et son type documente explicitement le cas tardif — « DEPS to the previous AddInboxCap commit(s) if it is an update. in this case, repo_id should match ». Le verifier l'applique à un repo déjà chargé. Que `new_store_default` pose les deux inbox de store à la création d'un site décrit comment ces deux-là sont faites, pas une contrainte du modèle. Donc `openDocumentInbox` reste appelable à tout moment, et le `createEntityDoc(…, { withInbox })` que j'avais proposé aurait rétréci le modèle. Écrit dans le code pour qu'il ne soit pas « simplifié » plus tard. --- README.md | 23 +++++++++++++++++++++++ packages/client/src/store-registry.ts | 25 +++++++++++++++++++++---- 2 files changed, 44 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 0e51bd5..45f2365 100644 --- a/README.md +++ b/README.md @@ -106,6 +106,29 @@ it outranks cost, latency and convenience. When the shape and the cost conflict, the shape and attack the cost elsewhere — and if it truly cannot be solved, say so rather than bend the model quietly. +### The three references, in cascade + +"NextGraph" is not one layer, and conflating them is how a fact about one gets asserted +about another. Walk them **top down, stop at the first that answers**: + +| # | Layer | Where | Rule | +|---|---|---|---| +| 1 | **JS SDK / ORM** | `@ng-org/orm`, `@ng-org/shex-orm` — source in `sdk/js/orm` (TypeScript) | Covers the need fully → **do not implement it here.** Pass through. | +| 2 | **wasm binding** | `@ng-org/web` — source in `sdk/js/lib-wasm` (77 exported methods) | Level 1 absent or unsatisfactory → build on the **equivalent binding call**. | +| 3 | **Rust engine** | `engine/` — `repo`, `verifier`, `net`, `broker`, `wallet` | Nothing above answers → hold to the engine's **MODEL**: cardinalities, addressing units, what a structure can and cannot express. | + +Level 3 is the deepest and the one whose facts are hardest-won — but reaching it means +**inventing a surface**, because the JS shape does not exist yet. So always **say which +level a choice came from**. A level-1 passthrough is a fact; a level-3 shape is a bet +constrained by the engine. Presenting them alike is what manufactures false certainty — +`inbox_post_link` was cited across eight files as a planned NextGraph API when it was +only a name proposed in [`docs/fork-inbox-fallback.md`](./docs/fork-inbox-fallback.md). + +Concretely for the inbox: level 1 has nothing, level 2 has **no `inbox` method at all** +(and the verifier has no `InboxPost` arm), so `inbox.*` is level 3 — held to the engine's +model (one inbox ↔ one repo, addressed by `(overlay, pubkey)`, no target document in the +message) with a JS surface of our own making. + - SDK-identical surface: the client wraps the real `ng` (a Proxy that forwards everything and overrides only what must be emulated) and `useShape`. The real SDK is injected via `configure()` (no hard import → build-alias safe and diff --git a/packages/client/src/store-registry.ts b/packages/client/src/store-registry.ts index ef51b6f..7db89a7 100644 --- a/packages/client/src/store-registry.ts +++ b/packages/client/src/store-registry.ts @@ -1109,10 +1109,27 @@ export async function userStoreDoc(id: string, scope: Scope): Promise { * per document. At migration this either becomes native or stays emulated here; either * way the consumer-facing act is unchanged. * - * Lazy on purpose: creating an inbox document for every entity up front would - * double every `createEntityDoc` for inboxes most documents never receive anything - * in. Upstream the keypair is cheap; here an inbox is a document, so it is minted - * when first asked for. + * **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. + * + * 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 + * anything in. Upstream the keypair is cheap; here an inbox is a document, so it is + * minted when first asked for. + * + * *(Not covered: ROTATING an inbox key — the engine's "update" case with a new + * `priv_key`. This function is idempotent and returns the existing inbox instead. A + * known limit, not an oversight.)* * * Only for a document this user OWNS — see {@link ownsDocument}. Opening an inbox on * someone else's document would be usurpation, not a courtesy: the opener keeps the