7bcb52a5fd
Deux feuilles affirmaient que la richesse de la config ORM est perdue au
niveau 1 - "no caller can reach any of it. The richness is real and
unreachable". La preuve invoquee etait le `json!({})` de
`Verifier::start_orm` a 213338f6, et elle ne porte pas cet enonce.
Ce qui est verifie a 213338f6 : `OrmConfig` declare where_, order_by,
select, page_size et max_active_pages ; `from_json` les analyse tous les
cinq et refuse un pageSize sans orderBy ; schema_shape_to_sparql et le
pagineur les consomment. La machinerie est implementee. Il manque un
seul saut, a l'ENTREE : start_orm ne prend aucun argument de config, et
son TODO voisin nomme le maillon absent, "use json in libwasm context".
Or ce maillon existe dans le build : @ng-org/web@0.1.2-alpha.14 publie
orm_start_graph avec SIX parametres, config en position 4, quand l'arbre
cite en declare CINQ, et @ng-org/orm@0.1.2-alpha.21 l'appelle avec six
en passant tout son objet d'options. Lus dans les artefacts publies.
Donc la config quitte le niveau 3 et est acceptee au niveau 2 aux
versions installees. Ce que le moteur derriere ce binding en fait n'est
etabli par rien d'atteignable, et c'est dit ainsi.
79 lines
9.8 KiB
Markdown
79 lines
9.8 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-24
|
|
---
|
|
|
|
# What a read scope is upstream
|
|
|
|
Read at the source in `nextgraph-rs` (`213338f6`), which declares OLDER releases than this repo installs - the `ANCHOR` in `packages/polyfill/test/provenance.test.ts` holds the gap and checks both its ends. 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` at `213338f6`), normalised by `normalizeScope` into `{ graphs: string[]; subjects: string[] }`. **In the installed `@ng-org/orm@0.1.2-alpha.21`, `graphs` is REQUIRED** (`graphs: string[] | string`, `dist/types.d.ts`); `subjects?` stays optional. A caller that omitted `graphs` no longer compiles.
|
|
|
|
**The two axes read `[]` in OPPOSITE directions, and this is the trap.** Upstream documents each field separately, and they do not agree:
|
|
|
|
| field | `["did:ng:i"]` or `[""]` | `[]` or `undefined` |
|
|
|---|---|---|
|
|
| `graphs` | the whole dataset | **no objects at all** - the EMPTY set |
|
|
| `subjects` | n/a | **no filtering** - every subject |
|
|
|
|
So `{ graphs: [], subjects: [] }` returns nothing, and it is `graphs` alone that decides it. Reading the pair as symmetrical is how a read that should have been unscoped becomes a read that matches nothing, silently, since a shape query matching nothing is indistinguishable from a scope holding nothing. 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.
|
|
|
|
## How far `where`, `orderBy` and `pageSize` are known to travel
|
|
|
|
`OrmConfig` (`engine/net/src/orm.rs`) carries `where_`, `order_by`, `select`, `page_size` and `max_active_pages`; `OrmConfig::from_json` parses all five and refuses a `pageSize` greater than zero without an `orderBy`; and every one of them is consumed downstream - `schema_shape_to_sparql` takes the where config, the order-by config and a `(limit, offset)` pair and emits `FILTER`, `ORDER BY`, `LIMIT` and `OFFSET` from them, while `initialize.rs` drives its paging window and its page eviction from `page_size` and `max_active_pages`. The config machinery is implemented, not stubbed.
|
|
|
|
One hop is missing, at the ENTRY: `Verifier::start_orm` takes no config argument at all - `request_processor.rs` calls it with three - so it builds one from `json!({})` behind a `// TODO`. `OrmConfig::from_json` names the missing hop in its own TODO, *"Use json in libwasm context"*, and that is exactly what is absent there: `orm_start_graph` (`sdk/js/lib-wasm/src/lib.rs`) declares FIVE parameters at this commit, with no slot for a config.
|
|
|
|
**That absence describes `213338f6` and nothing later, and the installed releases are past it.** `@ng-org/web@0.1.2-alpha.14` ships `orm_start_graph` with SIX parameters, `config` at index 4, and `@ng-org/orm@0.1.2-alpha.21` calls it with six, passing its whole options object into that slot - both read in the shipped artifacts, not inferred. So at the releases this repo installs the config LEAVES level 3 and is ACCEPTED at level 2. Whether the engine behind that binding feeds it to `from_json` is **settled by nothing reachable**: the shipped wasm carries no source, and this tree is demonstrably older than the binding it would be asked about. Only a live measurement answers it, and none has been made.
|
|
|
|
Reading the `json!({})` as *"level 1 discards the richness"* is inference from an absent implementation, which [[no-divergence-from-nextgraph]] forbids - and it was written here once, as *"no caller can reach any of it"*.
|
|
|
|
## "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. In the installed alpha.21 both the symbol and its file are renamed, to `RdfOrmSubscription` and `connector/RdfOrmSubscription.ts`, and it gains an `isReady` getter that answers the same question without awaiting. 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.
|