docs: la cascade des trois références, et l'inbox s'ouvre à tout moment

« 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.
This commit is contained in:
Sylvain Duchesne
2026-08-03 17:19:19 +02:00
parent 4f5c3ed03b
commit 1de9be9167
2 changed files with 44 additions and 4 deletions
+23
View File
@@ -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 the shape and attack the cost elsewhere — and if it truly cannot be solved, say so
rather than bend the model quietly. 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 - 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 everything and overrides only what must be emulated) and `useShape`. The real
SDK is injected via `configure()` (no hard import → build-alias safe and SDK is injected via `configure()` (no hard import → build-alias safe and
+21 -4
View File
@@ -1109,10 +1109,27 @@ export async function userStoreDoc(id: string, scope: Scope): Promise<Nuri> {
* per document. At migration this either becomes native or stays emulated here; either * per document. At migration this either becomes native or stays emulated here; either
* way the consumer-facing act is unchanged. * way the consumer-facing act is unchanged.
* *
* Lazy on purpose: creating an inbox document for every entity up front would * **Callable at ANY time, not only at creation — and that is the faithful shape.**
* double every `createEntityDoc` for inboxes most documents never receive anything * `AddInboxCap` is a User-branch commit (`CommitBody::on_user_branch`,
* in. Upstream the keypair is cheap; here an inbox is a document, so it is minted * `engine/repo/src/commit.rs:1043-1050`), i.e. an entry in a stream that stays open over
* when first asked for. * 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 * 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 * someone else's document would be usurpation, not a courtesy: the opener keeps the