docs: dire partout que la lecture par lot n'existe plus

Le contrat publie et l'api-contract etaient traites avec le retrait lui-meme ;
restaient les documents qui decrivaient readUnion comme la primitive de
lecture du paquet, ou qui nommaient le module disparu.

Ce que les lecteurs apprennent maintenant : lire un document, c'est
docs.sparqlQuery ancre dessus, et lire plusieurs documents, c'est cet appel
une fois par document -- avec la decision sur une lecture qui echoue ecrite
au point d'appel. Deux consequences sont dites plutot que decouvertes : une
lecture refusee leve au lieu de rendre une liste vide, et les sujets sous le
prefixe reserve urn:ng-eventually: reviennent dans le resultat, a l'appelant
de les ignorer -- sans aucun gain de droits, les deux registres qu'un
document ordinaire porte etant l'adresse de sa boite et, pour un document
public, la cle que son magasin sert deja a qui la demande.

Les briefs dates ne sont pas touches : ils decrivent un etat de juillet, et
le reecrire falsifierait ce qui avait ete decide alors.
This commit is contained in:
Sylvain Duchesne
2026-09-21 14:11:35 +02:00
parent 0c21adebf2
commit 0a2b8b1fbf
29 changed files with 90 additions and 77 deletions
@@ -7,7 +7,9 @@ summary: The two fates a published symbol can have, and why the deletion list is
Every published symbol has exactly one of two fates, and knowing which is the whole point of this boundary. Every published symbol has exactly one of two fates, and knowing which is the whole point of this boundary.
**Replaced in place.** The build alias resolving `@ng-eventually/polyfill` is removed, the import resolves to the real SDK, and the application's code is unchanged. This is almost everything: `ng`, `useShape`, `watchShape`, `init`, `initNg`, `readUnion`, `subscribeDoc(s)`, `docs.*`, `inbox.*`, `storeRegistry.*`, and the types. **Replaced in place.** The build alias resolving `@ng-eventually/polyfill` is removed, the import resolves to the real SDK, and the application's code is unchanged. This is almost everything: `ng`, `useShape`, `useShapeQuery`, `init`, `initNg`, `getObjects`, `insertObject`, `subscribeDoc(s)`, `docs.*`, `inbox.*`, `storeRegistry.*`, and the types.
**A third fate exists and is not one of the two: withdrawn before migration.** `readUnion` and `UnionSubject` were listed above as replaced in place, and they were never going to be: a batch over N anchored queries has no counterpart at any level, so nothing would have resolved to it. They left the surface in `1.0.0-dev.8`, and an application reads one document with `docs.sparqlQuery` instead. That is the reason this leaf exists — a symbol whose fate is asserted rather than checked is how the list goes wrong.
**Deleted.** The symbol has no counterpart at any level of the target, exists only because one shared wallet hosts every user, and goes with its call site. Today that is `configure` and its config type. **Deleted.** The symbol has no counterpart at any level of the target, exists only because one shared wallet hosts every user, and goes with its call site. Today that is `configure` and its config type.
@@ -44,7 +44,7 @@ The `urn:` strings that appear upstream are unit-test fixtures and nothing else:
**`urn:ng-eventually:` — `MACHINERY_NS`** (`packages/polyfill/src/emulated-verifier/machinery.ts`), in two families: `urn:ng-eventually:shim:…`, the compartments `shared-wallet/account-registry.ts` uses to emulate a repo's branches inside one document, and `urn:ng-eventually:inbox:…`, the deposit envelope and the deposit kinds `emulated-verifier/inbox-dispatch.ts` dispatches on. **`urn:ng-eventually:` — `MACHINERY_NS`** (`packages/polyfill/src/emulated-verifier/machinery.ts`), in two families: `urn:ng-eventually:shim:…`, the compartments `shared-wallet/account-registry.ts` uses to emulate a repo's branches inside one document, and `urn:ng-eventually:inbox:…`, the deposit envelope and the deposit kinds `emulated-verifier/inbox-dispatch.ts` dispatches on.
**Triples under it are dropped on read.** `isMachinerySubject` is called from `readUnion` (`packages/polyfill/src/surface/read-model.ts`), which skips every row whose **subject** starts with the prefix before the caller ever sees it. By subject only — a machinery predicate on an ordinary subject comes straight through. **Triples under it are dropped on this package's OWN read, and nowhere else.** `isMachinerySubject` is called from `readDocumentSubjects` (`packages/polyfill/src/surface/read-document.ts`), which skips every row whose **subject** starts with the prefix. By subject only — a machinery predicate on an ordinary subject comes straight through. That read is INTERNAL since `1.0.0-dev.8`: the published `readUnion` used to apply the same filter for an application, and with it gone **an anchored `docs.sparqlQuery` returns the machinery rows** and the application ignores them itself. No right moves with that: the only registers an ordinary document carries are its inbox address and, for a public document, the read key its store already serves to whoever asks.
**That filtering has no upstream counterpart: it is an invention, and the source annotates it as one** (`kind=invention level=none`). It exists because this package has no branches, so a compartment upstream keeps in a separate CRDT has to live as a reserved subject in the same graph. Do not read our reservation as mirroring anything upstream — nothing there is filtered out of a document's own graph. **That filtering has no upstream counterpart: it is an invention, and the source annotates it as one** (`kind=invention level=none`). It exists because this package has no branches, so a compartment upstream keeps in a separate CRDT has to live as a reserved subject in the same graph. Do not read our reservation as mirroring anything upstream — nothing there is filtered out of a document's own graph.
+4 -4
View File
@@ -1,10 +1,10 @@
# Internal contract — what `@ng-eventually/polyfill` keeps off its surface, and what NextGraph does or would do about each subject # Internal contract — what `@ng-eventually/polyfill` keeps off its surface, and what NextGraph does or would do about each subject
> **Updated 2026-08-03, after the source layout was reorganised by migration fate** (`docs/source-layout-by-fate.md`). Paths, and three names, changed under this document: `readModel` became the directly-exported `readUnion`; `accounts` / `AccountRecord` / `AccountStorage` became `virtualUsers` / `VirtualUserRecord` / `VirtualUserStorage` (module `shared-wallet/virtual-users.ts`); `store-registry-api.ts` became `surface/placement.ts`. Two modules were created and are covered here: `emulated-verifier/branch-registers.ts` (the four durable registers, split out of the shim) and `shared-wallet/bootstrap.ts` (the injection store, split out of the `/polyfill` entry). The subject-by-subject rulings below are unaffected — what moved is where the code lives, not what it promises. > **Updated 2026-08-03, after the source layout was reorganised by migration fate** (`docs/source-layout-by-fate.md`). Paths, and three names, changed under this document: `readModel` became the directly-exported `readUnion` (itself removed in `1.0.0-dev.8`); `accounts` / `AccountRecord` / `AccountStorage` became `virtualUsers` / `VirtualUserRecord` / `VirtualUserStorage` (module `shared-wallet/virtual-users.ts`); `store-registry-api.ts` became `surface/placement.ts`. Two modules were created and are covered here: `emulated-verifier/branch-registers.ts` (the four durable registers, split out of the shim) and `shared-wallet/bootstrap.ts` (the injection store, split out of the `/polyfill` entry). The subject-by-subject rulings below are unaffected — what moved is where the code lives, not what it promises.
**Scope.** The complement of [`docs/api-contract.md`](./api-contract.md): every module export under `packages/polyfill/src/` that is NOT reachable from the published entry point (`package.json` maps exactly `.` → `src/index.ts`). A consumer never reads this document; a maintainer does. The internal code is held to the same standard as the surface — as close as possible to what NextGraph does or plans — so every subject below carries the same target-side analysis. Written 2026-08-04, verified against the `nextgraph-rs` clone (HEAD `213338f6`) and the installed `@ng-org/web@0.1.2-alpha.13` declarations (`node_modules/.bun/@ng-org+web@0.1.2-alpha.13/node_modules/@ng-org/web/dist/index.d.ts`, hereafter `index.d.ts`). **Scope.** The complement of [`docs/api-contract.md`](./api-contract.md): every module export under `packages/polyfill/src/` that is NOT reachable from the published entry point (`package.json` maps exactly `.` → `src/index.ts`). A consumer never reads this document; a maintainer does. The internal code is held to the same standard as the surface — as close as possible to what NextGraph does or plans — so every subject below carries the same target-side analysis. Written 2026-08-04, verified against the `nextgraph-rs` clone (HEAD `213338f6`) and the installed `@ng-org/web@0.1.2-alpha.13` declarations (`node_modules/.bun/@ng-org+web@0.1.2-alpha.13/node_modules/@ng-org/web/dist/index.d.ts`, hereafter `index.d.ts`).
**How the boundary was computed — mechanically, from the `export` statements.** `index.ts` re-exports wholesale (`export *` / `export * as ns`) from `types.ts`, `inbox.ts`, `docs.ts`, `surface/read-model.ts`, and by name everything `surface/use-shape.ts`, `surface/watch-shape.ts`, `lifecycle.ts`, `sparql.ts` export, and `subscribeDoc`/`subscribeDocs`/`docChangeType` (+ types) from `subscribe.ts`; its `storeRegistry` namespace is the **`surface/placement.ts` slice only** (`createEntityDoc`, `listMyEntityDocs`, `resolveScopeGraph`, `resolveWriteGraph`, `openDocumentInbox`). `model/nuri.ts` is now internal in full — the guards `isNuri`/`hasReadCap` stopped being published when the entries became permissive-in (`NuriLike` validated at the door). *(The second entry, `polyfill.ts`, was merged into `index.ts` on 2026-08-07, and the polyfill-era block was then cut to ONE published call, `configure`. `configureStoreRegistry`, `setCurrentUser` and `connectedUser` became internal the same day — folded, replaced by the gate, and awaited inside it respectively; the machinery accessors and test resets are internal too. All four are inventoried below.)* Everything else that carries `export` in a `src/` module is internal and inventoried here. Eight modules are internal in their entirety: `shared-wallet/access-log.ts`, `emulated-verifier/machinery.ts`, `surface/ng-proxy.ts`, `emulated-verifier/open-repo.ts`, `shared-wallet/outbox-log.ts`, `shared-wallet/physical.ts`, `emulated-verifier/reach.ts`, `emulated-verifier/read-filter.ts`. Four are internal in part: `nuri.ts`, `emulated-verifier/connect.ts`, `subscribe.ts`, `shared-wallet/account-registry.ts`. **How the boundary was computed — mechanically, from the `export` statements.** `index.ts` re-exports wholesale (`export *` / `export * as ns`) from `types.ts`, `inbox.ts`, `docs.ts`, `surface/read-model.ts` (removed in `1.0.0-dev.8`, see below), and by name everything `surface/use-shape.ts`, `surface/watch-shape.ts`, `lifecycle.ts`, `sparql.ts` export, and `subscribeDoc`/`subscribeDocs`/`docChangeType` (+ types) from `subscribe.ts`; its `storeRegistry` namespace is the **`surface/placement.ts` slice only** (`createEntityDoc`, `listMyEntityDocs`, `resolveScopeGraph`, `resolveWriteGraph`, `openDocumentInbox`). `model/nuri.ts` is now internal in full — the guards `isNuri`/`hasReadCap` stopped being published when the entries became permissive-in (`NuriLike` validated at the door). *(The second entry, `polyfill.ts`, was merged into `index.ts` on 2026-08-07, and the polyfill-era block was then cut to ONE published call, `configure`. `configureStoreRegistry`, `setCurrentUser` and `connectedUser` became internal the same day — folded, replaced by the gate, and awaited inside it respectively; the machinery accessors and test resets are internal too. All four are inventoried below.)* Everything else that carries `export` in a `src/` module is internal and inventoried here. Nine modules are internal in their entirety: `shared-wallet/access-log.ts`, `emulated-verifier/machinery.ts`, `surface/ng-proxy.ts`, `emulated-verifier/open-repo.ts`, `shared-wallet/outbox-log.ts`, `shared-wallet/physical.ts`, `emulated-verifier/reach.ts`, `emulated-verifier/read-filter.ts`, and — since `1.0.0-dev.8`, when `readUnion` and `UnionSubject` left the entry — `surface/read-document.ts` (the former `surface/read-model.ts`). Four are internal in part: `nuri.ts`, `emulated-verifier/connect.ts`, `subscribe.ts`, `shared-wallet/account-registry.ts`.
**Labels** are those of `docs/api-contract.md`: **PASSTHROUGH (level 3/2, VERIFIED)**, **LEVEL-1 SHAPE (model VERIFIED, JS surface ASSUMED)**, **ASSUMPTION**, **NO COUNTERPART**. Level numbers per `README.md` § *The three references*: 3 = JS ORM, 2 = wasm binding (`@ng-org/web`), 1 = Rust engine. One label recurs here that the surface contract rarely needs: **NO COUNTERPART, shared-wallet machinery** — the code below the emulation's floor, which the target has no image of because the target has no shared wallet. Per the design principle, an absent implementation is never treated as evidence about the future. **Labels** are those of `docs/api-contract.md`: **PASSTHROUGH (level 3/2, VERIFIED)**, **LEVEL-1 SHAPE (model VERIFIED, JS surface ASSUMED)**, **ASSUMPTION**, **NO COUNTERPART**. Level numbers per `README.md` § *The three references*: 3 = JS ORM, 2 = wasm binding (`@ng-org/web`), 1 = Rust engine. One label recurs here that the surface contract rarely needs: **NO COUNTERPART, shared-wallet machinery** — the code below the emulation's floor, which the target has no image of because the target has no shared wallet. Per the design principle, an absent implementation is never treated as evidence about the future.
@@ -115,7 +115,7 @@ Heals the cold-start defect of the anchored read path: on a fresh session a not-
- The opening mechanism is **level 2, VERIFIED as a composition**: `doc_subscribe` exists (`sdk/js/lib-wasm/src/lib.rs:1908`), and the push variants `TabInfo`/`State`/`Patch` are the engine's `AppResponseV0` (`engine/net/src/app_protocol.rs:1354-1358`). The ORDER (TabInfo first, then the initial State) and "first State = presence guaranteed, absence definitive" are **empirical, pinned by the in-repo e2e CONTRACT-3 probe — an ASSUMPTION about ordering as far as upstream is concerned**: no upstream statement fixes the push order, so a future reordering upstream would silently break the barrier. Bound: the e2e probe fails loudly if the order changes. - The opening mechanism is **level 2, VERIFIED as a composition**: `doc_subscribe` exists (`sdk/js/lib-wasm/src/lib.rs:1908`), and the push variants `TabInfo`/`State`/`Patch` are the engine's `AppResponseV0` (`engine/net/src/app_protocol.rs:1354-1358`). The ORDER (TabInfo first, then the initial State) and "first State = presence guaranteed, absence definitive" are **empirical, pinned by the in-repo e2e CONTRACT-3 probe — an ASSUMPTION about ordering as far as upstream is concerned**: no upstream statement fixes the push order, so a future reordering upstream would silently break the barrier. Bound: the e2e probe fails loudly if the order changes.
- "Hold a live subscription to keep the repo open" — **ASSUMPTION** (nothing upstream documents subscription lifetime as what retains a repo in `self.repos`); observed to work, bounded by the same probe. - "Hold a live subscription to keep the repo open" — **ASSUMPTION** (nothing upstream documents subscription lifetime as what retains a repo in `self.repos`); observed to work, bounded by the same probe.
- **Defect — the header's mechanism claim is contradicted at the source (see Findings F2).** `open-repo.ts:10-12` says an anchored `sparql_query` on a repo absent from `self.repos` "silently returns 0 rows (never a `RepoNotFound`)". Verified upstream: absence from `self.repos` yields `Err(NgError::RepoNotFound)` (`request_processor.rs:264,269`), the ReadQuery arm converts it into `AppResponse::error` (`:1293-1296`), and the web binding REJECTS the JS promise with it (`sdk/js/lib-wasm/src/lib.rs:606`). The observed 0-rows-no-error behaviour has two candidate explanations that the source does support: a persistent verifier reloads every known repo into `self.repos` at `Verifier::load` (`engine/verifier/src/verifier.rs:535-560`) so the repo is present-but-unsynced (a genuine 0-row read), and/or the lib's own per-doc tolerance (`readUserStore`, `surface/read-model.ts` per-doc catch) converts a rejection into an empty result. The healed symptom is real and the fix correct; the stated mechanism is not established, and a maintainer reasoning from it would mispredict behaviour whenever the verifier is not persistent. - **Defect — the header's mechanism claim is contradicted at the source (see Findings F2).** `open-repo.ts:10-12` says an anchored `sparql_query` on a repo absent from `self.repos` "silently returns 0 rows (never a `RepoNotFound`)". Verified upstream: absence from `self.repos` yields `Err(NgError::RepoNotFound)` (`request_processor.rs:264,269`), the ReadQuery arm converts it into `AppResponse::error` (`:1293-1296`), and the web binding REJECTS the JS promise with it (`sdk/js/lib-wasm/src/lib.rs:606`). The observed 0-rows-no-error behaviour has two candidate explanations that the source does support: a persistent verifier reloads every known repo into `self.repos` at `Verifier::load` (`engine/verifier/src/verifier.rs:535-560`) so the repo is present-but-unsynced (a genuine 0-row read), and/or the lib's own per-doc tolerance (`readUserStore`, and the `surface/read-model.ts` per-doc catch until `1.0.0-dev.8` removed it) converts a rejection into an empty result. The healed symptom is real and the fix correct; the stated mechanism is not established, and a maintainer reasoning from it would mispredict behaviour whenever the verifier is not persistent.
- `SyncState` and `getSyncState` are lib-invented vocabulary — **NO COUNTERPART** (upstream has no consumer-facing "sync state of a repo" API at any level; `OrmSubscription.readyPromise`, `sdk/js/orm/src/connector/GraphOrmSubscription.ts:260`, is the closest level-3 signal, per-subscription not per-repo). - `SyncState` and `getSyncState` are lib-invented vocabulary — **NO COUNTERPART** (upstream has no consumer-facing "sync state of a repo" API at any level; `OrmSubscription.readyPromise`, `sdk/js/orm/src/connector/GraphOrmSubscription.ts:260`, is the closest level-3 signal, per-subscription not per-repo).
- At migration the whole module becomes "open the store by cap at bootstrap" (native) and is removed with the shim. - At migration the whole module becomes "open the store by cap at bootstrap" (native) and is removed with the shim.
@@ -270,4 +270,4 @@ Fully internal modules: `shared-wallet/access-log.ts` (`AccessOp`, `setAccessLog
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`, `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`, `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`, `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.
+1 -1
View File
@@ -37,7 +37,7 @@ this step swaps the *emulated* key for the real one, not the model:
- `caps.markInPublicStore` and the whole of `emulated-verifier/public-store.ts` disappear: which store a document sits in stops being a fact we record, and serving a public store's repos becomes the broker's job (`expose_outer`, the ReadCap downloaded from the outer overlay — `PublicRepoLinkV0`, `engine/net/src/types.rs:5098`). Nothing an application calls changes: it circulates bare references now, and will still. - `caps.markInPublicStore` and the whole of `emulated-verifier/public-store.ts` disappear: which store a document sits in stops being a fact we record, and serving a public store's repos becomes the broker's job (`expose_outer`, the ReadCap downloaded from the outer overlay — `PublicRepoLinkV0`, `engine/net/src/types.rs:5098`). Nothing an application calls changes: it circulates bare references now, and will still.
- `assertMayWrite` goes with it — refusing a write on a cap the public store served is a stand-in for the write cap this emulation does not have. - `assertMayWrite` goes with it — refusing a write on a cap the public store served is a stand-in for the write cap this emulation does not have.
- The read filter (`emulated-verifier/read-filter.ts`) and the possession gate in - The read filter (`emulated-verifier/read-filter.ts`) and the possession gate in
`read-model.readUnion` are then dead code — the broker only delivers documents the anchored per-document read are then dead code — the broker only delivers documents
whose cap the wallet holds. Remove them. whose cap the wallet holds. Remove them.
- The write guard (`surface/ng-proxy.ts` `sparql_update` override) is a separate axis and - The write guard (`surface/ng-proxy.ts` `sparql_update` override) is a separate axis and
is decorative today (every internal writer bypasses the proxy); it belongs to the is decorative today (every internal writer bypasses the proxy); it belongs to the
+3 -3
View File
@@ -301,8 +301,8 @@ from JS today a repo becomes queryable ONLY by being `doc_create`d in this sessi
**Consequence for this lib's mono-wallet polyfill:** every account's documents are **Consequence for this lib's mono-wallet polyfill:** every account's documents are
`doc_create`d in the one shared wallet within the same session, so they are all `doc_create`d in the one shared wallet within the same session, so they are all
already in `self.repos`. `surface/read-model.ts` reads the bounded, by-need set of docs already in `self.repos`. `surface/read-document.ts` reads one document
with one anchored `sparql_query` per doc (`SELECT ?s ?p ?o WHERE { ?s ?p ?o }`, with one anchored `sparql_query` (`SELECT ?s ?p ?o WHERE { ?s ?p ?o }`,
anchor = the doc NURI): the anchor resolves that same-session repo directly (no anchor = the doc NURI): the anchor resolves that same-session repo directly (no
separate open needed) and restricts the query to its graph, so it is O(1) per doc, separate open needed) and restricts the query to its graph, so it is O(1) per doc,
independent of the store's size. An absent repo throws `RepoNotFound` on its own independent of the store's size. An absent repo throws `RepoNotFound` on its own
@@ -768,7 +768,7 @@ anchored `sparql_query` against a document written in an earlier session comes b
reader of the polyfill and healed identically in each (`ensureRepoOpen` before the read, reader of the polyfill and healed identically in each (`ensureRepoOpen` before the read,
`packages/polyfill/src/emulated-verifier/open-repo.ts`): the user's own documents, `packages/polyfill/src/emulated-verifier/open-repo.ts`): the user's own documents,
the user's store (`shared-wallet/account-registry.ts` `readUserStore`), the by-need doc batch the user's store (`shared-wallet/account-registry.ts` `readUserStore`), the by-need doc batch
(`surface/read-model.ts` `readUnion`), and the store-root pointer read (`shared-wallet/account-registry.ts` (`surface/read-document.ts` `readDocumentSubjects`), and the store-root pointer read (`shared-wallet/account-registry.ts`
`resolvePointer`). The heal is `doc_subscribe(nuri)` → await the first `State` (the sync `resolvePointer`). The heal is `doc_subscribe(nuri)` → await the first `State` (the sync
barrier) → THEN the anchored read, and it is verified to return the data. barrier) → THEN the anchored read, and it is verified to return the data.
+18 -15
View File
@@ -104,7 +104,7 @@ There is no reactive union query. So reactivity is assembled:
- keep a lightweight reactive subscription — `doc_subscribe`, or the ORM on an - keep a lightweight reactive subscription — `doc_subscribe`, or the ORM on an
already-opened single store (never a per-entity fan-out) — on the synced docs; already-opened single store (never a per-entity fan-out) — on the synced docs;
- on its change signal, re-run the bounded set of per-doc anchored - on its change signal, re-run the bounded set of per-doc anchored
`sparql_query`s (`readUnion`) — never an anchorless union-scan. `sparql_query`s — never an anchorless union-scan.
Keep the reactive ORM strictly to already-opened single stores; it is a change Keep the reactive ORM strictly to already-opened single stores; it is a change
*signal* source here, not the list source. *signal* source here, not the list source.
@@ -165,7 +165,7 @@ Step (4) has a nuance worth recording, and it is exactly why the read path uses
graph — so an explicit `GRAPH ?g` body spans every opened graph independently of graph — so an explicit `GRAPH ?g` body spans every opened graph independently of
the anchor. The anchor's "one repo" restriction is observable only for a body that the anchor. The anchor's "one repo" restriction is observable only for a body that
reads the **default graph** (no `GRAPH` wrapper). That is precisely why the per-doc reads the **default graph** (no `GRAPH` wrapper). That is precisely why the per-doc
read in `surface/read-model.ts` uses the anchored default-graph body read in `surface/read-document.ts` uses the anchored default-graph body
`SELECT ?s ?p ?o WHERE { ?s ?p ?o }`: the anchor makes that one repo the default `SELECT ?s ?p ?o WHERE { ?s ?p ?o }`: the anchor makes that one repo the default
graph, so the read is bounded to it — O(1) per doc, independent of wallet size — graph, so the read is bounded to it — O(1) per doc, independent of wallet size —
and never iterates the other named graphs. (A repo absent from `self.repos` throws and never iterates the other named graphs. (A repo absent from `self.repos` throws
@@ -185,21 +185,24 @@ sync an unknown repo.)
> is per-doc anchored, preserved above), **not** a **constant** `GRAPH <D>` write, > is per-doc anchored, preserved above), **not** a **constant** `GRAPH <D>` write,
> which lands in D's repo. Re-run that harness to re-verify against a newer broker. > which lands in D's repo. Re-run that harness to re-verify against a newer broker.
## Implementation — `surface/read-model.ts` ## Implementation — one anchored query per document, and nothing above it
`readUnion(docs)` implements this: for each requested doc NURI (the For each doc NURI in the by-need set, run one anchored
bounded by-need set), run — in parallel, tolerant per-doc (a doc that fails is
skipped, never aborting the batch like the ORM fan-out would) — one anchored
`SELECT ?s ?p ?o WHERE { ?s ?p ?o }` with `anchor = docNuri`. The anchor restricts `SELECT ?s ?p ?o WHERE { ?s ?p ?o }` with `anchor = docNuri`. The anchor restricts
the query to that doc's graph (default graph), so it returns only that doc's the query to that doc's graph (default graph), so it returns only that doc's
triples, O(1) per doc, independent of wallet size. There is no anchorless triples, O(1) per doc, independent of wallet size. There is no anchorless
union-scan. Each entity's subject IRI is its own document NURI, so the subject is union-scan. Subjects come back as the document carries them: an entity written under
the anchor doc NURI; the result is grouped per subject (keeping the `UnionSubject[]` the document's own NURI reads back under it, and a document carrying several subjects
shape: `subject`, `graph`, `props`). A ReadCap gate drops any doc the current user yields several. A ReadCap gate refuses any doc the current user may not read, at the
may not read (defence-in-depth). The consumer application maps the result to its door of `docs.sparqlQuery`. Reactivity = the caller re-runs the reads on its change
types (e.g. its own `readEntities`). Reactivity = the consumer application re-calls signal (no reactive union query exists).
`readUnion` on its change signal (no reactive union query exists).
> The name `readUnion` / `UnionSubject` is historical (it once ran a union query). > **`readUnion` / `UnionSubject` were published for this until `1.0.0-dev.8`, and are
> The read is now per-doc anchored, bounded to the read-set — the "union" is only > gone.** They were the loop above, plus policies of this package's own: a document that
> the logical concatenation of the per-doc results, never an anchorless graph scan. > failed was swallowed into an empty answer, one whose key the reader did not hold was
> dropped rather than refused, and the rows were grouped into property bags. Nothing at
> any level of NextGraph answers that composition, and upstream an anchored query on a
> repo the verifier does not hold REJECTS — so "skip it and carry on" is a habit a caller
> would have had to unlearn. The loop, and what a failure inside it means, now belong to
> whoever writes it. The name was historical too: the read has been per-doc anchored
> throughout, and the "union" was only the logical concatenation of the results.
+2 -2
View File
@@ -359,12 +359,12 @@ The consequence for anything this library exposes: **a call either hands over th
### Where this library stands, act by act ### Where this library stands, act by act
- **Transmit the reference** — covered, with no dedicated call: every reference the surface returns is bare (`createEntityDoc`, `docCreate`, `listMyEntityDocs`, `UnionSubject.subject`/`.graph`). An application cites what it already holds. Faithful. - **Transmit the reference** — covered, with no dedicated call: every reference the surface returns is bare (`createEntityDoc`, `docCreate`). An application cites what it already holds. Faithful.
- **Transmit the reference and the key** — `inbox.share(doc, toUser)`. Names the document and the person; the key is looked up and sealed into a deposit, and the recipient applies it by connecting, with nothing to call. Faithful in shape. One recorded divergence: the deposit always goes to the recipient's PROTECTED inbox, where upstream the choice follows the profile the person was reached by (`engine/verifier/src/inbox_processor.rs:787`). - **Transmit the reference and the key** — `inbox.share(doc, toUser)`. Names the document and the person; the key is looked up and sealed into a deposit, and the recipient applies it by connecting, with nothing to call. Faithful in shape. One recorded divergence: the deposit always goes to the recipient's PROTECTED inbox, where upstream the choice follows the profile the person was reached by (`engine/verifier/src/inbox_processor.rs:787`).
**And the property that makes the first act worth anything — a public store SERVES its documents' caps.** `PublicRepoLinkV0` (`engine/net/src/types.rs:5098-5124`) carries `repo`, `public_store` and `peers` and no `read_cap`, and its own comment says why: *"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 durable, because the public site are **served differently by brokers**."* The key is not something a sender hands over; it is something the network gives to whoever asks, because the broker pinned the outer overlay (`expose_outer`, `engine/broker/src/server_storage/core/overlay.rs:103-133`). **And the property that makes the first act worth anything — a public store SERVES its documents' caps.** `PublicRepoLinkV0` (`engine/net/src/types.rs:5098-5124`) carries `repo`, `public_store` and `peers` and no `read_cap`, and its own comment says why: *"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 durable, because the public site are **served differently by brokers**."* The key is not something a sender hands over; it is something the network gives to whoever asks, because the broker pinned the outer overlay (`expose_outer`, `engine/broker/src/server_storage/core/overlay.rs:103-133`).
That is emulated, since 2026-08-06, in `emulated-verifier/public-store.ts` — and emulated **without touching the guard**. Possession remains the one criterion: a public document is readable not because `mayReach` makes an exception, but because its cap is *obtainable* — the library asks, files what it gets, and from there the ordinary path applies. Every read door asks first (`readUnion`, `docs.sparqlQuery`, `ensureRepoOpen`, `documentInboxAddress`). That is emulated, since 2026-08-06, in `emulated-verifier/public-store.ts` — and emulated **without touching the guard**. Possession remains the one criterion: a public document is readable not because `mayReach` makes an exception, but because its cap is *obtainable* — the library asks, files what it gets, and from there the ordinary path applies. Every read door asks first (`docs.sparqlQuery`, `ensureRepoOpen`, `documentInboxAddress`).
Where the emulation shows its seams, stated rather than hidden: Where the emulation shows its seams, stated rather than hidden:
+2 -2
View File
@@ -135,7 +135,7 @@ public/protected/private stores — on top of one shared wallet.
user, and the only listing there is: the cross-account fan-out user, and the only listing there is: the cross-account fan-out
(`listEntityDocs` / `resolveReadGraphs` / `allAccounts` / `loadShim`) was (`listEntityDocs` / `resolveReadGraphs` / `allAccounts` / `loadShim`) was
**removed on 2026-07-30**, being cross-user enumeration by construction. The real read path is **removed on 2026-07-30**, being cross-user enumeration by construction. The real read path is
`readUnion(docs)`, which reads the by-need doc set with one per-doc one anchored `docs.sparqlQuery` per document, which reads the by-need doc set with one per-doc
anchored `sparql_query`, never an anchorless union-scan of the physical anchored `sparql_query`, never an anchorless union-scan of the physical
wallet (see [`read-model.md`](./read-model.md)). The consumer application resolves wallet (see [`read-model.md`](./read-model.md)). The consumer application resolves
the by-need doc set from the current wallet's own scope index and the by-need doc set from the current wallet's own scope index and
@@ -528,7 +528,7 @@ redundant so a lapse in either is caught by the other:
/ `sparqlUpdate` / `subscribeDoc`): nothing reaches `ng` unless the connected user / `sparqlUpdate` / `subscribeDoc`): nothing reaches `ng` unless the connected user
possesses that document's cap. It fires on a request that should never have been possesses that document's cap. It fires on a request that should never have been
made, and makes it fail loudly rather than succeed quietly. made, and makes it fail loudly rather than succeed quietly.
- **Rule 2, do not even attempt** (`mustNotAttempt`, at the callers — `readUnion` - **Rule 2, do not even attempt** (`mustNotAttempt`, at the callers — `ensureRepoOpen`
filters before opening or reading, `ensureRepoOpen` returns): a reader holding no filters before opening or reading, `ensureRepoOpen` returns): a reader holding no
cap does not issue the operation at all. Upstream you cannot even *address* a repo cap does not issue the operation at all. Upstream you cannot even *address* a repo
you have no cap for, so asking is not "a read that will be refused" — it is a read you have no cap for, so asking is not "a read that will be refused" — it is a read
+3 -3
View File
@@ -77,7 +77,7 @@ The 25 current modules, with the two splits' offspring shown where a module divi
| `surface/use-shape.ts` | `surface/use-shape.ts` | Level-3 passthrough + the read-filter view imported from `emulated-verifier/` — mixture-by-import, already in the right shape. | | `surface/use-shape.ts` | `surface/use-shape.ts` | Level-3 passthrough + the read-filter view imported from `emulated-verifier/` — mixture-by-import, already in the right shape. |
| `surface/watch-shape.ts` | `surface/watch-shape.ts` | Surface composition over `emulated-verifier/` and the placement calls; its "planned `useShape` upgrade" header claim remains an ASSUMPTION with no provenance (`docs/api-contract.md` § 5) — a layout cannot fix that (§ 6). | | `surface/watch-shape.ts` | `surface/watch-shape.ts` | Surface composition over `emulated-verifier/` and the placement calls; its "planned `useShape` upgrade" header claim remains an ASSUMPTION with no provenance (`docs/api-contract.md` § 5) — a layout cannot fix that (§ 6). |
| `subscribe.ts` | `surface/subscribe.ts`, minus `subscribePhysicalDoc` → `shared-wallet/physical.ts` | The guarded `subscribeDoc`/`subscribeDocs` and `docChangeType` are surface (level 2, `doc_subscribe`, `sdk/js/lib-wasm/src/lib.rs:1908`). The physical door moves to the machinery module (§ 4); the unguarded core is exported under its `Unguarded` name for that one importer. | | `subscribe.ts` | `surface/subscribe.ts`, minus `subscribePhysicalDoc` → `shared-wallet/physical.ts` | The guarded `subscribeDoc`/`subscribeDocs` and `docChangeType` are surface (level 2, `doc_subscribe`, `sdk/js/lib-wasm/src/lib.rs:1908`). The physical door moves to the machinery module (§ 4); the unguarded core is exported under its `Unguarded` name for that one importer. |
| `surface/read-model.ts` | `surface/read-model.ts` — stays whole | The anchored-read mechanics are level-1-verified and survive as composition (`docs/api-contract.md` § 6). Its possession gate and machinery filter mirror native behaviour (§ 4 ruling) and arrive via named `emulated-verifier/` imports. | | `surface/read-model.ts` | `surface/read-document.ts` — INTERNAL since `1.0.0-dev.8` | The anchored-read mechanics are level-1-verified and the anchored query itself is published as `docs.sparqlQuery` (`docs/api-contract.md` § 6). What this module keeps is the one-document read the index arm needs: the cold-start open, and the grouping of a SELECT's rows. The batch above it (`readUnion`) was removed with its `UnionSubject`. |
| `inbox.ts` | **split**: `surface/inbox.ts` (post, `postToDocument`, `shareCap`) + `emulated-verifier/inbox-processing.ts` (`read`/`materialize`/`readSynced`/`processInbox`/`watch`, `assertOwnInbox`, the deposit RDF vocabulary) | § 4 ruling. `surface/inbox.ts` re-exports the processing functions with a header saying exactly what `docs/api-contract.md` § 9 says — that enumerating deposits is emulation detail — so the published `inbox.*` namespace is unchanged and the warning sits at the one place the two halves meet. | | `inbox.ts` | **split**: `surface/inbox.ts` (post, `postToDocument`, `shareCap`) + `emulated-verifier/inbox-processing.ts` (`read`/`materialize`/`readSynced`/`processInbox`/`watch`, `assertOwnInbox`, the deposit RDF vocabulary) | § 4 ruling. `surface/inbox.ts` re-exports the processing functions with a header saying exactly what `docs/api-contract.md` § 9 says — that enumerating deposits is emulation detail — so the published `inbox.*` namespace is unchanged and the warning sits at the one place the two halves meet. |
| `surface/placement.ts` | dissolved into `surface/placement.ts` | The hand-built slice becomes a real module: the app-facing placement/addressing calls (`createEntityDoc`, `listMyEntityDocs`, `resolveScopeGraph`, `resolveWriteGraph`, `userInbox`, `openDocumentInbox`, `documentInboxAddress`), composed from the two lower layers. Its existence today is the strongest in-repo evidence for the whole reorganisation: it does by hand what the folder boundary does structurally. | | `surface/placement.ts` | dissolved into `surface/placement.ts` | The hand-built slice becomes a real module: the app-facing placement/addressing calls (`createEntityDoc`, `listMyEntityDocs`, `resolveScopeGraph`, `resolveWriteGraph`, `userInbox`, `openDocumentInbox`, `documentInboxAddress`), composed from the two lower layers. Its existence today is the strongest in-repo evidence for the whole reorganisation: it does by hand what the folder boundary does structurally. |
| `shared-wallet/account-registry.ts` | **split three ways**: `surface/placement.ts` + `emulated-verifier/branch-registers.ts` + `shared-wallet/account-registry.ts` | The sharpest case — 1377 lines spanning all three fates. `branch-registers.ts` takes the durable compartments: the Store-branch cap register (`holdOwnCap`/`readStoreCaps` — emulating `AddRepo { read_cap }`, level 1, `engine/repo/src/types.rs:1890-1899`), the User-branch Link register (`addLink`/`readLinks` — `AddLink { read_cap }`, `types.rs:1939-1948`), the inbox-cap records (`AddInboxCapV0`, `types.rs:1969-1981`) and the Header-branch address records. `account-registry.ts` takes the pointer→doc-shim indirection, `resolveAccount`/`ensureAccount`, `reservedAccount`, the cache, `VirtualUserRecord`/`RegistrySession` — NO COUNTERPART, the shim proper. `placement.ts` (surface) keeps the app-facing calls listed above plus `userStoreDoc`/`isOwnInbox`/`myInboxes` staying internal on the register side per their contract entries. | | `shared-wallet/account-registry.ts` | **split three ways**: `surface/placement.ts` + `emulated-verifier/branch-registers.ts` + `shared-wallet/account-registry.ts` | The sharpest case — 1377 lines spanning all three fates. `branch-registers.ts` takes the durable compartments: the Store-branch cap register (`holdOwnCap`/`readStoreCaps` — emulating `AddRepo { read_cap }`, level 1, `engine/repo/src/types.rs:1890-1899`), the User-branch Link register (`addLink`/`readLinks` — `AddLink { read_cap }`, `types.rs:1939-1948`), the inbox-cap records (`AddInboxCapV0`, `types.rs:1969-1981`) and the Header-branch address records. `account-registry.ts` takes the pointer→doc-shim indirection, `resolveAccount`/`ensureAccount`, `reservedAccount`, the cache, `VirtualUserRecord`/`RegistrySession` — NO COUNTERPART, the shim proper. `placement.ts` (surface) keeps the app-facing calls listed above plus `userStoreDoc`/`isOwnInbox`/`myInboxes` staying internal on the register side per their contract entries. |
@@ -106,7 +106,7 @@ Fate labels exist per *export* in the two contracts; a module is mixed when its
- **Signal B — guarded/unguarded twin exports:** the `*Physical*` / `*Unguarded` pairs. Hits: `subscribe.ts:104/118`, `open-repo.ts:167/184`. - **Signal B — guarded/unguarded twin exports:** the `*Physical*` / `*Unguarded` pairs. Hits: `subscribe.ts:104/118`, `open-repo.ts:167/184`.
- **Signal C — act vs stand-in-processing under one namespace:** exports of the same module carrying different fate labels in the contracts. Hits: `inbox.ts` (§ 9: sender acts are target-shaped; deposit enumeration is emulation detail), `shared-wallet/account-registry.ts` (§ 12: labels range from level-2 VERIFIED to NO COUNTERPART), `types.ts` (model types vs injection types, § 1 vs § 10), `nuri.ts` (guards vs `mintCap`, internal contract § 2), `polyfill.ts` (entry vs config store). - **Signal C — act vs stand-in-processing under one namespace:** exports of the same module carrying different fate labels in the contracts. Hits: `inbox.ts` (§ 9: sender acts are target-shaped; deposit enumeration is emulation detail), `shared-wallet/account-registry.ts` (§ 12: labels range from level-2 VERIFIED to NO COUNTERPART), `types.ts` (model types vs injection types, § 1 vs § 10), `nuri.ts` (guards vs `mintCap`, internal contract § 2), `polyfill.ts` (entry vs config store).
Full mixed list: `docs.ts`, `surface/read-model.ts`, `inbox.ts`, `subscribe.ts`, `emulated-verifier/open-repo.ts`, `shared-wallet/account-registry.ts`, `nuri.ts`, `types.ts`, `polyfill.ts`, `surface/ng-proxy.ts`, `emulated-verifier/caps.ts`, `emulated-verifier/connect.ts`. (`surface/use-shape.ts` and `surface/watch-shape.ts` cross fates only through imports — already the desired end state.) Full mixed list: `docs.ts`, `surface/read-document.ts`, `inbox.ts`, `subscribe.ts`, `emulated-verifier/open-repo.ts`, `shared-wallet/account-registry.ts`, `nuri.ts`, `types.ts`, `polyfill.ts`, `surface/ng-proxy.ts`, `emulated-verifier/caps.ts`, `emulated-verifier/connect.ts`. (`surface/use-shape.ts` and `surface/watch-shape.ts` cross fates only through imports — already the desired end state.)
### The criterion ### The criterion
@@ -115,7 +115,7 @@ Full mixed list: `docs.ts`, `surface/read-model.ts`, `inbox.ts`, `subscribe.ts`,
### Rulings ### Rulings
- **`docs.ts` — KEEP WHOLE.** The cap filing inside `docCreate` mirrors the target's own `doc_create`, which commits `AddRepo` to the Store branch and `ldp:contains` to the Main branch *in the same native call* (level 1, `engine/verifier/src/request_processor.rs:697-710`, re-verified). Splitting the mint into a separate caller-visible step would create a two-step creation surface the target does not have — the split itself would be the divergence. Same for the reach guard: the refusal is native (`RepoNotFound`). The fix is visibility, not surgery: both effects become named imports from `emulated-verifier/`. - **`docs.ts` — KEEP WHOLE.** The cap filing inside `docCreate` mirrors the target's own `doc_create`, which commits `AddRepo` to the Store branch and `ldp:contains` to the Main branch *in the same native call* (level 1, `engine/verifier/src/request_processor.rs:697-710`, re-verified). Splitting the mint into a separate caller-visible step would create a two-step creation surface the target does not have — the split itself would be the divergence. Same for the reach guard: the refusal is native (`RepoNotFound`). The fix is visibility, not surgery: both effects become named imports from `emulated-verifier/`.
- **`surface/read-model.ts` — KEEP WHOLE.** Its possession gate mirrors native non-delivery, and its machinery-subject drop mirrors the structural invisibility of non-content branches (`BranchCrdt::None`, `types.rs:1420`, level 1). Both are the target's own composition of "read a document". - **`surface/read-model.ts` — KEEP WHOLE** (now `surface/read-document.ts`, and no longer published). Its machinery-subject drop mirrors the structural invisibility of non-content branches (`BranchCrdt::None`, `types.rs:1420`, level 1). Its possession gate is no longer its own: the refusal lives at `docs.sparqlQuery`, where it has always been, which is also why removing the batch above it lost nothing.
- **`inbox.ts` — SPLIT.** Different fates (api-contract § 9: the acts are target-shaped level-1 inventions; the deposit-list surface "may never have this shape") *and* different callers (apps post/share/watch; `emulated-verifier/connect.ts` processes). The deposit RDF vocabulary — pure emulation transport (upstream a deposit is a sealed message, `InboxMsgBody`, `engine/net/src/types.rs:4265`, level 1, carrying no target document) — lives once, on the emulated side. - **`inbox.ts` — SPLIT.** Different fates (api-contract § 9: the acts are target-shaped level-1 inventions; the deposit-list surface "may never have this shape") *and* different callers (apps post/share/watch; `emulated-verifier/connect.ts` processes). The deposit RDF vocabulary — pure emulation transport (upstream a deposit is a sealed message, `InboxMsgBody`, `engine/net/src/types.rs:4265`, level 1, carrying no target document) — lives once, on the emulated side.
- **`subscribe.ts` / `emulated-verifier/open-repo.ts` — SPLIT the physical doors out** (criterion's corollary): different caller (machinery only), different fate (the guarded/unguarded pair collapses to one call when the wallet is the boundary). They regroup in `shared-wallet/physical.ts`. - **`subscribe.ts` / `emulated-verifier/open-repo.ts` — SPLIT the physical doors out** (criterion's corollary): different caller (machinery only), different fate (the guarded/unguarded pair collapses to one call when the wallet is the boundary). They regroup in `shared-wallet/physical.ts`.
- **`shared-wallet/account-registry.ts` — SPLIT three ways** (§ 3). It is the module the flat layout hides most: signal C fires on nearly every export group, and the repo already voted for the split by hand-building `surface/placement.ts`. - **`shared-wallet/account-registry.ts` — SPLIT three ways** (§ 3). It is the module the flat layout hides most: signal C fires on nearly every export group, and the repo already voted for the split by hand-building `surface/placement.ts`.
+1 -1
View File
@@ -8,7 +8,7 @@ It exists for two reasons, and the second is the one that matters.
**It is what the applicative e2e suite drives** (`packages/polyfill/e2e/notebook.ts`, `bun run test:e2e:app`). The other suite talks to a bag of methods on `window.__sdk`, which proves the functions run but never that an application can be written with them — and that gap shipped a real defect once: a document's inbox was green in tests and unusable in practice, because the harness handed an address across an identity boundary through a variable, something no application can do. Here each identity is its own browser page, and the only values that cross between them are the ones that cross in life: a note's reference, copied off one screen, and an identifier typed into a field. **It is what the applicative e2e suite drives** (`packages/polyfill/e2e/notebook.ts`, `bun run test:e2e:app`). The other suite talks to a bag of methods on `window.__sdk`, which proves the functions run but never that an application can be written with them — and that gap shipped a real defect once: a document's inbox was green in tests and unusable in practice, because the harness handed an address across an identity boundary through a variable, something no application can do. Here each identity is its own browser page, and the only values that cross between them are the ones that cross in life: a note's reference, copied off one screen, and an identifier typed into a field.
It has already paid for itself: writing it surfaced that `UnionSubject` returned `string` where the values are always document references (so a consumer had to cast whatever it had just read before passing it back), and that the access gate normalized what a user typed but not what the URL carried. It has already paid for itself: writing it surfaced that the then-published `readUnion`'s property bags returned `string` where the values are always document references (so a consumer had to cast whatever it had just read before passing it back), and that the access gate normalized what a user typed but not what the URL carried.
## What it exercises ## What it exercises
+10 -5
View File
@@ -2,7 +2,7 @@
One entry point. Most of what it publishes has the same signature as the future SDK — One entry point. Most of what it publishes has the same signature as the future SDK —
`ng`, `useShape`, `useShapeQuery`, `getObjects`, `insertObject`, `docs`, `inbox`, `ng`, `useShape`, `useShapeQuery`, `getObjects`, `insertObject`, `docs`, `inbox`,
`storeRegistry`, `readUnion` (+ types) - `storeRegistry` (+ types) -
and is a drop-in for `@ng-org/web` / `@ng-org/orm`: as NextGraph matures it resolves to and is a drop-in for `@ng-org/web` / `@ng-org/orm`: as NextGraph matures it resolves to
the real SDK (build alias removed) with no code change. the real SDK (build alias removed) with no code change.
@@ -28,7 +28,7 @@ Per-symbol, with the target signature and an epistemic label on every claim:
```ts ```ts
import { import {
// SDK-shaped — the real SDK replaces these in place. // SDK-shaped — the real SDK replaces these in place.
ensureIdentity, storeRegistry, inbox, readUnion, docs, ensureIdentity, storeRegistry, inbox, docs,
// Polyfill-era — one call, and it is the whole of what goes away. // Polyfill-era — one call, and it is the whole of what goes away.
configure, configure,
} from "@ng-eventually/polyfill"; } from "@ng-eventually/polyfill";
@@ -91,7 +91,7 @@ which the e2e suite drives.
## How a document is reached — the three acts, and no others ## How a document is reached — the three acts, and no others
```ts ```ts
import { storeRegistry, inbox, readUnion } from "@ng-eventually/polyfill"; import { storeRegistry, inbox, docs } from "@ng-eventually/polyfill";
// 1. CREATE — you hold its cap, with nothing to declare. No identity parameter: a // 1. CREATE — you hold its cap, with nothing to declare. No identity parameter: a
// session belongs to one user, exactly as the target's own `doc_create` assumes. // session belongs to one user, exactly as the target's own `doc_create` assumes.
@@ -109,7 +109,11 @@ await inbox.share(doc, "bob");
// it and opens nothing. // it and opens nothing.
const publicDoc = await storeRegistry.createEntityDoc("public"); const publicDoc = await storeRegistry.createEntityDoc("public");
// …put `publicDoc` in a QR code, a message, another document. Nothing else to do. // …put `publicDoc` in a QR code, a message, another document. Nothing else to do.
await readUnion([publicDoc]); // a stranger holding only this reads it // A stranger holding only that reference reads it with ONE anchored query — the anchor
// scopes the read to that document's own graph, and the key is fetched from the public
// store on the way in. `readUnion([publicDoc])` did this until `1.0.0-dev.8`; it was
// this call in a loop, so the loop is yours now and the call is unchanged.
await docs.sparqlQuery(sessionId, "SELECT ?s ?p ?o WHERE { ?s ?p ?o }", undefined, publicDoc);
``` ```
**The invariant behind all three: you never derive a cap from a bare reference.** You **The invariant behind all three: you never derive a cap from a bare reference.** You
@@ -139,5 +143,6 @@ runtime checks stay regardless — a JavaScript caller never meets the compiler.
```ts ```ts
const saved = localStorage.getItem("doc"); // string | null const saved = localStorage.getItem("doc"); // string | null
if (saved) await readUnion([saved]); // ✓ validated at the door // ✓ validated at the door — `anchor` is a `NuriLike`, so nothing to narrow here
if (saved) await docs.sparqlQuery(sessionId, "SELECT ?s ?p ?o WHERE { ?s ?p ?o }", undefined, saved);
``` ```
+11 -12
View File
@@ -173,14 +173,13 @@ In `@ng-eventually/polyfill` the one-shot read is exposed as:
- **`docs.sparqlQuery(sid, query, base?, anchor?)`** — a raw anchored SPARQL query - **`docs.sparqlQuery(sid, query, base?, anchor?)`** — a raw anchored SPARQL query
([`../src/surface/docs.ts`](../src/surface/docs.ts)). `anchor` = the document NURI to read; the ([`../src/surface/docs.ts`](../src/surface/docs.ts)). `anchor` = the document NURI to read; the
anchor restricts the query to that one repo's graph. anchor restricts the query to that one repo's graph.
- **`readUnion(docs)`** — read a **bounded, by-need set** of document NURIs, `readUnion(docs)` sat above this until `1.0.0-dev.8` and is **gone**: it was this call
each with its own anchored query, grouped per subject once per document plus a batch policy nothing upstream answers. Reading a bounded set
([`../src/surface/read-model.ts`](../src/surface/read-model.ts)). This is the polyfill's listing of documents is this call in a loop, and what one unreadable document does to the rest
primitive (see [§ Current emulation status](#current-emulation-status) and is decided at the call site (see [`read-model.md`](../../../docs/read-model.md)).
[`read-model.md`](../../../docs/read-model.md)).
One-shot reads do not re-render on change. To stay live over a one-shot read you must One-shot reads do not re-render on change. To stay live over a one-shot read you must
re-run it on a change **signal** (e.g. re-call `readUnion` when a `doc_subscribe` re-run it on a change **signal** (e.g. re-run the anchored query when a `doc_subscribe`
fires) — a manual assembly that exists only because of the emulation gap below; the fires) — a manual assembly that exists only because of the emulation gap below; the
finished contract is `useShape`. finished contract is `useShape`.
@@ -303,11 +302,11 @@ from the reactive contract:
`engine/verifier/src/request_processor.rs` `resolve_target` → `engine/verifier/src/request_processor.rs` `resolve_target` →
`self.repos.get(...).ok_or(RepoNotFound)`; see `self.repos.get(...).ok_or(RepoNotFound)`; see
[`nextgraph-current-state.md`](../../../docs/nextgraph-current-state.md) § *The ORM [`nextgraph-current-state.md`](../../../docs/nextgraph-current-state.md) § *The ORM
fan-out hang*). So the lib reads entity lists with **`readUnion`** — a fan-out hang*). So the lib reads entity lists as **one anchored `sparql_query` per
bounded set of one-shot anchored `sparql_query`s document** ([`read-model.md`](../../../docs/read-model.md)) — and reassembles
([`read-model.md`](../../../docs/read-model.md)) — and reassembles reactivity by reactivity by **re-querying on a change signal** (a lightweight `doc_subscribe` /
**re-querying on a change signal** (a lightweight `doc_subscribe` / single-store ORM single-store ORM used only as a *signal* source, then re-run the reads). `useShape`
used only as a *signal* source, then re-run `readUnion`). `useShape` remains valid remains valid
for a **single already-opened document**; it is the per-entity **fan-out** that is for a **single already-opened document**; it is the per-entity **fan-out** that is
unfit today. unfit today.
@@ -334,7 +333,7 @@ from the reactive contract:
`ng.doc_subscribe`), the subscription callback appears NOT to fire for its OWN `ng.doc_subscribe`), the subscription callback appears NOT to fire for its OWN
local commit in the same session — so the reactive re-read chain local commit in the same session — so the reactive re-read chain
([`../src/surface/use-shape-query.ts`](../src/surface/use-shape-query.ts) `useShapeQuery` → `reread` → ([`../src/surface/use-shape-query.ts`](../src/surface/use-shape-query.ts) `useShapeQuery` → `reread` →
[`../src/surface/read-model.ts`](../src/surface/read-model.ts) `readUnion`) never runs, and the anchored per-document read) never runs, and
consumers keep the STALE value until the next connection delivers a fresh consumers keep the STALE value until the next connection delivers a fresh
initial `State`. **Remote** commits DO push correctly (verified: cross-browser initial `State`. **Remote** commits DO push correctly (verified: cross-browser
reactive update works). A code review verified the consumer wiring is correct, reactive update works). A code review verified the consumer wiring is correct,
@@ -232,7 +232,8 @@ export async function readStoreCaps(storeDoc: Nuri): Promise<ReadCap[]> {
* store it was created in ({@link holdOwnCap}) — and until 2026-08-16 exactly one path * store it was created in ({@link holdOwnCap}) — and until 2026-08-16 exactly one path
* read it back: `listMyEntityDocs`. So a creator that reloaded the page and went straight * read it back: `listMyEntityDocs`. So a creator that reloaded the page and went straight
* to the document it had just made, without listing that scope first, held nothing for it: * to the document it had just made, without listing that scope first, held nothing for it:
* the read was refused, the batch read (`readUnion`) dropped it silently, and the write * the read was refused, the batch read of the day (`readUnion`, removed in `1.0.0-dev.8`)
* dropped it silently, and the write
* was refused. Its own document, denied to its own author, on the first refresh. * was refused. Its own document, denied to its own author, on the first refresh.
* *
* Restoring what a user HOLDS is already what connecting means here — that is what the * Restoring what a user HOLDS is already what connecting means here — that is what the
@@ -288,7 +288,7 @@ async function applyLink(payload: unknown, ctx: Applying): Promise<boolean> {
// reports the whole inbox, and everything not yet popped is preserved for the next // reports the whole inbox, and everything not yet popped is preserved for the next
// attempt. That is the rule stated beside {@link report}, and BOTH arms follow it — but // attempt. That is the rule stated beside {@link report}, and BOTH arms follow it — but
// the reference arm only since its `unreadable` outcome was split out of `refused` on the // the reference arm only since its `unreadable` outcome was split out of `refused` on the
// same day. It had been the opposite: `read-model.readDoc` swallows a failed read and // same day. It had been the opposite: the batch read of the day swallowed a failed read and
// answers `[]`, so an outage arrived as an ordinary refusal and the drain walked the // answers `[]`, so an outage arrived as an ordinary refusal and the drain walked the
// whole queue destroying it. Correcting one arm and leaving the other is precisely how // whole queue destroying it. Correcting one arm and leaving the other is precisely how
// that survived a round of review. // that survived a round of review.
@@ -32,7 +32,7 @@
* nothing had to tell an absence from a failure. **That is no longer true, and it is the * nothing had to tell an absence from a failure. **That is no longer true, and it is the
* sharpest consequence of consuming.** An absence is deterministic and costs its own deposit; * sharpest consequence of consuming.** An absence is deterministic and costs its own deposit;
* a silent broker is transient and must stop the drain, or one outage walks the queue and * a silent broker is transient and must stop the drain, or one outage walks the queue and
* destroys every reference in it. `read-model.readDoc` answers `[]` for both, so this module * destroys every reference in it. An anchored read answers `[]` for both, so this module
* passes it an `onUnreadable` reporter and asks which one happened. * passes it an `onUnreadable` reporter and asks which one happened.
* *
* What it no longer means, since deposits became consumed on 2026-08-26, is "and a later * What it no longer means, since deposits became consumed on 2026-08-26, is "and a later
@@ -2,7 +2,7 @@
* open-repo — cold-start repo opening for the ANCHORED read path (polyfill-era). * open-repo — cold-start repo opening for the ANCHORED read path (polyfill-era).
* *
* ── The cold-start defect this heals ────────────────────────────────────── * ── The cold-start defect this heals ──────────────────────────────────────
* The anchored read path (`surface/read-model.ts` `readDoc`, * The anchored read path (`surface/read-document.ts` `readDocumentSubjects`,
* `shared-wallet/account-registry.ts` `readUserStore`) assumes the target repo is * `shared-wallet/account-registry.ts` `readUserStore`) assumes the target repo is
* already usable by the verifier — true within the session that CREATED the doc * already usable by the verifier — true within the session that CREATED the doc
* (every `doc_create` opens it), but FALSE on a FRESH session over the same * (every `doc_create` opens it), but FALSE on a FRESH session over the same
@@ -19,9 +19,10 @@
* the verifier: on a persistent profile `Verifier::load` repopulates `self.repos` from * the verifier: on a persistent profile `Verifier::load` repopulates `self.repos` from
* user storage at construction (`engine/verifier/src/verifier.rs:535-560`), so the repo * user storage at construction (`engine/verifier/src/verifier.rs:535-560`), so the repo
* is PRESENT but unsynced and the anchored query legitimately matches nothing; and this * is PRESENT but unsynced and the anchored query legitimately matches nothing; and this
* library's own `readDoc` catches every error and returns `[]` * library's own per-document read caught every error and returned `[]`
* (`surface/read-model.ts:122`), so anything that did throw would reach the caller as * (`surface/read-model.ts`'s `readDoc`, until `1.0.0-dev.8` removed it along with the
* emptiness anyway. The fix below is right; the diagnosis written beside it was not. * batch it served), so anything that did throw reached the caller as emptiness anyway.
* It no longer does: `surface/read-document.ts` lets the rejection through. The fix below is right; the diagnosis written beside it was not.
* *
* The circularity that made this self-inflicted: `doc_subscribe` WOULD open the * The circularity that made this self-inflicted: `doc_subscribe` WOULD open the
* repo, but the reactive layer only subscribes AFTER the listing produced NURIs — * repo, but the reactive layer only subscribes AFTER the listing produced NURIs —
@@ -165,7 +165,7 @@ export async function assertMayWrite(nuri: Nuri, op: string): Promise<void> {
} }
/** /**
* **Rule 2 — do not even attempt**, at the CALLERS (`read-model`, `open-repo`, * **Rule 2 — do not even attempt**, at the CALLERS (`open-repo`,
* `subscribe`'s callers…). * `subscribe`'s callers…).
* *
* A reader that does not hold a document's cap must not issue the operation at all. * A reader that does not hold a document's cap must not issue the operation at all.
@@ -17,7 +17,8 @@
* It is SYNCHRONOUS and decides from what the holder holds at that instant. A document * It is SYNCHRONOUS and decides from what the holder holds at that instant. A document
* in a PUBLIC store hands its cap to whoever asks (`public-store.ts`), but asking is a * in a PUBLIC store hands its cap to whoever asks (`public-store.ts`), but asking is a
* round-trip - so this filter drops such a document until some read path has asked. * round-trip - so this filter drops such a document until some read path has asked.
* The paths that DO ask are the ones this library still reads with (`readUnion`, * The paths that DO ask are the ones this library still reads with (the anchored
* document read,
* `docs.sparqlQuery`, `ensureRepoOpen`, `documentInboxAddress`). The two SHAPE reads * `docs.sparqlQuery`, `ensureRepoOpen`, `documentInboxAddress`). The two SHAPE reads
* ask nothing: `useShape` because its signature is the real ORM's and has no await to * ask nothing: `useShape` because its signature is the real ORM's and has no await to
* spend, and `useShapeQuery` since `1.0.0-dev.6`, because it reads through the SDK's * spend, and `useShapeQuery` since `1.0.0-dev.6`, because it reads through the SDK's
@@ -166,7 +166,7 @@ export const STORE_BRANCH_SUBJECT = `${SHIM}:storeBranch`;
/** /**
* Fixed subject of the **Header branch** emulation, inside an ENTITY document — the * Fixed subject of the **Header branch** emulation, inside an ENTITY document — the
* first compartment we put in a document the consumer also reads, hence the filter in * first compartment we put in a document the consumer also reads, hence the filter in
* `read-model.ts` (every `${SHIM}:` subject is machinery and never surfaces as data). * `read-document.ts` (every `${SHIM}:` subject is machinery, and the internal read drops it).
* *
* It carries what must be readable by *whoever can read the document*, as opposed to * It carries what must be readable by *whoever can read the document*, as opposed to
* what belongs to its owner alone. Today that is one thing: the ADDRESS of the * what belongs to its owner alone. Today that is one thing: the ADDRESS of the
@@ -1165,7 +1165,7 @@ export async function createEntityDoc(id: string, scope: StoreType): Promise<Nur
s.sessionId, s.sessionId,
// NO explicit `GRAPH <…>` wrapper: write the anchored DEFAULT graph (the // NO explicit `GRAPH <…>` wrapper: write the anchored DEFAULT graph (the
// `indexDoc` anchor scopes it) — the CANONICAL, always-safe shape the anchored // `indexDoc` anchor scopes it) — the CANONICAL, always-safe shape the anchored
// default-graph read queries (readUserStore below, same as read-model.ts). // default-graph read queries (readUserStore below, same as read-document.ts).
// entityNuri is a NURI stored as a literal → escapeLiteral. // entityNuri is a NURI stored as a literal → escapeLiteral.
`INSERT DATA { <${MAIN_BRANCH_SUBJECT}> <${P.contains}> "${escapeLiteral(entityNuri)}" }`, `INSERT DATA { <${MAIN_BRANCH_SUBJECT}> <${P.contains}> "${escapeLiteral(entityNuri)}" }`,
indexDoc, indexDoc,
@@ -315,7 +315,7 @@ export function configureStoreRegistry(deps: StoreRegistryDeps): void {
// Fire the outbox inspection (Volet 3 of the low-level data-path trace) once, // Fire the outbox inspection (Volet 3 of the low-level data-path trace) once,
// on the FIRST successful `getSession()` resolution — the most reliable // on the FIRST successful `getSession()` resolution — the most reliable
// "a session is established" signal available: every low-level reader/writer // "a session is established" signal available: every low-level reader/writer
// (store-registry, open-repo, read-model, subscribe, inbox) reaches its // (store-registry, open-repo, read-document, subscribe, inbox) reaches its
// session through this SAME injected `getSession`, so wrapping it HERE catches // session through this SAME injected `getSession`, so wrapping it HERE catches
// the first success from whichever caller happens to run first, instead of // the first success from whichever caller happens to run first, instead of
// tying the probe to one particular call site. Only on SUCCESS (an error // tying the probe to one particular call site. Only on SUCCESS (an error
@@ -424,7 +424,7 @@ export function getCurrentUser(): PrincipalId | null {
} }
/** The emulated cap registry — what the current identity holds, plus the emulated /** The emulated cap registry — what the current identity holds, plus the emulated
* public store. The read filter and the read-model consult it. */ * public store. The read filter and the document read consult it. */
export function getCaps(): CapRegistry { export function getCaps(): CapRegistry {
return caps; return caps;
} }
+1 -1
View File
@@ -24,7 +24,7 @@ import type { Nuri, NuriLike } from "../model/types";
// through `sparqlQuery`, every write through `sparqlUpdate` (+ container creation // through `sparqlQuery`, every write through `sparqlUpdate` (+ container creation
// through `docCreate`) — each ultimately calling the real injected `ng` here. The // through `docCreate`) — each ultimately calling the real injected `ng` here. The
// access log is therefore instrumented HERE so no access path escapes it. Callers // access log is therefore instrumented HERE so no access path escapes it. Callers
// pass a semantic `label` (readDoc|readUnion|listMyEntityDocs|writeEntity|deposit // pass a semantic `label` (readDocument|listMyEntityDocs|writeEntity|deposit
// |…); it is a lib-internal probe param, NOT forwarded to the real `ng` (the docs // |…); it is a lib-internal probe param, NOT forwarded to the real `ng` (the docs
// primitives forward the exact SDK signature — see test/docs.test.ts). When the // primitives forward the exact SDK signature — see test/docs.test.ts). When the
// log is OFF (default) the extra param is inert and costs one boolean read. // log is OFF (default) the extra param is inert and costs one boolean read.
+1 -1
View File
@@ -212,7 +212,7 @@ export async function post(targetInboxLike: NuriLike, opts: PostOptions): Promis
// NO explicit `GRAPH <…>` wrapper — write the anchored DEFAULT graph: // NO explicit `GRAPH <…>` wrapper — write the anchored DEFAULT graph:
// `sparqlUpdate(sid, update, targetInbox)` scopes the write to that repo's // `sparqlUpdate(sid, update, targetInbox)` scopes the write to that repo's
// default graph (same shape as read-model.ts readDoc/readUnion). This is the // default graph (same shape as `read-document.ts`'s anchored read). This is the
// CANONICAL, always-safe shape and the one the anchored default-graph read // CANONICAL, always-safe shape and the one the anchored default-graph read
// queries. (Not a round-trip necessity on the current broker: the e2e harness // queries. (Not a round-trip necessity on the current broker: the e2e harness
// `packages/polyfill/e2e/` verified that an anchored `GRAPH <plainNuri>` write // `packages/polyfill/e2e/` verified that an anchored `GRAPH <plainNuri>` write
+1 -1
View File
@@ -137,7 +137,7 @@ export async function createEntityDoc(store?: NuriLike): Promise<Nuri> {
// //
// - `listMyEntityDocs` has no counterpart at any level: there is no `doc_list`, no // - `listMyEntityDocs` has no counterpart at any level: there is no `doc_list`, no
// `list_docs`, nothing in the binding. Listing what one created is an ordinary query // `list_docs`, nothing in the binding. Listing what one created is an ordinary query
// over the store's `ldp:contains` — the same shape `readUnion` is, sugar over // over the store's `ldp:contains` — the same shape the anchored document read is, sugar over
// something already expressible. It stays reachable at `../shared-wallet/account-registry` // something already expressible. It stays reachable at `../shared-wallet/account-registry`
// for this library's own read path, which still calls it until that path queries the // for this library's own read path, which still calls it until that path queries the
// store itself. // store itself.
+1 -1
View File
@@ -86,7 +86,7 @@ import type { Nuri, NuriLike } from "../model/types";
* A push from the platform to a document subscriber. Loosely typed: the raw * A push from the platform to a document subscriber. Loosely typed: the raw
* serialized `AppResponse` (`{ V0: { State | Patch | TabInfo | ... } }`). The * serialized `AppResponse` (`{ V0: { State | Patch | TabInfo | ... } }`). The
* consumer typically ignores the payload and uses the push purely as a * consumer typically ignores the payload and uses the push purely as a
* change SIGNAL (re-query on change — the read-model pattern), so this stays * change SIGNAL (re-query on change — the read model in `docs/read-model.md`), so this stays
* permissive rather than modelling every AppResponse variant. * permissive rather than modelling every AppResponse variant.
*/ */
// @provenance DocChange kind=aligned level=1 ref=engine/net/src/app_protocol.rs:AppResponseV0 — the raw serialized response, left `unknown` rather than modelling every variant // @provenance DocChange kind=aligned level=1 ref=engine/net/src/app_protocol.rs:AppResponseV0 — the raw serialized response, left `unknown` rather than modelling every variant
@@ -22,9 +22,9 @@
* The SDK's own shape query - `RdfOrmSubscription.getOrCreate(shapeType, scope)` - and * The SDK's own shape query - `RdfOrmSubscription.getOrCreate(shapeType, scope)` - and
* nothing of this library's own. Four steps went with the change, and they were one * nothing of this library's own. Four steps went with the change, and they were one
* mechanism: a logical scope resolved to a document set (`listMyEntityDocs`), those * mechanism: a logical scope resolved to a document set (`listMyEntityDocs`), those
* documents opened through `ensureReposOpen` for a sync barrier, `readUnion` over them, * documents opened through `ensureReposOpen` for a sync barrier, one anchored read over
* and a filter of the union by the shape's `@type` IRI. All four existed because the * each of them, and a filter of the result by the shape's `@type` IRI. All four existed
* parameter had to be TRANSLATED; once the parameter is upstream's own `Scope`, the * because the parameter had to be TRANSLATED; once the parameter is upstream's own `Scope`, the
* question goes straight to the thing that answers it. `graphs`, `subjects` and the * question goes straight to the thing that answers it. `graphs`, `subjects` and the
* whole-dataset marker `did:ng:i` are now read by the engine, literally, and this module * whole-dataset marker `did:ng:i` are now read by the engine, literally, and this module
* does not interpret them at all - so the three readings are upstream's, not ours. * does not interpret them at all - so the three readings are upstream's, not ours.
@@ -82,7 +82,7 @@ function unescapeLiteral(s: string): string {
} }
/** A stateful fake `ng`: the shim SPARQL, the inbox SPARQL, and the anchored /** A stateful fake `ng`: the shim SPARQL, the inbox SPARQL, and the anchored
* per-doc `?s ?p ?o` read the read-model uses. */ * per-doc `?s ?p ?o` read the anchored document read uses. */
function makeFakeNg() { function makeFakeNg() {
const quads: Quad[] = []; const quads: Quad[] = [];
let docCounter = 0; let docCounter = 0;
@@ -537,10 +537,11 @@ test("a broker that cannot read the referenced objects costs ONE deposit, not th
fake.quads.filter((q) => q.p === `${INBOX}:payload` && q.o.includes("did:ng:o:")).length; fake.quads.filter((q) => q.p === `${INBOX}:payload` && q.o.includes("did:ng:o:")).length;
expect(queued()).toBe(3); expect(queued()).toBe(3);
// The broker goes quiet on the referenced objects. `read-model.readDoc` SWALLOWS a failed // The broker goes quiet on the referenced objects. The read THROWS, so the arm is told a
// read and answers `[]` — so the arm is TOLD, through the `onUnreadable` reporter, rather // failure happened rather than left to guess it from an empty answer — a distinction the
// than left to guess from an empty answer. Until 2026-08-26 this was classed as an // batch read of the day made with an `onUnreadable` reporter, because it swallowed the
// ordinary refusal: the drain reported it and carried on, popping and destroying all three // failure into `[]`, and `1.0.0-dev.8` makes with the language. Until 2026-08-26 this was
// classed as an ordinary refusal: the drain reported it and carried on, popping and destroying all three
// while printing "will not be retried" for each. // while printing "will not be retried" for each.
const realQuery = fake.sparql_query; const realQuery = fake.sparql_query;
fake.sparql_query = (async (...a: unknown[]) => { fake.sparql_query = (async (...a: unknown[]) => {
@@ -208,7 +208,7 @@ function makeWallet(quads: Quad[] = []) {
const g = wrapped ? wrapped[1]! : anchor; const g = wrapped ? wrapped[1]! : anchor;
const inGraph = quads.filter((q) => q.g === g); const inGraph = quads.filter((q) => q.g === g);
// The whole-document read (`read-model.readDoc`). // The whole-document read (`read-document.readDocumentSubjects`).
if (/SELECT\s+\?s\s+\?p\s+\?o/.test(query)) { if (/SELECT\s+\?s\s+\?p\s+\?o/.test(query)) {
return { return {
results: { results: {
+1 -1
View File
@@ -305,7 +305,7 @@ export function makeWallet(quads: Quad[], options: WalletOptions = {}): FakeWall
if (cold && target !== undefined && !synced.has(target)) return { results: { bindings: [] } }; if (cold && target !== undefined && !synced.has(target)) return { results: { bindings: [] } };
const inGraph = quads.filter((q) => q.g === g); const inGraph = quads.filter((q) => q.g === g);
// The whole-document read (`read-model.readDoc`). // The whole-document read (`read-document.readDocumentSubjects`).
if (/SELECT\s+\?s\s+\?p\s+\?o/.test(query)) { if (/SELECT\s+\?s\s+\?p\s+\?o/.test(query)) {
return { return {
results: { results: {