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