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
+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
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
`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
*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
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
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
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
@@ -185,21 +185,24 @@ sync an unknown repo.)
> 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.
## 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
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
For each doc NURI in the by-need set, run one anchored
`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
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
the anchor doc NURI; the result is grouped per subject (keeping the `UnionSubject[]`
shape: `subject`, `graph`, `props`). A ReadCap gate drops any doc the current user
may not read (defence-in-depth). The consumer application maps the result to its
types (e.g. its own `readEntities`). Reactivity = the consumer application re-calls
`readUnion` on its change signal (no reactive union query exists).
union-scan. Subjects come back as the document carries them: an entity written under
the document's own NURI reads back under it, and a document carrying several subjects
yields several. A ReadCap gate refuses any doc the current user may not read, at the
door of `docs.sparqlQuery`. Reactivity = the caller re-runs the reads on its change
signal (no reactive union query exists).
> The name `readUnion` / `UnionSubject` is historical (it once ran a union query).
> The read is now per-doc anchored, bounded to the read-set — the "union" is only
> the logical concatenation of the per-doc results, never an anchorless graph scan.
> **`readUnion` / `UnionSubject` were published for this until `1.0.0-dev.8`, and are
> gone.** They were the loop above, plus policies of this package's own: a document that
> 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.