64 lines
7.3 KiB
Markdown
64 lines
7.3 KiB
Markdown
---
|
|
type: knowledge
|
|
summary: What the target offers for reading many objects — a graph is one document, a store's graph holds only its containment triples, and scoping a read by store is not expressible at all
|
|
last_checked: 2026-09-16
|
|
---
|
|
|
|
# What a read scope is upstream
|
|
|
|
Read at the source in `nextgraph-rs` (`213338f6`). Every claim points at a symbol or a module rather than a line, so `git grep` settles any doubt after the code moves. The question answered here is the one a caller actually asks: **how do I ask for many objects at once, and what can the request say?**
|
|
|
|
## The type, and the two axes it filters on
|
|
|
|
`Scope = { graphs?: string[] | string; subjects?: string[] }` (`sdk/js/orm/src/types.ts`), normalised by `normalizeScope` into `{ graphs: string[]; subjects: string[] }`. Its own documentation states the edges: `["did:ng:i"]` or `[""]` means the whole dataset, while `[]` or `undefined` returns **no objects at all**. Two axes, no third.
|
|
|
|
## A `graphs` entry is one document's graph, never a set of them
|
|
|
|
At the binding, `orm_start_graph` (`sdk/js/lib-wasm/src/lib.rs`) passes each string through `NuriV0::new_from` (`engine/net/src/app_protocol.rs`). That function accepts exactly five forms and rejects everything else: `did:ng:i`; a repo id `did:ng:o:<repo>`; a file read-cap `did:ng:j:<id>:k:<key>`; a repo graph `did:ng:o:<repo>:v:<overlay>`; a branch `did:ng:o:<repo>:v:<overlay>:b:<branch>`. **No string parses to a store target.** And `nuri_to_string` (`engine/verifier/src/orm/graph/utils.rs`) then collapses every non-`UserSite` NURI to a single repo graph name, `did:ng:o:<repo>:v:<overlay>` — one document's graph, whatever went in.
|
|
|
|
## Naming a store's graph reaches its containment triples, not its documents
|
|
|
|
`Verifier::doc_create` (`engine/verifier/src/request_processor.rs`) writes `<store> <http://www.w3.org/ns/ldp#contains> <did:ng:o:doc>` into the **store's** graph, while the new document's own triples live in **its** repo graph. The object of that triple is the bare repo-id form, not even a graph name.
|
|
|
|
So the obvious formulation — *"search my private store"* — resolves to the store's graph, matches the containment triples, and returns **an empty set for the shape being asked about**. Silently: no error exists for it, because a shape query matching nothing is indistinguishable from a scope holding nothing.
|
|
|
|
## `did:ng:i` and `""` replace the whole list
|
|
|
|
In `orm_start_graph`, an entry equal to `""` or `did:ng:i` **discards every other entry** and substitutes `NuriV0::new_entire_user_site()`; `nuri_to_string` renders that back as the string `did:ng:i`. Downstream, `query_quads_for_shape` and `query_graph_subjects` (`engine/verifier/src/orm/graph/query.rs`) test `nuris[0] == "did:ng:i"` — positional, which holds because the binding has already collapsed the list to a singleton — and when it does, they **drop the graph filter entirely**: the emitted SELECT carries no `FILTER(?g …)` and runs over the whole local dataset.
|
|
|
|
Nothing is added to the query to narrow it by what the reader may open. `query_sparql_select` parses the SPARQL and runs it against the verifier's dataset as-is. The only bound is what the verifier has **materialised locally** — documents opened from a held `ReadCap`, `load_repo_from_read_cap` (`engine/verifier/src/verifier.rs`) — not a check performed when the query runs.
|
|
|
|
## The graph filter is literal
|
|
|
|
`schema_shape_to_sparql` (`engine/verifier/src/orm/graph/query.rs`) emits `FILTER(?g IN (<g1>, <g2>, …))` from the list verbatim. **No prefix, pattern or hierarchy matching**: a graph is in the list or it is not. `StorageReader::repos_in_store` (`engine/oxigraph/src/oxigraph/storage/mod.rs`) could enumerate a store's repos — and has **zero callers**.
|
|
|
|
## `entire_store` is the shape to align on, and nothing wires it
|
|
|
|
`NuriV0.entire_store` (`engine/net/src/app_protocol.rs`) is documented *"If it is a store, will include all the docs belonging to the store"*. Every constructor sets it `false`, **nothing anywhere sets it `true`**, and its only three readings are guards (`is_branch_identifier`, `is_valid_for_sparql_update`, `is_valid_for_discrete_update`) that require it false. No code expands it into a graph list.
|
|
|
|
That is `declared-not-wired` in the taxonomy of [[no-divergence-from-nextgraph]]: the target has written down the shape of the question even though nothing answers it yet — and a declared shape is the strongest thing to align on, far stronger than the absence of an implementation is evidence against it.
|
|
|
|
## `subjects` filters the root shape only
|
|
|
|
`query_quads_for_shape` seeds the caller's subject list as the pending set of the **root** shape; every nested shape gets its subjects from what the root query discovered, not from the caller. The emitted filter is `FILTER(?s IN (…))` over root-subject IRIs.
|
|
|
|
## The engine takes more than the surface hands it
|
|
|
|
`OrmConfig` (`engine/net/src/orm.rs`) carries `where_`, `order_by`, `select`, `page_size` and `max_active_pages`, and `schema_shape_to_sparql` accepts a where config, an order-by config and a `(limit, offset)` pair — it emits `ORDER BY`, `LIMIT` and `OFFSET` from them. But `Verifier::start_orm` (`engine/verifier/src/orm/graph/initialize.rs`) builds its config from `json!({})` behind a `// TODO`, so the config every query reads is always empty: **no caller can reach any of it**. The richness is real and unreachable.
|
|
|
|
## "Not ready" and "ready and empty" are distinguishable — one level down
|
|
|
|
`OrmSubscription` (`sdk/js/orm/src/connector/GraphOrmSubscription.ts`) is exported from the package entry — `core.ts`, which `@ng-org/orm` resolves `.` to — and carries a public `readyPromise` getter. So at that level the two states **are** distinguishable. The React `useShape` adapter drops the distinction: it returns a `DeepSignalSet` and documents it as *"the orm objects or an empty set, if still loading"*. The gap is between level 3's class and level 3's hook, not between us and the target.
|
|
|
|
## What this package does instead
|
|
|
|
`useShape` here is a passthrough (`packages/polyfill/src/surface/use-shape.ts`, annotated `kind=passthrough level=3`): **the scope goes to the injected SDK hook unchanged**, and the rights filter is applied to the *results* before they reach the caller. `makeReadFilteredView` (`packages/polyfill/src/emulated-verifier/read-filter.ts`) keeps only items whose document — their `@graph` — the current holder has a cap for, annotated `kind=aligned level=1 ref=engine/verifier/src/verifier.rs:load_repo_from_read_cap`.
|
|
|
|
That is the emulation of what the broker will bound **at delivery**, done where the target does it: per document, on what comes back. It disappears at migration, with the module — see [[knowledge_what-an-app-deletes-at-migration]].
|
|
|
|
## What follows
|
|
|
|
**Scoping a read by *store* is a question the SDK cannot express.** No string names a store as a read target; the store's own graph holds containment triples and nothing else; the one field that would mean it is declared and unwired. Nor is it a question an application needs to ask: an application names the documents it holds — it cannot discover, it can only follow a reference ([[knowledge_what-an-iri-says-upstream]] for what those references say). **Enumerating documents is only forced if you insist on asking by store.**
|
|
|
|
Where the target does answer, it answers on two axes — by document (`graphs`) and by subject (`subjects`) — and those are the two to build on.
|