docs: sept affirmations sur NextGraph requalifiées à la source
Lot F de la revue adverse. Chaque affirmation relue dans `nextgraph-rs` par symbole avant
d'être réécrite ; aucune ne s'est révélée exacte.
- `AddLink` / `RemoveLink` / `RepoLinkV0` étaient présentés comme **implémentés**. Leurs
arms de vérificateur sont des `Ok(())`, là où celui d'`AddRepo` fait un vrai travail, et
rien ne les construit. Le tableau dit désormais « déclaré, stubbé », et la conclusion qui
en déduisait « le registre existe, seule la livraison manque » est corrigée : **les deux
bouts** sont déclarés-et-stubbés.
- La table `inboxes` était dite « reconstruite vide à chaque session » — elle est
repeuplée au chargement, et la clé privée d'inbox est persistée par repo. L'argument de
sécurité qui s'appuyait dessus repose maintenant sur le bon motif : la table est **par
vérificateur**, pas éphémère.
- La citation de « `doc_create` laisse `inbox: None` » pointait un constructeur réservé aux
tests ; re-ciblée sur le chemin de production.
- `ExtObjectGet` était dit « le seul » primitif accessible à un non-membre et exigeant les
clés : il y en a trois, et sa structure n'a aucun champ de clé.
- L'en-tête de `public-store.ts` était marqué **VERIFIED** alors qu'il repose sur un
commentaire de doc, et la condition qu'il citait (« si les brokers pairs l'autorisent »)
disparaissait de la conclusion. Requalifié en **pari**, condition rétablie.
- Deux sur-restrictions corrigées (ce qu'écrit le traitement d'un `ContactDetails`, et le
prétendu « miroir 1:1 » de `NuriV0`, qui a dix champs).
**Sur la grammaire du ReadCap, une correction de MA correction.** J'avais écrit que la
forme `{target}:r:{cap}` était notre invention. Faux : le segment `r:` et son encodage sont
ceux d'amont, et l'auteur de NextGraph l'a énoncé. Ce qui est établi est plus étroit —
aucun parseur amont n'accepte aujourd'hui un NURI de repo qui le porte, et le segment est
produit comme valeur de champ. J'avais conclu d'une implémentation absente à ce que la
cible ferait, ce que la doctrine du projet interdit nommément. Seule « P1b remplace la
valeur, pas la forme » est corrigée, requalifiée en **pari**.
**Et la surface ne publie plus de type que personne n'utilise.** `export * from
"./model/types"` publiait huit types en bloc ; c'est une liste nommée de six. `ReadCap`
sort — aucune signature publiée ne le prend ni ne le rend, seules deux fonctions privées
de `inbox.ts` s'en servent — et `InboxScope` aussi. Un type n'est publié que si une
signature publiée l'utilise.
197 tests, 0 échec ; les trois typechecks propres ; `lint` sans erreur.
This commit is contained in:
@@ -36,11 +36,12 @@ export interface EventuallyConfig {
|
|||||||
export async function ensureIdentity(): Promise<PrincipalId>; // returns who you are
|
export async function ensureIdentity(): Promise<PrincipalId>; // returns who you are
|
||||||
|
|
||||||
// ── addressing ───────────────────────────────────────────────────────────
|
// ── addressing ───────────────────────────────────────────────────────────
|
||||||
|
// A type is published only when a published signature uses it. `ReadCap` and
|
||||||
|
// `InboxScope` were withdrawn on 2026-08-10: no published call takes or returns
|
||||||
|
// either. They still exist inside the library — they are simply not yours to hold.
|
||||||
export type Nuri = `did:ng:${string}`;
|
export type Nuri = `did:ng:${string}`;
|
||||||
export type ReadCap = `did:ng:${string}:r:${string}`;
|
|
||||||
export type NuriLike = Nuri | string;
|
export type NuriLike = Nuri | string;
|
||||||
export type Scope = "public" | "protected" | "private";
|
export type Scope = "public" | "protected" | "private";
|
||||||
export type InboxScope = "public" | "protected";
|
|
||||||
|
|
||||||
// ── placement: where an application's documents live ─────────────────────
|
// ── placement: where an application's documents live ─────────────────────
|
||||||
export const storeRegistry: { // no identity parameter — the session is one user's
|
export const storeRegistry: { // no identity parameter — the session is one user's
|
||||||
|
|||||||
+41
-8
@@ -399,7 +399,7 @@ export function watch(
|
|||||||
|
|
||||||
Consequences per function:
|
Consequences per function:
|
||||||
|
|
||||||
- `post` / `postToDocument` — the sender-side act exists in the model (the broker routes `InboxPost` natively, `engine/net/src/server_broker.rs`); its JS surface does not. **The future SDK's name and signature are unknown** — `docs/nextgraph-current-state.md:187` records that nothing is announced. `postToDocument`'s resolution step (find the document's inbox address) rides on a **deliberate divergence**: this lib PUBLISHES the address on the document (Header-branch emulation), whereas upstream an address is only ever TRANSMITTED (`ContactDetails` carries `ng:site_inbox`/`ng:protected_inbox`, `engine/verifier/src/inbox_processor.rs:778-830`; the verifier's `inboxes` table is session-local, rebuilt empty — `verifier.rs:520,2820`). Documented in `docs/briefs/2026-08-03-document-inbox-addressing.md`.
|
- `post` / `postToDocument` — the sender-side act exists in the model (the broker routes `InboxPost` natively, `engine/net/src/server_broker.rs`); its JS surface does not. **The future SDK's name and signature are unknown** — `docs/nextgraph-current-state.md:187` records that nothing is announced. `postToDocument`'s resolution step (find the document's inbox address) rides on a **deliberate divergence**: this lib PUBLISHES the address on the document (Header-branch emulation), whereas upstream an address is only ever TRANSMITTED (`ContactDetails` carries `ng:site_inbox`/`ng:protected_inbox`, `engine/verifier/src/inbox_processor.rs:778-830`; the address→repo association lives in `inboxes: PubKey → RepoId`, a table of the **verifier** — one per user, `verifier.rs:105`). *(Corrected 2026-08-10: this said the table was "session-local, rebuilt empty". It is initialized empty (`verifier.rs:520,2820`) and then repopulated at every load — `Verifier::load` → `add_repo_without_saving` → `add_repo_`, `verifier.rs:534-566,2871,2887` — with the inbox private key persisted per repo, `user_storage/repo.rs:61,171,207,362`. The property that matters is that it is **per verifier**, not that it is ephemeral.)* Documented in `docs/briefs/2026-08-03-document-inbox-addressing.md`.
|
||||||
- `share` — a **gap upstream, not a disagreement**, verified at both ends: `ContactDetails.read_cap: Option<ReadCap>` exists (`engine/net/src/types.rs:4233`) but building a message with it is `unimplemented!()` (`types.rs:3786`), its only caller passes `with_readcap: false`, and the receiving arm never reads the field (`inbox_processor.rs:778-830`). `InboxMsgContent::Link` is a **unit variant carrying nothing** (`types.rs:4252`) — do not read it as the delivery channel. The recipient-side filing the lib emulates is real: `AddLink { read_cap }` on the User branch (`engine/repo/src/types.rs:1939-1948`). The consumer's *act* (share one document's cap to one inbox) is target-shaped; only the transport is emulated.
|
- `share` — a **gap upstream, not a disagreement**, verified at both ends: `ContactDetails.read_cap: Option<ReadCap>` exists (`engine/net/src/types.rs:4233`) but building a message with it is `unimplemented!()` (`types.rs:3786`), its only caller passes `with_readcap: false`, and the receiving arm never reads the field (`inbox_processor.rs:778-830`). `InboxMsgContent::Link` is a **unit variant carrying nothing** (`types.rs:4252`) — do not read it as the delivery channel. The recipient-side filing the lib emulates is real: `AddLink { read_cap }` on the User branch (`engine/repo/src/types.rs:1939-1948`). The consumer's *act* (share one document's cap to one inbox) is target-shaped; only the transport is emulated.
|
||||||
- `read` / `materialize` / `readSynced` / `processInbox` / `watch` — **stand-ins for the recipient's own verifier processing**, which has no consumer-facing JS surface upstream and may never have this list-of-deposits shape. A consumer should treat "my inbox gets processed when I connect, and applied caps just appear in what I hold" as the durable contract (that is what `connectedUser` automates, § 13); code that leans on enumerating raw deposits as a mailbox UI is coding against emulation detail it may have to unlearn. The consumer-payload case (`Deposit.payload` as app data) maps to `InboxMsgContent` variants upstream (`types.rs:4249-4260`), of which only `ContactDetails` and `SocialQuery` are more than unit variants today — arbitrary app payloads through the inbox are an **ASSUMPTION**, constrained by the model only in that messages are sealed, per-recipient, and applied by the recipient.
|
- `read` / `materialize` / `readSynced` / `processInbox` / `watch` — **stand-ins for the recipient's own verifier processing**, which has no consumer-facing JS surface upstream and may never have this list-of-deposits shape. A consumer should treat "my inbox gets processed when I connect, and applied caps just appear in what I hold" as the durable contract (that is what `connectedUser` automates, § 13); code that leans on enumerating raw deposits as a mailbox UI is coding against emulation detail it may have to unlearn. The consumer-payload case (`Deposit.payload` as app data) maps to `InboxMsgContent` variants upstream (`types.rs:4249-4260`), of which only `ContactDetails` and `SocialQuery` are more than unit variants today — arbitrary app payloads through the inbox are an **ASSUMPTION**, constrained by the model only in that messages are sealed, per-recipient, and applied by the recipient.
|
||||||
- `readForDocument(doc)` — the owner's side of a document's inbox, named by the DOCUMENT. Same LEVEL-1 SHAPE ruling as `read`: it is the recipient's own processing, which has no consumer-facing JS surface upstream, and enumerating its deposits is emulation detail. It exists so an application never handles an inbox address.
|
- `readForDocument(doc)` — the owner's side of a document's inbox, named by the DOCUMENT. Same LEVEL-1 SHAPE ruling as `read`: it is the recipient's own processing, which has no consumer-facing JS surface upstream, and enumerating its deposits is emulation detail. It exists so an application never handles an inbox address.
|
||||||
@@ -415,12 +415,22 @@ Consequences per function:
|
|||||||
### Today
|
### Today
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
// @ng-eventually/sdk — model/types.ts. The types are the whole published cap surface.
|
// @ng-eventually/sdk — model/types.ts. The published cap surface is now ONE type.
|
||||||
export type Nuri = `did:ng:${string}`;
|
export type Nuri = `did:ng:${string}`;
|
||||||
export type ReadCap = `did:ng:${string}:r:${string}`;
|
|
||||||
export type NuriLike = Nuri | string;
|
export type NuriLike = Nuri | string;
|
||||||
|
|
||||||
// NOT published, each deliberately:
|
// NOT published, each deliberately:
|
||||||
|
// ReadCap — `did:ng:${string}:r:${string}`. Unpublished 2026-08-10, when
|
||||||
|
// `export * from "./model/types"` became a named list. It remains the library's
|
||||||
|
// internal type for a cap-bearing reference, but NO published signature takes or
|
||||||
|
// returns one: within `surface/inbox.ts` only two private helpers use it
|
||||||
|
// (`capsSeenIn`, `capOfPayload`), plus the emulated registers. Publishing it named
|
||||||
|
// the one value the model says must never be handed over on request (§ 0 of
|
||||||
|
// `readcap-and-nuri-model.md`) — while leaving no published call able to produce
|
||||||
|
// one, since `linkTo` was removed and `mintCap` is unreachable (§ 11). A type whose
|
||||||
|
// only possible use by a consumer is a cast is worse than no type. See § 14.
|
||||||
|
// InboxScope — unpublished the same day, same rule: its only user is
|
||||||
|
// `account-registry.userInbox(id, scope)`, which is not published (§ 12).
|
||||||
// isNuri / hasReadCap — the type guards (`model/nuri.ts`). Unpublished since the
|
// isNuri / hasReadCap — the type guards (`model/nuri.ts`). Unpublished since the
|
||||||
// permissive-in change: every entry takes `NuriLike` and validates at the door, so
|
// permissive-in change: every entry takes `NuriLike` and validates at the door, so
|
||||||
// a consumer holding a plain string narrows nothing. Publishing a guard would
|
// a consumer holding a plain string narrows nothing. Publishing a guard would
|
||||||
@@ -452,7 +462,8 @@ clear(): void;
|
|||||||
|
|
||||||
**LEVEL-1 SHAPE.** There is no capability API at level 2 or 3 (no cap method in `index.d.ts`, none in the ORM), and there is **nothing to introspect upstream**: reading is key possession. The model, VERIFIED:
|
**LEVEL-1 SHAPE.** There is no capability API at level 2 or 3 (no cap method in `index.d.ts`, none in the ORM), and there is **nothing to introspect upstream**: reading is key possession. The model, VERIFIED:
|
||||||
|
|
||||||
- A ReadCap is the serialized `ObjectRef` — `format!("r:{}", base64_url::encode(&ser))` (`BlockRef::readcap_nuri`, `engine/repo/src/types.rs:518-521`). The lib's `ReadCap` template-literal grammar (`…:r:{cap}`) is upstream's, with the stand-in constant `OK` in place of the key material (P1b swaps the value, not the shape).
|
- A ReadCap is the serialized `ObjectRef` — `format!("r:{}", base64_url::encode(&ser))` (`BlockRef::readcap_nuri`, `engine/repo/src/types.rs:518-521`). **The `r:` segment and its encoding are upstream's**, reported by NextGraph's developer and verified in that function: id and key are serialized together into ONE opaque segment, unlike the `:k:` object/file/commit forms where they are two. The lib's `ReadCap` template-literal type uses that segment, with the stand-in constant `OK` in place of the key material.
|
||||||
|
- **"P1b swaps the value, not the shape" is a BET, and this section stated it as a fact until 2026-08-10.** What the source establishes is narrower, in three readings: (a) `readcap_nuri()` is produced as a **field value**, never concatenated onto a NURI — every call site fills `AppTabBranchInfo.readcap: Option<String>` (`engine/net/src/app_protocol.rs:1334`; `engine/verifier/src/verifier.rs:278,320`; `rocksdb_user_storage.rs:162,172`); (b) **no upstream parser accepts a repo NURI carrying `:r:`** — `NuriV0::new_from` (`app_protocol.rs:643-737`) tries `did:ng:i`, `RE_REPO_O`, `RE_FILE_READ_CAP`, `RE_REPO` and `RE_BRANCH`, and none of the regexes at `engine/net/src/types.rs:48-80` has an `r:` form; (c) the slot the type *declares* for a repo read cap is a **field** — `NuriV0.access: Vec<NgAccessV0>` with `NgAccessV0::ReadCap(ReadCap)` (`app_protocol.rs:54-62,192`) — itself constructed nowhere today (only `NgAccessV0::Key`, `:622`). Per the design principle none of that says the target will *not* parse a cap-bearing repo NURI; it says nothing parses one yet, so "the shape survives, only the value changes" is an assumption and not a passthrough. If the cap turns out to belong in a field, P1b moves it there instead of swapping a substring — a change the surface absorbs, because the value is opaque and nothing published parses it (§ 11).
|
||||||
- Caps live in two durable registers by origin: created documents → `AddRepo { read_cap }` on the store's Store branch (`engine/repo/src/types.rs:1890-1899`, committed by `doc_create` via `send_add_repo_to_store`, `engine/verifier/src/request_processor.rs:698`); received caps → `AddLink { read_cap }` on the private store's User branch (`types.rs:1939-1948`).
|
- Caps live in two durable registers by origin: created documents → `AddRepo { read_cap }` on the store's Store branch (`engine/repo/src/types.rs:1890-1899`, committed by `doc_create` via `send_add_repo_to_store`, `engine/verifier/src/request_processor.rs:698`); received caps → `AddLink { read_cap }` on the private store's User branch (`types.rs:1939-1948`).
|
||||||
- The one path that loads a repo from a cap is `pub(crate)` — `Verifier::load_repo_from_read_cap` (`engine/verifier/src/verifier.rs:2237`) — unexposed to JS.
|
- The one path that loads a repo from a cap is `pub(crate)` — `Verifier::load_repo_from_read_cap` (`engine/verifier/src/verifier.rs:2237`) — unexposed to JS.
|
||||||
|
|
||||||
@@ -469,7 +480,10 @@ The `CapRegistry` class itself is machinery (the in-memory record of what the co
|
|||||||
```ts
|
```ts
|
||||||
// NOT published — internal, and each for a stated reason:
|
// NOT published — internal, and each for a stated reason:
|
||||||
// surface/sparql.ts escapeLiteral, escapeIri, assertNuri
|
// surface/sparql.ts escapeLiteral, escapeIri, assertNuri
|
||||||
// model/nuri.ts isNuri, hasReadCap, targetOf, parseNuri, toNuri, mintCap
|
// model/nuri.ts isNuri, hasReadCap, targetOf, parseNuri, toNuri
|
||||||
|
// emulated-verifier/caps.ts mintCap (it lived in `model/nuri.ts` until the source
|
||||||
|
// layout was reorganised by migration fate; this list
|
||||||
|
// still said so until 2026-08-10)
|
||||||
```
|
```
|
||||||
|
|
||||||
Two decisions meet here, and both point the same way.
|
Two decisions meet here, and both point the same way.
|
||||||
@@ -536,7 +550,7 @@ export async function openDocumentInbox(doc: NuriLike): Promise<Nuri>;
|
|||||||
- **`listMyEntityDocs(id, scope)` → level 1/2, VERIFIED mechanism.** Upstream the listing is the store's `ldp:contains` graph (written at `request_processor.rs:706-708`), readable with an anchored `sparql_query` on the store; the caps come back by replaying the Store branch (`AddRepo::verify` → `load_repo_from_read_cap`). The function's shape (give me my per-scope doc NURIs) survives; its implementation becomes one native read.
|
- **`listMyEntityDocs(id, scope)` → level 1/2, VERIFIED mechanism.** Upstream the listing is the store's `ldp:contains` graph (written at `request_processor.rs:706-708`), readable with an anchored `sparql_query` on the store; the caps come back by replaying the Store branch (`AddRepo::verify` → `load_repo_from_read_cap`). The function's shape (give me my per-scope doc NURIs) survives; its implementation becomes one native read.
|
||||||
- **`userStoreDoc(id, scope)` / `resolveScopeGraph(scope)` / `resolveWriteGraph(id, scope)` → level 2, VERIFIED.** The target answers these from the session: `did:ng:` + `session.private_store_id | protected_store_id | public_store_id` (`Session`, `index.d.ts:264-272`). The store IS the container; the per-scope index document disappears.
|
- **`userStoreDoc(id, scope)` / `resolveScopeGraph(scope)` / `resolveWriteGraph(id, scope)` → level 2, VERIFIED.** The target answers these from the session: `did:ng:` + `session.private_store_id | protected_store_id | public_store_id` (`Session`, `index.d.ts:264-272`). The store IS the container; the per-scope index document disappears.
|
||||||
- **`userInbox(id)` → level 1, VERIFIED counterpart with a different granularity.** Upstream a user's inboxes are their public and protected STORE repos' inboxes — the only two `AddInboxCap` commits in the engine (`engine/verifier/src/site.rs:128,149`). An identity-level "my inbox" therefore maps to a store inbox; the resolution moves into the lib/SDK and the consumer's act (deposit to an address, process my own) is unchanged.
|
- **`userInbox(id)` → level 1, VERIFIED counterpart with a different granularity.** Upstream a user's inboxes are their public and protected STORE repos' inboxes — the only two `AddInboxCap` commits in the engine (`engine/verifier/src/site.rs:128,149`). An identity-level "my inbox" therefore maps to a store inbox; the resolution moves into the lib/SDK and the consumer's act (deposit to an address, process my own) is unchanged.
|
||||||
- **`openDocumentInbox(doc)` / `documentInboxAddress(doc)` → level 1, VERIFIED support, no exerciser.** Every `Repo` carries `inbox: Option<PrivKey>` (`engine/repo/src/repo.rs:126`); `AddInboxCapV0` is keyed by `repo_id` with no is-store restriction (`engine/repo/src/types.rs:1973`; applied at `engine/verifier/src/verifier.rs:1920-1928`); but no code path creates one for a plain document (`doc_create` leaves `inbox: None`, `repo.rs:574`) and no level-2/3 API exposes any of it. So: the *capability* is engine-verified; the *functions* are invented surface; and the **address publication is a real, deliberate divergence** (upstream transmits addresses, never publishes them — § 9), with the ownership guard compensating our design, not mirroring an upstream rule.
|
- **`openDocumentInbox(doc)` / `documentInboxAddress(doc)` → level 1, VERIFIED support, no exerciser.** Every `Repo` carries `inbox: Option<PrivKey>` (`engine/repo/src/repo.rs:126`); `AddInboxCapV0` is keyed by `repo_id` with no is-store restriction (`engine/repo/src/types.rs:1973`; applied at `engine/verifier/src/verifier.rs:1920-1928`); but no code path creates one for a plain document (`doc_create` → `new_repo_default` → `Store::create_repo_default` → `create_repo_with_keys`, which builds the `Repo` with `inbox: None` — `engine/verifier/src/verifier.rs:3004`, `engine/repo/src/store.rs:264,284,691`) and no level-2/3 API exposes any of it. So: the *capability* is engine-verified; the *functions* are invented surface; and the **address publication is a real, deliberate divergence** (upstream transmits addresses, never publishes them — § 9), with the ownership guard compensating our design, not mirroring an upstream rule.
|
||||||
- **`addLink(cap)` / `readLinks()` → level 1, VERIFIED model, no JS surface.** The emulated `AddLink { read_cap }` register (`engine/repo/src/types.rs:1939-1948` — *"so that a user can share with all its device a new Link they received"*, external repos only). Upstream this filing happens inside the verifier when it processes the inbox; the future SDK most likely never exposes these as calls, so consumers should not code against them (§ 15).
|
- **`addLink(cap)` / `readLinks()` → level 1, VERIFIED model, no JS surface.** The emulated `AddLink { read_cap }` register (`engine/repo/src/types.rs:1939-1948` — *"so that a user can share with all its device a new Link they received"*, external repos only). Upstream this filing happens inside the verifier when it processes the inbox; the future SDK most likely never exposes these as calls, so consumers should not code against them (§ 15).
|
||||||
- **`resolveAccount` / `ensureAccount` / `VirtualUserRecord` / `RegistrySession` / `reservedAccount` / `resetRegistryCache` → NO COUNTERPART.** The shared-wallet shim (accounts directory, pointer → doc-shim indirection) has no image in the target — the target has no central directory of identities (`docs/migration-guide.md` § 3). The whole group disappears with the shim.
|
- **`resolveAccount` / `ensureAccount` / `VirtualUserRecord` / `RegistrySession` / `reservedAccount` / `resetRegistryCache` → NO COUNTERPART.** The shared-wallet shim (accounts directory, pointer → doc-shim indirection) has no image in the target — the target has no central directory of identities (`docs/migration-guide.md` § 3). The whole group disappears with the shim.
|
||||||
- **`isOwnInbox` / `myInboxes` → NO COUNTERPART as API.** Upstream the question "which inboxes may I read" is answered inside the verifier by the User branch's `AddInboxCap` records; nothing suggests a JS API for it. These exist for the emulated read guard and the connection drain.
|
- **`isOwnInbox` / `myInboxes` → NO COUNTERPART as API.** Upstream the question "which inboxes may I read" is answered inside the verifier by the User branch's `AddInboxCap` records; nothing suggests a JS API for it. These exist for the emulated read guard and the connection drain.
|
||||||
@@ -583,7 +597,7 @@ declare function user_disconnect(user_id: string): Promise<void>;
|
|||||||
|
|
||||||
## 14. Type re-exports
|
## 14. Type re-exports
|
||||||
|
|
||||||
`@ng-eventually/sdk` re-exports, type-only (erased at build, `index.ts:48-50`):
|
`@ng-eventually/sdk` re-exports, type-only (erased at build, `src/index.ts`):
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
export type { ShapeType, BaseType, Schema } from "@ng-org/shex-orm";
|
export type { ShapeType, BaseType, Schema } from "@ng-org/shex-orm";
|
||||||
@@ -593,6 +607,25 @@ export type { NG } from "@ng-org/web";
|
|||||||
|
|
||||||
**PASSTHROUGH (levels 2/3, VERIFIED)** — `ShapeType`/`BaseType` at `@ng-org/shex-orm` `dist/types.d.ts:5,12` (installed 0.1.2-alpha.8); `NG` at `index.d.ts:136`. At migration these imports point at the same packages directly; nothing changes for the consumer.
|
**PASSTHROUGH (levels 2/3, VERIFIED)** — `ShapeType`/`BaseType` at `@ng-org/shex-orm` `dist/types.d.ts:5,12` (installed 0.1.2-alpha.8); `NG` at `index.d.ts:136`. At migration these imports point at the same packages directly; nothing changes for the consumer.
|
||||||
|
|
||||||
|
### The library's own model types — published by NAME since 2026-08-10
|
||||||
|
|
||||||
|
The entry used to say `export * from "./model/types"`, a blanket re-export publishing eight types in one gesture. It now names them, under one rule:
|
||||||
|
|
||||||
|
> **A type is published only if a PUBLISHED SIGNATURE uses it.**
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export type { Nuri, NuriLike, Scope, PrincipalId, NgLike, UseShapeLike } from "./model/types";
|
||||||
|
```
|
||||||
|
|
||||||
|
Each one's warrant: `Nuri` is what every reference-returning call returns and `NuriLike` what every entry accepts (§ 10, § 11); `Scope` types `storeRegistry.*` and `watchShape` (§ 12, § 5); `PrincipalId` is `ensureIdentity`'s return and a field of `Deposit`, `PostOptions` and `EventuallyConfig` (§ 2bis, § 9, § 1); `NgLike` and `UseShapeLike` type the two injected objects in `EventuallyConfig` (§ 1).
|
||||||
|
|
||||||
|
Two types the blanket export published are now internal, each because **nothing published names it**:
|
||||||
|
|
||||||
|
- **`ReadCap`** — no published signature takes or returns one. Its users are two private helpers of `surface/inbox.ts` (`capsSeenIn`, `capOfPayload`) and the emulated registers. Publishing it advertised a value a consumer has no published call to obtain, and deliberately so: `linkTo` was removed precisely for handing one out (§ 0 of `readcap-and-nuri-model.md`), and `mintCap` is unreachable from outside (§ 11). The only use a consumer could make of it is a cast — which is what the surface's permissive-in / precise-out design exists to make unnecessary.
|
||||||
|
- **`InboxScope`** — used only by `account-registry.userInbox(id, scope)`, unpublished since 2026-08-05 (§ 12). An application never handles an inbox address, so it never names an inbox scope.
|
||||||
|
|
||||||
|
Both remain **defined** in `model/types.ts` and are used throughout the library; only their publication changed. Nothing about the target motivates either removal — this is a statement about *this* surface, and the same test that pins the appendix pins it.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 15. Machinery on the surface — what a consumer should NOT code against
|
## 15. Machinery on the surface — what a consumer should NOT code against
|
||||||
@@ -628,7 +661,7 @@ Exported, but not SDK surface. Coding against these builds knowledge that migrat
|
|||||||
### `@ng-eventually/sdk` — `src/index.ts` (the only entry since 2026-08-07)
|
### `@ng-eventually/sdk` — `src/index.ts` (the only entry since 2026-08-07)
|
||||||
|
|
||||||
```text
|
```text
|
||||||
direct: BaseType, DeepSignalSet, DocChange, DocChangeType, EventuallyConfig, InboxScope, NG, NgLike, Nuri, NuriLike, PrincipalId, ReadCap, RegistrySession, Schema, Scope, ShapeObservable, ShapeQuery, ShapeType, SharedWalletConfig, UnionSubject, Unsubscribe, UseShapeLike, configure, docChangeType, ensureIdentity, init, initNg, ng, readUnion, subscribeDoc, subscribeDocs, useShape, watchShape
|
direct: BaseType, DeepSignalSet, DocChange, DocChangeType, EventuallyConfig, NG, NgLike, Nuri, NuriLike, PrincipalId, RegistrySession, Schema, Scope, ShapeObservable, ShapeQuery, ShapeType, SharedWalletConfig, UnionSubject, Unsubscribe, UseShapeLike, configure, docChangeType, ensureIdentity, init, initNg, ng, readUnion, subscribeDoc, subscribeDocs, useShape, watchShape
|
||||||
docs: docCreate, sparqlQuery, sparqlUpdate
|
docs: docCreate, sparqlQuery, sparqlUpdate
|
||||||
inbox: Deposit, PostOptions, materialize, post, postToDocument, processInbox, read, readForDocument, readSynced, share, watch
|
inbox: Deposit, PostOptions, materialize, post, postToDocument, processInbox, read, readForDocument, readSynced, share, watch
|
||||||
storeRegistry: createEntityDoc, listMyEntityDocs, openDocumentInbox, resolveScopeGraph, resolveWriteGraph
|
storeRegistry: createEntityDoc, listMyEntityDocs, openDocumentInbox, resolveScopeGraph, resolveWriteGraph
|
||||||
|
|||||||
@@ -70,11 +70,13 @@ The surface consequence: the *act* — obtain a link, circulate it — is the sa
|
|||||||
|
|
||||||
## 5. Recommendation for the polyfill
|
## 5. Recommendation for the polyfill
|
||||||
|
|
||||||
The surface already exists: `linkTo(doc: NuriLike): ReadCap` (`packages/sdk/src/surface/placement.ts:65-77`) for the traveling value, `inbox.share(doc, toUser)` (`packages/sdk/src/surface/inbox.ts:285`) for directed delivery. **Keep `linkTo` — the act is the right one** — with four adjustments:
|
**SUPERSEDED on its first point, 2026-08-06 — `linkTo` was REMOVED, and the removal stands.** This section used to open: *"The surface already exists: `linkTo(doc: NuriLike): ReadCap` … `inbox.share(doc, toUser)` for directed delivery. **Keep `linkTo` — the act is the right one**"*. What that recommendation missed is the single thing § 0 of `docs/readcap-and-nuri-model.md` exists to hold: a call that returns a document's **key** where a caller asked for its **reference** converts *"whoever has the reference AND the key reads"* into *"whoever has the reference reads"* — for that document and for every document it mentions — so confidentiality can no longer be composed inside anything one circulates. The *act* (circulate a value that opens a document) may still be right; making it the answer to "give me the link to my document" was not. The reasoning is recorded where the function was, in the `No linkTo here` block of `packages/sdk/src/surface/placement.ts`.
|
||||||
|
|
||||||
1. **Label it LEVEL-1 SHAPE in `docs/api-contract.md`.** What supports it: the `NgLink` family and its stated sharing flow, the `PermaShare` permission, the exercised object-URL and profile-QR precedents, and the PO doctrine that circulation is the only distribution. The model's own stated flows are unusable without *some* produce-a-link affordance, which is as much confidence as an unbuilt feature allows. What cannot be promised: the SDK's name for it, sync vs async (upstream link-building needs overlay + peers from the session, so async is plausible — same adapter-sized delta class as `subscribeDoc`'s sync unsubscribe), and whether the value is a NURI string or a structured link. Therefore: **the returned value is opaque**; a consumer that stores it, transmits it, and hands it back unmodified learns nothing to unlearn; a consumer that parses it does.
|
What an application does instead, today: it names a document with the bare reference it already holds — every published call returns one — and grants access with `inbox.share(doc, toUser)` (`packages/sdk/src/surface/inbox.ts`). The four points below are kept and re-read against that: 1 and 4 stand as written for **any** future link-producing surface; 2 is void with the function; 3 is unchanged and still open.
|
||||||
2. **Fix the comment-vs-code mismatch in `linkTo`.** The docstring claims *"A protected document's key never comes out this way — it goes through `share`"*; the code returns any held cap, with no scope check. The **code** is the model-true side: `RepoLinkV0`-with-key IS the protected-document link, and sharing it out-of-band is the documented normal case (`:5059`). Align the comment: a protected link carries the key and is legitimate to circulate — with the §4 durability caveat, not a prohibition.
|
|
||||||
3. **The recipient verb is missing.** Nothing exported ingests an out-of-band link: `learn` is reached only by inbox processing and the connection drain (`packages/sdk/src/surface/inbox.ts:410`, `packages/sdk/src/emulated-verifier/connect.ts:68`), and `getCaps()` is documented machinery (api-contract §15). The model names the recipient act precisely — open the link: load the repo from its read cap, file `AddLink` durably on the User branch, subscribe (`:5059`; `engine/repo/src/types.rs:1934-1950`; `verifier.rs:2237`). Suggested surface, same epistemic label as `linkTo`: `openLink(link: string): Promise<Nuri>` — files the cap in the emulated registers and returns the cap-less target for use in reads. Without it, path 2 has a producer and no consumer, and the multi-actor test where Bob *obtains* the document through calls (never through a shared variable) cannot be written — the exact failure mode `rules/engineering/multi-actor-tests-obtain-not-receive.md` records.
|
1. **Label any such call LEVEL-1 SHAPE in `docs/api-contract.md`.** What supports it: the `NgLink` family and its stated sharing flow, the `PermaShare` permission, the exercised object-URL and profile-QR precedents, and the PO doctrine that circulation is the only distribution. The model's own stated flows are unusable without *some* produce-a-link affordance, which is as much confidence as an unbuilt feature allows. What cannot be promised: the SDK's name for it, sync vs async (upstream link-building needs overlay + peers from the session, so async is plausible — same adapter-sized delta class as `subscribeDoc`'s sync unsubscribe), and whether the value is a NURI string or a structured link. Therefore: **the returned value is opaque**; a consumer that stores it, transmits it, and hands it back unmodified learns nothing to unlearn; a consumer that parses it does.
|
||||||
|
2. ~~**Fix the comment-vs-code mismatch in `linkTo`.**~~ **VOID — the function is gone.** The observation it rested on survives and is worth keeping: `RepoLinkV0`-with-key IS the protected-document link, and circulating it out-of-band is the documented normal case (`:5059`), so a protected link carrying its key is not in itself a violation — with the § 4 durability caveat. What made `linkTo` wrong was not that the value carried a key; it was that a caller got one **by asking for a reference**. Handing over a key must be its own act, which is what `inbox.share` is.
|
||||||
|
3. **The recipient verb is missing.** Nothing exported ingests an out-of-band link: `learn` is reached only by inbox processing and the connection drain (`packages/sdk/src/surface/inbox.ts:410`, `packages/sdk/src/emulated-verifier/connect.ts:68`), and `getCaps()` is documented machinery (api-contract §15). The model names the recipient act precisely — open the link: load the repo from its read cap, file `AddLink` durably on the User branch, subscribe (`:5059`; `engine/repo/src/types.rs:1934-1950`; `verifier.rs:2237`). Suggested surface, same epistemic label as point 1: `openLink(link: string): Promise<Nuri>` — files the cap in the emulated registers and returns the cap-less target for use in reads. Without it, path 2 has a producer and no consumer, and the multi-actor test where Bob *obtains* the document through calls (never through a shared variable) cannot be written — the exact failure mode `rules/engineering/multi-actor-tests-obtain-not-receive.md` records.
|
||||||
4. **Do not add**: link options (expiry, audience, revoke-this-link), per-reader introspection for public documents, or any API that parses or inspects a link's insides — nothing upstream supports any of them, and each teaches a lever the model does not have.
|
4. **Do not add**: link options (expiry, audience, revoke-this-link), per-reader introspection for public documents, or any API that parses or inspects a link's insides — nothing upstream supports any of them, and each teaches a lever the model does not have.
|
||||||
|
|
||||||
## 6. The question for the NextGraph developer
|
## 6. The question for the NextGraph developer
|
||||||
|
|||||||
@@ -24,21 +24,21 @@ Builds the published `ng` Proxy (consumed once, `index.ts:61`): forwards every p
|
|||||||
- **Defect — the `login` arm fabricates a member (see Findings F1).** `@ng-org/web` has no `login`: none among the exports of `index.d.ts` (re-verified), and no `fn login` in `sdk/js/lib-wasm/src/lib.rs`. The proxy nevertheless returns a function for `prop === "login"` (`ng-proxy.ts:16-22`), so `typeof ng.login === "function"` on the wrapper while the real SDK yields `undefined` — the one place the proxy adds a member, contradicting its own header and the surface contract's "adds no member and removes none" (§ 3). Calling it throws at runtime (`ng[prop]` is undefined). **ASSUMPTION with no provenance** — no target layer names a `login`.
|
- **Defect — the `login` arm fabricates a member (see Findings F1).** `@ng-org/web` has no `login`: none among the exports of `index.d.ts` (re-verified), and no `fn login` in `sdk/js/lib-wasm/src/lib.rs`. The proxy nevertheless returns a function for `prop === "login"` (`ng-proxy.ts:16-22`), so `typeof ng.login === "function"` on the wrapper while the real SDK yields `undefined` — the one place the proxy adds a member, contradicting its own header and the surface contract's "adds no member and removes none" (§ 3). Calling it throws at runtime (`ng[prop]` is undefined). **ASSUMPTION with no provenance** — no target layer names a `login`.
|
||||||
- Disappears at migration (the whole module).
|
- Disappears at migration (the whole module).
|
||||||
|
|
||||||
## 2. NURI internals — the unexported slice of `nuri.ts`
|
## 2. NURI internals — the unexported slice of `nuri.ts`, plus the minting point
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
// nuri.ts:73
|
// model/nuri.ts
|
||||||
export function targetOf(nuri: Nuri): Nuri;
|
export function targetOf(nuri: Nuri): Nuri;
|
||||||
// nuri.ts:82
|
|
||||||
export function parseNuri(nuri: Nuri): { target: Nuri; readCap?: ReadCap };
|
export function parseNuri(nuri: Nuri): { target: Nuri; readCap?: ReadCap };
|
||||||
// nuri.ts:116
|
// emulated-verifier/caps.ts — NOT `model/nuri.ts`; it moved when the source layout was
|
||||||
|
// reorganised by migration fate, and this block said `nuri.ts` until 2026-08-10.
|
||||||
export function mintCap(nuri: Nuri): ReadCap;
|
export function mintCap(nuri: Nuri): ReadCap;
|
||||||
```
|
```
|
||||||
|
|
||||||
`targetOf` strips a `:r:` cap segment to the naming form; `parseNuri` is the parsed pair; `mintCap` builds the cap-bearing form with the stand-in value `OK`. Kept off the surface deliberately: nothing published turns a bare reference into a cap.
|
`targetOf` strips a `:r:` cap segment to the naming form; `parseNuri` is the parsed pair; `mintCap` builds the cap-bearing form with the stand-in value `OK` (`STAND_IN_CAP`). Kept off the surface deliberately: nothing published turns a bare reference into a cap.
|
||||||
|
|
||||||
- `targetOf` / `parseNuri` — **LEVEL-1 SHAPE, model VERIFIED**: a 1:1 mirror of upstream's one-type-with-optional-access NURI. The ReadCap encoding they discriminate on is `r:{base64url(serde_bare(ObjectRef))}` (`BlockRef::readcap_nuri`, `engine/repo/src/types.rs:518-521`), distinct from the `:k:` object/commit forms (`object_nuri`/`commit_nuri`, `types.rs:510-514`). No JS surface parses NURIs at level 2 or 3 — the real SDK takes plain strings — so these helpers never surface in signatures and survive only as internals.
|
- `targetOf` / `parseNuri` — **LEVEL-1 SHAPE, model VERIFIED**: they transcribe upstream's one-type-with-optional-access NURI, on the **two** fields this library uses. Not a "1:1 mirror" of `NuriV0`, as this line claimed until 2026-08-10: that type has TEN fields — `identity, target, entire_store, objects, signature, branch, overlay, access, topic, locator` (`engine/net/src/app_protocol.rs:181-194`) — of which `parseNuri` carries `target` and the cap half of `access`. The other eight have no counterpart here (the missing `locator` is a stated gap, `docs/readcap-and-nuri-model.md` § 4sexies). The ReadCap encoding they discriminate on is `r:{base64url(serde_bare(ObjectRef))}` (`BlockRef::readcap_nuri`, `engine/repo/src/types.rs:518-521`), distinct from the `:k:` object/commit forms (`object_nuri`/`commit_nuri`, `types.rs:510-514`). No JS surface parses NURIs at level 2 or 3 — the real SDK takes plain strings — so these helpers never surface in signatures and survive only as internals.
|
||||||
- `mintCap` — **NO COUNTERPART as an operation, and that is the point**: upstream a ReadCap is produced by the engine when a repo is created, never derived from a bare reference by a caller. `mintCap` exists solely because the emulation needs a cap VALUE at creation time and P1b has not yet supplied real key material; the constant `OK` pretends nothing (`nuri.ts:87-103`). It has exactly two call sites (`shared-wallet/account-registry.ts` `createEntityDoc`; `emulated-verifier/caps.ts` internals) — the minting points of the emulation. At P1b the constant becomes a real key; at migration the function is deleted (the engine mints).
|
- `mintCap` — **NO COUNTERPART as an operation, and that is the point**: upstream a ReadCap is produced by the engine when a repo is created, never derived from a bare reference by a caller. `mintCap` exists solely because the emulation needs a cap VALUE at creation time and P1b has not yet supplied real key material; the constant `OK` pretends nothing (`STAND_IN_CAP`, `emulated-verifier/caps.ts`). It has exactly two call sites (`shared-wallet/account-registry.ts` `createEntityDoc`; `emulated-verifier/caps.ts` internals) — the minting points of the emulation. At P1b the constant becomes a real key; at migration the function is deleted (the engine mints).
|
||||||
|
|
||||||
## 3. The reach boundary — `emulated-verifier/reach.ts`
|
## 3. The reach boundary — `emulated-verifier/reach.ts`
|
||||||
|
|
||||||
@@ -266,6 +266,6 @@ export function inspectOutbox(): void;
|
|||||||
|
|
||||||
Fully internal modules: `shared-wallet/access-log.ts` (`AccessOp`, `setAccessLog`, `enabled`, `activeIdentity`, `accessLogPrefix`, `logStage`, `shortNuri`, `logAccess`); `emulated-verifier/machinery.ts` (`MACHINERY_NS`, `isMachinerySubject`); `surface/ng-proxy.ts` (`makeNg`); `emulated-verifier/open-repo.ts` (`SyncState`, `setOpenTimeoutForTests`, `resetOpenedRepos`, `getSyncState`, `ensureRepoOpen`, `ensurePhysicalRepoOpen`, `ensureReposOpen`); `shared-wallet/outbox-log.ts` (`inspectOutbox`); `shared-wallet/physical.ts` (`physicalCreate`, `physicalQuery`, `physicalUpdate`); `emulated-verifier/reach.ts` (`declareInfrastructure`, `isInfrastructure`, `resetInfrastructure`, `mayReach`, `assertMayReach`, `mustNotAttempt`); `emulated-verifier/read-filter.ts` (`filterReadable`, `makeReadFilteredView`).
|
Fully internal modules: `shared-wallet/access-log.ts` (`AccessOp`, `setAccessLog`, `enabled`, `activeIdentity`, `accessLogPrefix`, `logStage`, `shortNuri`, `logAccess`); `emulated-verifier/machinery.ts` (`MACHINERY_NS`, `isMachinerySubject`); `surface/ng-proxy.ts` (`makeNg`); `emulated-verifier/open-repo.ts` (`SyncState`, `setOpenTimeoutForTests`, `resetOpenedRepos`, `getSyncState`, `ensureRepoOpen`, `ensurePhysicalRepoOpen`, `ensureReposOpen`); `shared-wallet/outbox-log.ts` (`inspectOutbox`); `shared-wallet/physical.ts` (`physicalCreate`, `physicalQuery`, `physicalUpdate`); `emulated-verifier/reach.ts` (`declareInfrastructure`, `isInfrastructure`, `resetInfrastructure`, `mayReach`, `assertMayReach`, `mustNotAttempt`); `emulated-verifier/read-filter.ts` (`filterReadable`, `makeReadFilteredView`).
|
||||||
|
|
||||||
Internal slices of partially-published modules: `nuri.ts` (`targetOf`, `parseNuri`, `mintCap`); `emulated-verifier/connect.ts` (`startConnect`); `subscribe.ts` (`subscribePhysicalDoc`); `shared-wallet/account-registry.ts` (`reservedAccount`, `resetRegistryCache`, `resolveAccount`, `ensureAccount`, `userStoreDoc`, `isOwnInbox`, `myInboxes`, `addLink`, `readLinks`).
|
Internal slices of partially-published modules: `model/nuri.ts` (`targetOf`, `parseNuri`) and `emulated-verifier/caps.ts` (`mintCap`); `emulated-verifier/connect.ts` (`startConnect`); `subscribe.ts` (`subscribePhysicalDoc`); `shared-wallet/account-registry.ts` (`reservedAccount`, `resetRegistryCache`, `resolveAccount`, `ensureAccount`, `userStoreDoc`, `isOwnInbox`, `myInboxes`, `addLink`, `readLinks`).
|
||||||
|
|
||||||
Modules with no internal exports (everything they export is published): `types.ts`, `docs.ts`, `inbox.ts`, `surface/read-model.ts`, `shared-wallet/virtualUsers.ts`, `emulated-verifier/caps.ts`, `sparql.ts`, `lifecycle.ts`, `surface/use-shape.ts`, `surface/watch-shape.ts`, `surface/placement.ts`, and the entry point.
|
Modules with no internal exports (everything they export is published): `types.ts`, `docs.ts`, `inbox.ts`, `surface/read-model.ts`, `shared-wallet/virtualUsers.ts`, `emulated-verifier/caps.ts`, `sparql.ts`, `lifecycle.ts`, `surface/use-shape.ts`, `surface/watch-shape.ts`, `surface/placement.ts`, and the entry point.
|
||||||
|
|||||||
@@ -23,8 +23,11 @@ this step swaps the *emulated* key for the real one, not the model:
|
|||||||
(`readCap` on the store's Store branch, `link` on its User branch) become the real
|
(`readCap` on the store's Store branch, `link` on its User branch) become the real
|
||||||
`AddRepo` / `AddLink` commits. Remove the emulation; the wallet and the branches
|
`AddRepo` / `AddLink` commits. Remove the emulation; the wallet and the branches
|
||||||
already hold them.
|
already hold them.
|
||||||
- `nuri.ts`'s stand-in cap value — the constant `OK` — becomes the real
|
- the stand-in cap value — the constant `OK` (`STAND_IN_CAP`,
|
||||||
`r:{base64url(serde_bare(ObjectRef))}`. It is **one function** (`mintCap`), because
|
`emulated-verifier/caps.ts`) — becomes the real
|
||||||
|
`r:{base64url(serde_bare(ObjectRef))}`. It is **one function** (`mintCap`, in that
|
||||||
|
same module since the source layout was reorganised — this said `nuri.ts` until
|
||||||
|
2026-08-10), because
|
||||||
every path now READS a stored cap instead of recomputing one. `hasReadCap` /
|
every path now READS a stored cap instead of recomputing one. `hasReadCap` /
|
||||||
`targetOf` stay meaningful: the `r:` discriminant is upstream grammar, not ours.
|
`targetOf` stay meaningful: the `r:` discriminant is upstream grammar, not ours.
|
||||||
- `inbox.share(doc, toUser)` becomes the native sealed delivery (whatever the SDK ends up naming it — see the note below
|
- `inbox.share(doc, toUser)` becomes the native sealed delivery (whatever the SDK ends up naming it — see the note below
|
||||||
|
|||||||
@@ -123,10 +123,20 @@ offline"*; *"removing permissions … requires a SyncSignature"* (synchronous).
|
|||||||
**Only two repos have an inbox today: a user's public and protected STORES.** Not
|
**Only two repos have an inbox today: a user's public and protected STORES.** Not
|
||||||
documents, and not the private store. `new_store_default` attaches one solely `if
|
documents, and not the private store. `new_store_default` attaches one solely `if
|
||||||
!private` (`engine/verifier/src/verifier.rs:2994`), and `doc_create` goes through
|
!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
|
`new_repo_default` (`verifier.rs:3004`, called at `request_processor.rs:689`) →
|
||||||
|
`Store::create_repo_default` (`engine/repo/src/store.rs:264`) →
|
||||||
|
`create_repo_with_keys` (`store.rs:284`), which builds the `Repo` with `inbox: None`
|
||||||
|
(`store.rs:691`). The only
|
||||||
`AddInboxCap` commits in the whole engine are the two in `engine/verifier/src/site.rs:128,149`
|
`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.
|
— one for the public store repo, one for the protected one.
|
||||||
|
|
||||||
|
*(Citation corrected 2026-08-10. This pointed at `engine/repo/src/repo.rs:574`, which is
|
||||||
|
inside `Repo::new_with_member` (`repo.rs:543`) — a constructor no production path
|
||||||
|
reaches: its callers are `Repo::new_with_perms`, gated `#[cfg(any(test, feature =
|
||||||
|
"testing"))]` (`repo.rs:186-192`), and `#[cfg(test)]` blocks in `branch.rs:387,490` and
|
||||||
|
`commit.rs:1659,1849,1919`. The claim itself was right; it was being proved by a test
|
||||||
|
fixture.)*
|
||||||
|
|
||||||
**But the engine SUPPORTS an inbox on any repo — "does not" and "cannot" are different
|
**But the engine SUPPORTS an inbox on any repo — "does not" and "cannot" are different
|
||||||
statements.** `inbox: Option<PrivKey>` is a field of EVERY `Repo`
|
statements.** `inbox: Option<PrivKey>` is a field of EVERY `Repo`
|
||||||
(`engine/repo/src/repo.rs:126`), not of a store structure. `AddInboxCapV0` is keyed by
|
(`engine/repo/src/repo.rs:126`), not of a store structure. `AddInboxCapV0` is keyed by
|
||||||
|
|||||||
@@ -161,7 +161,7 @@ Consistent with the rest of the model: no role and no list, only "do you hold th
|
|||||||
- There is **no existence command at the SDK level**.
|
- There is **no existence command at the SDK level**.
|
||||||
- The only probe (`BlocksExist`) is **internal to the crate**, requires `BlockId`s **and** an already **loaded** repo, and addresses the **inner** overlay — which is derived from the **read secret**.
|
- The only probe (`BlocksExist`) is **internal to the crate**, requires `BlockId`s **and** an already **loaded** repo, and addresses the **inner** overlay — which is derived from the **read secret**.
|
||||||
- A cap-less reference carries a RepoId and the **outer** overlay: no `BlockId` to probe. And the outer is never registered anyway (`expose_outer` hard-coded to `false`, with no SDK parameter).
|
- A cap-less reference carries a RepoId and the **outer** overlay: no `BlockId` to probe. And the outer is never registered anyway (`expose_outer` hard-coded to `false`, with no SDK parameter).
|
||||||
- The only primitive accessible to a non-member (`ExtObjectGet`) requires the ObjectIds **and their keys**.
|
- The primitive a non-member can reach (`ExtObjectGet`) requires the **ObjectIds**, which one only holds once one can already read. *(Corrected 2026-08-10 — this line used to say "the ObjectIds **and their keys**", and to call `ExtObjectGet` the **only** such primitive. Both are wrong at the source: `ExtObjectGetV0 { overlay, ids: Vec<ObjectId>, include_files }` has **no key field** at all (`engine/net/src/types.rs:4492-4501`), and `ExtRequestContentV0` has **three** variants — `WalletGetExport`, `ExtObjectGet`, `ExtTopicSyncReq` (`:4520-4526`), the last of which falls into `unimplemented!()` (`:4533`). The conclusion is unchanged and rests on addressing, not on keys: blocks come back **encrypted**, and naming them needs ObjectIds a non-holder does not have — the formulation `docs/nextgraph-current-state.md` § "The `Ext` protocol serves blocks with no control" already carried.)*
|
||||||
|
|
||||||
> **Addressing itself presupposes the cap.** Proving a document's existence without holding its key is not constructible today, and nothing indicates that it is planned.
|
> **Addressing itself presupposes the cap.** Proving a document's existence without holding its key is not constructible today, and nothing indicates that it is planned.
|
||||||
|
|
||||||
@@ -258,16 +258,18 @@ A **wallet is only a keyring**. What we have been calling a "virtual user" is, u
|
|||||||
|
|
||||||
### Giving access is a **Link** — one word, three places, all already named
|
### Giving access is a **Link** — one word, three places, all already named
|
||||||
|
|
||||||
**VERIFIED 2026-07-30.** The delivery message, the register and the record all exist upstream under the same word, which is what a shape being real looks like:
|
**VERIFIED 2026-07-30, state column corrected 2026-08-10.** The delivery message, the register and the record all exist upstream under the same word, which is what a shape being real looks like. What none of them is, is *implemented* — the table said so of three rows, and re-reading the source refuted it:
|
||||||
|
|
||||||
| Step | Upstream | State |
|
| Step | Upstream | State |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| The message deposited in the recipient's inbox | `InboxMsgContent::Link` (`engine/net/src/types.rs:4249-4261`) | **declared, payload-less** — a variant with no fields, i.e. specified and not implemented |
|
| The message deposited in the recipient's inbox | `InboxMsgContent::Link` (`engine/net/src/types.rs:4249-4261`) | **declared, payload-less** — a variant with no fields, i.e. specified and not implemented |
|
||||||
| Where the recipient files it on processing | `AddLink { read_cap }` on the **User branch** of the private store (`engine/repo/src/types.rs:1934-1950`) | implemented (verifier arm `commits/mod.rs:681`) |
|
| Where the recipient files it on processing | `AddLink { read_cap }` on the **User branch** of the private store (`engine/repo/src/types.rs:1934-1950`) | **declared, stubbed** — the verifier arm is a no-op `Ok(())` (`engine/verifier/src/commits/mod.rs:681-693`), and nothing in the workspace constructs one |
|
||||||
| Withdrawing it | `RemoveLink`, ORset (`engine/repo/src/types.rs:1952`) | implemented |
|
| Withdrawing it | `RemoveLink`, ORset (`engine/repo/src/types.rs:1952`) | **declared, stubbed** — the same no-op `Ok(())` arm (`commits/mod.rs:695-707`) |
|
||||||
| What circulates | `RepoLinkV0 { read_cap, … }` (`engine/net/src/types.rs:5062`) | implemented |
|
| What circulates | `RepoLinkV0 { read_cap, … }` (`engine/net/src/types.rs:5062-5078`) | **declared only** — zero constructors and zero consumers in the workspace (only the `RepoLink` wrapper and its two accessors, `:5082-5097`) |
|
||||||
|
|
||||||
So: **deposit a Link into the recipient's inbox; on connection the recipient processes the inbox and files it with `AddLink` on their User branch.** That is the whole gesture, and every piece of it has a name.
|
**What makes "stub" the right word rather than a quibble**: the neighbouring arm in the same file does real work. `CommitVerifier for AddRepo` calls `load_repo_from_read_cap` then `add_doc` (`commits/mod.rs:644-664`); `AddLink` and `RemoveLink`, twenty lines below, return `Ok(())` with `#[allow(unused_variables)]` on every parameter. Same trait, same file, opposite states — so "there is a verifier arm" cannot be read as "the register works".
|
||||||
|
|
||||||
|
So: **deposit a Link into the recipient's inbox; on connection the recipient processes the inbox and files it with `AddLink` on their User branch.** That is the whole gesture, and every piece of it has a **name** — which is not the same as having a behaviour. Per this document's own rule, none of that says what the target will do; it says the gesture is fully specified and none of it runs.
|
||||||
|
|
||||||
Two consequences worth stating, because both are easy to get wrong:
|
Two consequences worth stating, because both are easy to get wrong:
|
||||||
|
|
||||||
@@ -286,23 +288,25 @@ There IS a register, and it is a fourth commit type next to `AddRepo`:
|
|||||||
pub struct AddLinkV0 { pub read_cap: ReadCap, /* … */ }
|
pub struct AddLinkV0 { pub read_cap: ReadCap, /* … */ }
|
||||||
```
|
```
|
||||||
|
|
||||||
`engine/repo/src/types.rs:1934-1950`, with `RemoveLink` as its ORset counterpart (`:1952`) and a verifier arm at `engine/verifier/src/commits/mod.rs:681`. So:
|
`engine/repo/src/types.rs:1934-1950`, with `RemoveLink` as its ORset counterpart (`:1952`) and a verifier arm — a no-op `Ok(())` one — at `engine/verifier/src/commits/mod.rs:681-693`. So:
|
||||||
|
|
||||||
- it lives on the **User branch** — created only on the **private store** (`engine/repo/src/store.rs:448-452`; the public/protected stores get an `Overlay` branch instead), which also carries `AddInboxCap { repo_id, overlay, priv_key }` — *"so that a user can share with all its device"* (`engine/repo/src/types.rs:1969-1981`). So the User branch answers two questions with one mechanism: **which caps I received**, and **which inboxes I may read**;
|
- it lives on the **User branch** — created only on the **private store** (`engine/repo/src/store.rs:448-452`; the public/protected stores get an `Overlay` branch instead), which also carries `AddInboxCap { repo_id, overlay, priv_key }` — *"so that a user can share with all its device"* (`engine/repo/src/types.rs:1969-1981`). So the User branch answers two questions with one mechanism: **which caps I received**, and **which inboxes I may read**;
|
||||||
- it is explicitly for **external repos** — someone else's documents, exactly the received-cap case;
|
- it is explicitly for **external repos** — someone else's documents, exactly the received-cap case;
|
||||||
- and its stated purpose is to **share the link with all of the user's devices**. It is wallet-resident and cross-device, not a local cache.
|
- and its stated purpose is to **share the link with all of the user's devices**. It is wallet-resident and cross-device, not a local cache.
|
||||||
|
|
||||||
**Level 3 (local user storage) is therefore a cache, not the register.** The register is level 2': `AddLink` on the User branch of the private store.
|
**Level 3 (local user storage) is therefore a cache, not the register.** The register — the durable, cross-device record — is level 2': `AddLink` on the User branch of the private store.
|
||||||
|
|
||||||
What remains true, and is a separate matter — the *delivery* path is unimplemented:
|
**But "the register exists, only the delivery is missing" is FALSE, and this section said it until 2026-08-10.** Both ends are declared and stubbed, as the table above now records: nothing constructs an `AddLink` commit anywhere in the workspace, and the arm that would apply one returns `Ok(())`. So the corrected statement is: `AddLink` on the User branch is **where a received cap belongs in the model** — a placement the source states unambiguously and this library aligns on — and no part of the gesture runs today, neither the road nor the destination. Per the design principle that gap says nothing about the target; it forbids only calling the register "implemented".
|
||||||
|
|
||||||
- `InboxMsgContent::ContactDetails` processing (`engine/verifier/src/inbox_processor.rs:778-847`) creates a contact document holding the profile, inbox, name and email — and **never reads `details.read_cap`**. Confirmed on sight: the receiver discards it. So the cap never reaches the User branch today — the register exists, the road to it does not.
|
The delivery half, in detail:
|
||||||
|
|
||||||
|
- `InboxMsgContent::ContactDetails` processing (`engine/verifier/src/inbox_processor.rs:778-847`) creates a contact document holding the profile, inbox, name and email — and **never reads `details.read_cap`**. Confirmed on sight: the receiver discards it. So no cap reaches the User branch today.
|
||||||
- `RepoLinkV0` states the intended flow (`engine/net/src/types.rs:5055-5061`): *"the link is shared and then the recipient opens it and subscribes soon afterward"*. **The key IS kept**: opening the repo persists its `read_cap` in local user storage, so the next session decrypts fine. What is not durable is the key's **validity** — a `RootCapRefresh` (§3) mints a new one, and receiving it depends on **the rotating party choosing to send it to you** (§3's DIRECTION block), not on any subscription state.
|
- `RepoLinkV0` states the intended flow (`engine/net/src/types.rs:5055-5061`): *"the link is shared and then the recipient opens it and subscribes soon afterward"*. **The key IS kept**: opening the repo persists its `read_cap` in local user storage, so the next session decrypts fine. What is not durable is the key's **validity** — a `RootCapRefresh` (§3) mints a new one, and receiving it depends on **the rotating party choosing to send it to you** (§3's DIRECTION block), not on any subscription state.
|
||||||
|
|
||||||
> **Do not write "only a subscriber receives the new key".** That reads the `RepoLinkV0` comment as intent, which §3 already forbids. **Subscribing is a purely LOCAL act** — automatic pull of changes — and the other party records nothing about it; there is no subscriber list to send to. Who gets a rotated key is the rotating party's decision, delivered to an inbox.
|
> **Do not write "only a subscriber receives the new key".** That reads the `RepoLinkV0` comment as intent, which §3 already forbids. **Subscribing is a purely LOCAL act** — automatic pull of changes — and the other party records nothing about it; there is no subscriber list to send to. Who gets a rotated key is the rotating party's decision, delivered to an inbox.
|
||||||
- `PermaCap` — still a **TODO** (`engine/repo/src/types.rs:578`) — covers exactly the gap that leaves: a link *"stored on disk and kept there unopened for a long period"*, i.e. never loaded, therefore never subscribed, therefore missing every refresh.
|
- `PermaCap` — still a **TODO** (`engine/repo/src/types.rs:578`) — covers exactly the gap that leaves: a link *"stored on disk and kept there unopened for a long period"*, i.e. never loaded, therefore never subscribed, therefore missing every refresh.
|
||||||
|
|
||||||
> **So there are TWO registers, by origin**: `AddRepo` on the **Store** branch for the documents a user creates in that store, and `AddLink` on the **User** branch of the private store for caps received for someone else's documents. Local user storage caches both. Opening a repo persists its cap locally, but that is the cache filling — not the durable record.
|
> **So there are TWO registers, by origin**: `AddRepo` on the **Store** branch for the documents a user creates in that store, and `AddLink` on the **User** branch of the private store for caps received for someone else's documents. (`AddRepo` runs; `AddLink` is declared and stubbed — see the table above. The *split by origin* is the model's, whatever each half's state.) Local user storage caches both. Opening a repo persists its cap locally, but that is the cache filling — not the durable record.
|
||||||
|
|
||||||
*Consequence for this library*: **both durable registers are now emulated** (2026-07-30) — `AddRepo` as a `shim:readCap` record on a distinct subject of the store document (`storeBranch`), `AddLink` as `shim:link` on another (`userBranch`) — and the in-memory `CapRegistry` is what it always was, level 3: the cache. Caps are READ back from those records, never recomputed. What stays an invention is representing branches as RDF subjects at all: upstream both branches carry `BranchCrdt::None` and hold service commits, not triples. What is faithful is that the key sits beside the document, and that the listing (`contains`, the Main branch) is separate from the keys.
|
*Consequence for this library*: **both durable registers are now emulated** (2026-07-30) — `AddRepo` as a `shim:readCap` record on a distinct subject of the store document (`storeBranch`), `AddLink` as `shim:link` on another (`userBranch`) — and the in-memory `CapRegistry` is what it always was, level 3: the cache. Caps are READ back from those records, never recomputed. What stays an invention is representing branches as RDF subjects at all: upstream both branches carry `BranchCrdt::None` and hold service commits, not triples. What is faithful is that the key sits beside the document, and that the listing (`contains`, the Main branch) is separate from the keys.
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -20,7 +20,7 @@ Without a minimum of crypto simulation, damaging shortcuts get taken (reading th
|
|||||||
|
|
||||||
Concretely, **in the target**: a document's data is **stored encrypted** (per-doc symmetric encryption, however lightweight); the **ReadCap = the key**; without it, **decrypting/reading is impossible**. No ACL, no plaintext accessible "on the side". Obtaining read access = **holding the key**, exactly as in the target model.
|
Concretely, **in the target**: a document's data is **stored encrypted** (per-doc symmetric encryption, however lightweight); the **ReadCap = the key**; without it, **decrypting/reading is impossible**. No ACL, no plaintext accessible "on the side". Obtaining read access = **holding the key**, exactly as in the target model.
|
||||||
|
|
||||||
> **Not yet true here, and saying so matters.** The shape is in place — possession decides, every access is confined to the connected virtual user, caps are stored and read back — but the cap value is the constant `OK` and nothing is encrypted. Per-document encryption is **P1b**, and it is one function (`nuri.ts` `mintCap`). Until it lands, nothing this library does may be described as anonymous or private.
|
> **Not yet true here, and saying so matters.** The shape is in place — possession decides, every access is confined to the connected virtual user, caps are stored and read back — but the cap value is the constant `OK` and nothing is encrypted. Per-document encryption is **P1b**, and it is one function (`mintCap`, in `emulated-verifier/caps.ts` — this said `nuri.ts` until 2026-08-10). Until it lands, nothing this library does may be described as anonymous or private.
|
||||||
|
|
||||||
## Shape consequences (to respect everywhere)
|
## Shape consequences (to respect everywhere)
|
||||||
|
|
||||||
|
|||||||
@@ -432,12 +432,22 @@ export async function readLinks(): Promise<ReadCap[]> {
|
|||||||
* construction, and at any time (see the User-branch note above).
|
* construction, and at any time (see the User-branch note above).
|
||||||
*
|
*
|
||||||
* What is true is narrower: no code path CREATES one for a document — `new_store_default`
|
* 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`
|
* 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
|
* 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
|
* 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
|
* capability exists and is simply unexposed above level 1: this function is aligned on
|
||||||
* the engine's model, it does not bet past it.
|
* the engine's model, it does not bet past it.
|
||||||
*
|
*
|
||||||
|
* *(The `inbox: None` claim is true; its citation was wrong until 2026-08-10. It pointed
|
||||||
|
* at `repo.rs:574`, inside `Repo::new_with_member` (`engine/repo/src/repo.rs:543`) —
|
||||||
|
* a constructor reached only from `Repo::new_with_perms`, itself gated
|
||||||
|
* `#[cfg(any(test, feature = "testing"))]` (`repo.rs:186-192`), and from `#[cfg(test)]`
|
||||||
|
* blocks (`branch.rs:387,490`; `commit.rs:1659,1849,1919`). The PRODUCTION path is
|
||||||
|
* `doc_create` → `Verifier::new_repo_default` (`engine/verifier/src/verifier.rs:3004`,
|
||||||
|
* called at `request_processor.rs:689`) → `Store::create_repo_default`
|
||||||
|
* (`engine/repo/src/store.rs:264`) → `create_repo_with_keys` (`store.rs:284`), which
|
||||||
|
* builds the `Repo` with `inbox: None` at `store.rs:691`.)*
|
||||||
|
*
|
||||||
* Lazy on purpose, for the same reason: creating an inbox document for every entity up
|
* 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
|
* 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
|
* anything in. Upstream the keypair is cheap; here an inbox is a document, so it is
|
||||||
@@ -472,9 +482,18 @@ export async function openDocumentInbox(docLike: NuriLike): Promise<Nuri> {
|
|||||||
// false: that commit lands on the committer's OWN User branch, so anyone may write
|
// false: that commit lands on the committer's OWN User branch, so anyone may write
|
||||||
// one naming anyone's repo. What protects upstream is that an inbox address is never
|
// one naming anyone's repo. What protects upstream is that an inbox address is never
|
||||||
// PUBLISHED — it is TRANSMITTED (in a `ContactDetails` message, or a profile QR
|
// PUBLISHED — it is TRANSMITTED (in a `ContactDetails` message, or a profile QR
|
||||||
// code), and `inboxes: PubKey → RepoId` is a per-verifier local table
|
// code), and `inboxes: PubKey → RepoId` is a table of the VERIFIER
|
||||||
// (`engine/verifier/src/verifier.rs:105`, rebuilt empty each session). A forged pair
|
// (`engine/verifier/src/verifier.rs:105`) — one per user. A forged pair reaches
|
||||||
// reaches nobody, because nobody was told about it.
|
// nobody because it only ever lands in the forger's OWN table; nobody else was told.
|
||||||
|
//
|
||||||
|
// The motive matters, and it was wrong here until 2026-08-10: this comment said the
|
||||||
|
// table is "rebuilt empty each session", which is not what the source does. It is
|
||||||
|
// initialized empty at construction (`:520`, `:2820`) and then REPOPULATED at every
|
||||||
|
// load — `Verifier::load` (`:534-566`) → `add_repo_without_saving` (`:2871`) →
|
||||||
|
// `add_repo_` (`:2887`), which re-inserts `repo.inbox.to_pub() → repo.id` for each
|
||||||
|
// repo it reloads — and the inbox private key itself is persisted per repo
|
||||||
|
// (`INBOX_CAP`, `engine/verifier/src/user_storage/repo.rs:61,171,207,362`). So the
|
||||||
|
// knowledge is durable; what it is not is SHARED. Per-verifier, not ephemeral.
|
||||||
//
|
//
|
||||||
// We publish instead of transmitting — the only way a third party can find the
|
// We publish instead of transmitting — the only way a third party can find the
|
||||||
// address at all here — which creates a vector upstream does not have: whoever can
|
// address at all here — which creates a vector upstream does not have: whoever can
|
||||||
|
|||||||
@@ -1,26 +1,44 @@
|
|||||||
/**
|
/**
|
||||||
* public-store — a document in a PUBLIC store gives up its ReadCap to whoever asks.
|
* public-store — a document in a PUBLIC store gives up its ReadCap to whoever asks.
|
||||||
*
|
*
|
||||||
* ── The upstream mechanism this emulates (VERIFIED) ───────────────────────
|
* ── The upstream mechanism this emulates — a DECLARED model, so a BET ──────
|
||||||
* `PublicRepoLinkV0` (`engine/net/src/types.rs:5098-5124`) carries `repo`,
|
* **Labelled VERIFIED until 2026-08-10, wrongly.** What supports it is a doc COMMENT
|
||||||
* `public_store` and `peers` — and **no `read_cap`**. Its own doc comment says why:
|
* on a type nothing constructs — a statement of intent, not of behaviour — and this
|
||||||
|
* repo's own rules say both halves of that: a comment describing the current state is
|
||||||
|
* not the intent, and an absent implementation is not evidence either. So this is a
|
||||||
|
* bet, and `docs/document-links.md` § 5 and `docs/readcap-and-nuri-model.md` § 4sexies
|
||||||
|
* already called it one. This header now says the same word.
|
||||||
|
*
|
||||||
|
* What IS read in source: `PublicRepoLinkV0` (`engine/net/src/types.rs:5098-5124`)
|
||||||
|
* carries `repo`, `public_store` and `peers` — and **no `read_cap`**. Its own doc
|
||||||
|
* comment says why:
|
||||||
*
|
*
|
||||||
* > *"The latest ReadCap of the branch (or main branch) will be **downloaded from
|
* > *"The latest ReadCap of the branch (or main branch) will be **downloaded from
|
||||||
* > the outerOverlay**, if the peer brokers listed below allow it. […] This link is
|
* > the outerOverlay**, **if the peer brokers listed below allow it**. […] This link
|
||||||
* > durable, because the public site are **served differently by brokers**."*
|
* > is durable, because the public site are **served differently by brokers**."*
|
||||||
*
|
*
|
||||||
* So for a repo in a public store, the key is not something a sender hands over: it is
|
* So for a repo in a public store, the key is not something a sender hands over: it is
|
||||||
* something the **network gives to anyone who asks**. The broker decides, by pinning
|
* something the **network gives to whoever asks — and whom the peer brokers allow**.
|
||||||
* the outer overlay (`expose_outer`, `engine/broker/src/server_storage/core/overlay.rs:103-133`).
|
* That condition is part of the mechanism, not decoration: the broker decides, by
|
||||||
* That is the whole of the property — nothing about the reader, everything about where
|
* pinning the outer overlay (`expose_outer`, `engine/broker/src/server_storage/core/overlay.rs:103-133`).
|
||||||
* the document sits and how brokers serve it.
|
* Nothing about the reader; everything about where the document sits and how brokers
|
||||||
|
* serve it.
|
||||||
|
*
|
||||||
|
* And what is NOT wired, which is precisely why this is a bet: both `PinRepo`
|
||||||
|
* constructors hard-code `expose_outer: false`
|
||||||
|
* (`engine/net/src/actors/client/pin_repo.rs:51,79`), so no client ever asks for the
|
||||||
|
* exposure; and `ExtTopicSyncReq` — the anonymous branch-sync such a link needs — is
|
||||||
|
* declared and falls into `unimplemented!()` (`engine/net/src/types.rs:4523,4533`).
|
||||||
|
* The emulation follows the model the engine DECLARES, in a place the engine does not
|
||||||
|
* yet serve. That is this library's intended posture, named here as the bet it is.
|
||||||
*
|
*
|
||||||
* ── What that means for the model, and why nothing is special-cased ───────
|
* ── What that means for the model, and why nothing is special-cased ───────
|
||||||
* Possession stays the ONE criterion. A public document is readable not because the
|
* Possession stays the ONE criterion. A public document is readable not because the
|
||||||
* guard makes an exception for it, but because its cap is **obtainable**: you ask, you
|
* guard makes an exception for it, but because its cap is **obtainable**: you ask, you
|
||||||
* receive, you hold it, and from there the ordinary path applies. `reach.ts` is
|
* receive, you hold it, and from there the ordinary path applies. `reach.ts` is
|
||||||
* untouched, and "whoever has the reference AND the key reads" still describes
|
* untouched, and "whoever has the reference AND the key reads" still describes
|
||||||
* everything — a public store simply hands the key to whoever has the reference.
|
* everything — a public store hands the key to whoever has the reference, where the
|
||||||
|
* brokers serving that store allow it (see the condition above).
|
||||||
*
|
*
|
||||||
* The consequence an application must be able to rely on: **a bare reference to a
|
* The consequence an application must be able to rely on: **a bare reference to a
|
||||||
* document in a public store is enough**, and that is why nothing in this library
|
* document in a public store is enough**, and that is why nothing in this library
|
||||||
|
|||||||
@@ -46,7 +46,23 @@
|
|||||||
// At migration the build alias is removed and these resolve to the real SDK. The
|
// At migration the build alias is removed and these resolve to the real SDK. The
|
||||||
// per-symbol ruling, with its epistemic label, is in `docs/api-contract.md`.
|
// per-symbol ruling, with its epistemic label, is in `docs/api-contract.md`.
|
||||||
|
|
||||||
export * from "./model/types";
|
// A type is published only when a PUBLISHED SIGNATURE uses it. `export *` published
|
||||||
|
// eight in one gesture (2026-08-10: it was a blanket re-export), of which two named
|
||||||
|
// nothing a consumer can reach — `ReadCap` (used only by two private helpers of
|
||||||
|
// `surface/inbox.ts`) and `InboxScope` (used only by the unpublished
|
||||||
|
// `account-registry.userInbox`). A published type with no published signature is a
|
||||||
|
// promise about the target that nothing here keeps: it invites a consumer to hold a
|
||||||
|
// value it has no call to obtain — and for `ReadCap`, the one value the model says a
|
||||||
|
// caller must never be handed on request. They stay DEFINED in `model/types.ts`, where
|
||||||
|
// the library uses them; they stop being surface. `docs/api-contract.md` § 10, § 14.
|
||||||
|
// Each one, and the signature that earns it its place:
|
||||||
|
// Nuri every reference the surface RETURNS
|
||||||
|
// NuriLike every reference the surface ACCEPTS
|
||||||
|
// Scope `storeRegistry.*`, `watchShape`
|
||||||
|
// PrincipalId `ensureIdentity`, `inbox.Deposit`/`PostOptions`, `EventuallyConfig`
|
||||||
|
// NgLike `EventuallyConfig.ng`
|
||||||
|
// UseShapeLike `EventuallyConfig.useShape`
|
||||||
|
export type { Nuri, NuriLike, Scope, PrincipalId, NgLike, UseShapeLike } from "./model/types";
|
||||||
export { useShape } from "./surface/use-shape";
|
export { useShape } from "./surface/use-shape";
|
||||||
export { watchShape } from "./surface/watch-shape";
|
export { watchShape } from "./surface/watch-shape";
|
||||||
export type { ShapeQuery, ShapeObservable } from "./surface/watch-shape";
|
export type { ShapeQuery, ShapeObservable } from "./surface/watch-shape";
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
/**
|
/**
|
||||||
* NURI primitives — the cap-less / cap-bearing distinction, kept as ONE object.
|
* NURI primitives — the cap-less / cap-bearing distinction, kept as ONE object.
|
||||||
*
|
*
|
||||||
* Upstream a NURI is a single type, `NuriV0 { target, access }`: a cap-less NURI
|
* Upstream a NURI is a single type, `NuriV0` — TEN fields: `identity, target,
|
||||||
* simply has an empty `access`. `did:ng:` is the URI SCHEME prefix (inboxes,
|
* entire_store, objects, signature, branch, overlay, access, topic, locator`
|
||||||
* branches and overlays all carry it) — it does NOT mean "without cap". The
|
* (`engine/net/src/app_protocol.rs:181-194`) — and a cap-less NURI is simply one
|
||||||
* discriminant is the `:r:` segment:
|
* whose `access` is empty. This module transcribes **two** of those ten (`target`,
|
||||||
|
* and the cap half of `access`); the other eight have no counterpart here.
|
||||||
|
* `did:ng:` is the URI SCHEME prefix (inboxes, branches and overlays all carry it) —
|
||||||
|
* it does NOT mean "without cap". The discriminant is the `:r:` segment:
|
||||||
*
|
*
|
||||||
* did:ng:o:{doc}:v:{overlay} — names, does NOT read (a {@link Nuri})
|
* did:ng:o:{doc}:v:{overlay} — names, does NOT read (a {@link Nuri})
|
||||||
* did:ng:o:{doc}:v:{overlay}:r:{cap} — names AND reads (a {@link ReadCap})
|
* did:ng:o:{doc}:v:{overlay}:r:{cap} — names AND reads (a {@link ReadCap})
|
||||||
@@ -19,9 +22,11 @@
|
|||||||
* 2026-07-30; it was the wrong letter *and* the wrong structure.
|
* 2026-07-30; it was the wrong letter *and* the wrong structure.
|
||||||
*
|
*
|
||||||
* These helpers are INTERNAL to the library. The parsed form {@link parseNuri}
|
* These helpers are INTERNAL to the library. The parsed form {@link parseNuri}
|
||||||
* mirrors `NuriV0 { target, access }` 1:1 but never surfaces in the SDK-identical
|
* mirrors that PAIR — `target` and the cap — and not the type: it was described as a
|
||||||
* entry's signatures — the real SDK takes plain `String`s and enforces at runtime,
|
* "1:1 mirror of `NuriV0`" until 2026-08-10, which claimed eight fields it has never
|
||||||
* through cryptography, so no branded type and no parsed struct leaks outward.
|
* carried. It never surfaces in the SDK-identical entry's signatures either — the
|
||||||
|
* real SDK takes plain `String`s and enforces at runtime, through cryptography, so no
|
||||||
|
* branded type and no parsed struct leaks outward.
|
||||||
*
|
*
|
||||||
* ── The stand-in key (deliberately NOT a secret) ───────────────────────────
|
* ── The stand-in key (deliberately NOT a secret) ───────────────────────────
|
||||||
* This library is deliberately insecure (see docs/vision.md). The only question it
|
* This library is deliberately insecure (see docs/vision.md). The only question it
|
||||||
@@ -80,8 +85,9 @@ export function targetOf(nuri: Nuri): Nuri {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The parsed form — a 1:1 mirror of upstream `NuriV0 { target, access }`, where a
|
* The parsed form — upstream `NuriV0`'s `target` plus the cap half of its `access`,
|
||||||
* cap-less NURI has no `readCap`. Library-internal (see the module header).
|
* and none of the type's eight other fields; a cap-less NURI has no `readCap`.
|
||||||
|
* Library-internal (see the module header).
|
||||||
*/
|
*/
|
||||||
export function parseNuri(nuri: Nuri): { target: Nuri; readCap?: ReadCap } {
|
export function parseNuri(nuri: Nuri): { target: Nuri; readCap?: ReadCap } {
|
||||||
return hasReadCap(nuri) ? { target: targetOf(nuri), readCap: nuri } : { target: nuri };
|
return hasReadCap(nuri) ? { target: targetOf(nuri), readCap: nuri } : { target: nuri };
|
||||||
|
|||||||
@@ -292,9 +292,13 @@ function capOfPayload(payload: unknown): ReadCap | null {
|
|||||||
* - the field exists, `ContactDetails.read_cap: Option<ReadCap>`
|
* - the field exists, `ContactDetails.read_cap: Option<ReadCap>`
|
||||||
* (`engine/net/src/types.rs:4233`), but building a message that carries one is
|
* (`engine/net/src/types.rs:4233`), but building a message that carries one is
|
||||||
* `read_cap: if with_readcap { unimplemented!() }` (`types.rs:3786`);
|
* `read_cap: if with_readcap { unimplemented!() }` (`types.rs:3786`);
|
||||||
* - and the receiver ignores it: `InboxMsgContent::ContactDetails` writes only
|
* - and the receiver ignores it: `InboxMsgContent::ContactDetails` creates a fresh
|
||||||
* `ng:site`/`ng:protected` + `ng:*_inbox` into a fresh contact document
|
* contact document and writes `ng:site`/`ng:protected` + `ng:*_inbox`, a
|
||||||
* (`engine/verifier/src/inbox_processor.rs:778-830`), never `details.read_cap`.
|
* `vcard:Individual` type, a `vcard:fn` name and an optional `vcard:hasEmail`,
|
||||||
|
* then sets the header title (`engine/verifier/src/inbox_processor.rs:778-845`) —
|
||||||
|
* but never `details.read_cap`. *(The list was "only the two `ng:` predicates"
|
||||||
|
* until 2026-08-10, which understated what the arm writes; the load-bearing part
|
||||||
|
* is the omission, not the length of the list.)*
|
||||||
*
|
*
|
||||||
* Do NOT read `InboxMsgContent::Link` as the intended channel either: it is a **unit
|
* Do NOT read `InboxMsgContent::Link` as the intended channel either: it is a **unit
|
||||||
* variant carrying nothing** (`engine/net/src/types.rs:4251`).
|
* variant carrying nothing** (`engine/net/src/types.rs:4251`).
|
||||||
|
|||||||
Reference in New Issue
Block a user