feat(client): union-read ReadCap gate + listMyEntityDocs + doc corrections

- read-model.ts `readUnion` now applies the emulated ReadCap gate (drops a
  subject when its doc is governsRead && !canRead for the current identity), so
  per-scope isolation holds by construction AND by filter.
- store-registry: `listMyEntityDocs(username, scope)` (current account only) vs
  the all-accounts `listEntityDocs` fallback (documented as the enumeration to
  avoid on the read path).
- docs: nextgraph-current-state / read-model — corrected to the SOURCE-VERIFIED
  reality that the JS SDK exposes NO open/sync-by-cap primitive
  (load_repo_from_read_cap is pub(crate)); in the mono-wallet all repos are
  already local (same session), so the anchorless union spans them with no open
  step. simulation.md: listEntityDocs+useShape({graphs}) is a fallback, not the
  read path.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Sylvain Duchesne
2026-07-05 20:49:01 +02:00
parent e24f52749f
commit 6a3501e700
5 changed files with 112 additions and 36 deletions
+23 -7
View File
@@ -176,13 +176,29 @@ and the web variant at ~`553-610`) reads the target from the `nuri` arg: a strin
A repo's triples enter `graph_dataset` (hence the union) only after the repo is
opened/synced into `self.repos` **and** its commits applied via `update_graph`.
Paths that open a repo: `doc_create` (own docs), bootstrap /
`load_repo_from_read_cap`, or being followed from a store's `ldp:contains` / a
shared cap / an inbox. Opening requires possessing the repo's **NURI + ReadCap**
there is **no store-level read inheritance** (see § Capability / ReadCap
granularity). *(INFERRED: that "following a graph reference makes a previously
unknown repo known/openable" is the one step not read verbatim in source; the
open-requires-cap and no-inheritance facts around it ARE verified.)*
**VERIFIED (T03.k) — there is NO JS primitive to sync an *unknown* repo.** Every
JS entry point that could "open" a repo — `sparql_query` anchored,
`doc_subscribe` (`Fetch::Subscribe`), `orm_start_graph` — resolves its target via
`resolve_target`/`resolve_target_for_sparql`, which does
`self.repos.get(repo_id).ok_or(RepoNotFound)` (`request_processor.rs:155/163/264/269`).
None of them PULLS a repo that is absent from `self.repos`; they only touch a repo
already there. The primitive that actually loads a repo from its ReadCap,
`Verifier::load_repo_from_read_cap` (`verifier.rs:2237`), is **`pub(crate)`
unexposed to JS**; it is only reached internally (bootstrap, inbox processing). So
from JS today a repo becomes queryable ONLY by being `doc_create`d in this session
(own docs) or synced by an internal path — never on demand by NURI+ReadCap.
**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
already in `self.repos` and queryable by the anchorless union **without any per-doc
open step**. `read-model.ts`'s per-doc anchored `ASK` is therefore a *no-op* on a
present repo and an instant `RepoNotFound` skip on an absent one — it never SYNCS
(it cannot). The union query alone spans all same-session repos. At the real
multi-store migration this gap closes: opening a real per-user store repo by cap is
a native broker sync (the `OpenRepo` TODO at `verifier.rs:1423`), and the open step
becomes a real sync. Opening still requires the repo's **NURI + ReadCap** — there is
**no store-level read inheritance** (see § Capability / ReadCap granularity).
### The union is READ-ONLY — writes must target one document