Files
ng-eventually/.tasks/done/T01.c-use-shape-query-name-and-scope.md

63 lines
10 KiB
Markdown

---
id: T01.c
title: watchShape becomes useShapeQuery and takes the SDK's Scope
parent: T01
depends_on: [T01.a]
created: 2026-09-16
claimed_by: null
claimed_at: null
blocked: false
---
## Description
`watchShape` becomes **`useShapeQuery`** and takes the SDK's `Scope` — `{ graphs?: string[] | string; subjects?: string[] }` (`sdk/js/orm/src/types.ts`) — in place of the three invented literals `"public" | "protected" | "private"` (`packages/polyfill/src/model/types.ts`).
**The name and the parameter only.** The read path underneath stays as it is in this child; T01.d replaces it. The translation from the SDK `Scope` down to what the current pipeline needs lives here and is **explicitly temporary** — say so beside the code, because *a parameter that must be translated is the tell that a mechanism below it is about to become ours* (`.project/rules/nextgraph/surface-mirrors-the-sdk.md`).
**What stays, and why that is not a fault.** The `ShapeQuery` return shape (TanStack-like: `{ data, isPending, isSuccess, isError, error }`) and the capability filter both survive. That is the **justified** kind of difference: NextGraph's direction, as reported by its developers, is a `useShapeQuery` returning exactly that shape, and deviating *toward* the announced target is alignment. `useShape` (the existing passthrough to the SDK's current hook, `packages/polyfill/src/surface/use-shape.ts`) is a different symbol and keeps its own life — do not collapse the two.
**Facts — cite them, do not re-derive them.** `.project/concepts/app-contract/knowledge_what-a-read-scope-is-upstream.md`: a `graphs` entry is **one document's graph**, never a set of them; `did:ng:i` or `""` discards every other entry and means the whole local dataset **with no permission narrowing**; a store's graph holds only its containment triples, so *"search my private store"* returns empty **silently**; `[]` or `undefined` returns no objects at all; the graph filter is literal, with no prefix or hierarchy matching; **scoping a read by store is not expressible at all**.
**The boundary that must not be lost.** The store axis survives for **creation** — `docs.docCreate` takes a destination upstream, and `storeRegistry.createEntityDoc(scope)` keeps its three literals — and dies for **reading**.
**Open question — settle it in the open, or escalate.** After this change two different types would want the name `Scope`. The published `Scope` today is the three literals and is used by `storeRegistry.createEntityDoc / listMyEntityDocs / resolveScopeGraph / resolveWriteGraph`. The SDK's `Scope` is the object. The rule says publish the SDK's names, so the SDK's type should take the name and the **placement axis needs a name of its own** — but what that name is, and whether `listMyEntityDocs` (a *read* keyed by the store axis) survives at all, is settled by no leaf. Propose it in `## Progress`; if `listMyEntityDocs`'s fate is genuinely in question, **stop and ask** rather than removing a published symbol on your own judgment.
**This child owns the contract edit and the version bump for its own change.** Edit `.project/concepts/app-contract/polyfill-surface/contract_polyfill-surface.md`: `## Surface` (the signature, the `Scope` type block), `## Non-guarantees` (every mention of `watchShape`), `## Change policy` (the new pre-release string and what it narrows). By the contract's own `## Change policy` this is a **major** — a published symbol is removed, and an existing call changes a parameter a caller must react to. No other child edits the contract for this change; do not touch the sections another child owns.
**Also in scope:** `packages/polyfill/src/surface/watch-shape.ts`, `packages/polyfill/src/index.ts` (export and the comments around it), `packages/polyfill/src/model/types.ts`, `test/vocabulary.test.ts` and `test/app-surface.test.ts` (they pin the export inventory and the vocabulary), `test/watch-shape.test.ts`, `test/watch-shape-surfaces-failure.test.ts`, `docs/api-contract.md`, `packages/polyfill/README.md`, `packages/polyfill/docs/polyfill-reference.md`, `examples/notebook/app.ts`, and the `packages/polyfill/e2e/` callers.
**The annotation.** `useShapeQuery` has zero occurrences in `nextgraph-rs`; its justification is NextGraph's own direction as stated by its developers. Read T01.a's `## Result` and use the form it installed. Do **not** cite an approximate symbol — that is the `readUnion` failure this whole task exists to undo.
**Hard gate:** `bun test` → 0 fail; `bunx tsc --noEmit -p packages/polyfill/tsconfig.json` → exit 0; `bunx tsc --noEmit -p packages/polyfill/e2e/tsconfig.json` → exit 0; `python3 ~/.claude/skills/concept/lint.py` → 0 ERROR. Anti-cheat: no `any`, no `@ts-ignore`, no deleting or skipping a test, no widening an assertion.
**Resolved since planning (2026-09-16).** Our `Scope` type is NOT renamed — it DISAPPEARS. A renamed invented name is still an invented name. This extends to creation: the earlier note that "the store axis survives for creation" was preserving an invention because it looked necessary. Upstream `docCreate(sessionId, crdt, cls, dest, store?)` does take a store, so the NOTION survives; what is invented is our three-literal encoding. The creation path aligns on `docCreate`'s own parameters. **`listMyEntityDocs` is DELETED**: no upstream equivalent exists (no `doc_list`, no `list_docs`, nothing in the binding), and listing one's documents is an ordinary query over the store graph's `ldp:contains` — sugar over an expressible query, the same shape as `readUnion`.
## Progress
**2026-09-16 — the open question, settled in the open.** The planning note left two things unsettled: what the placement axis should be called once the SDK's `Scope` takes the name, and whether `listMyEntityDocs` survives. The Resolved block answered the second (deleted) and refused a rename of the first (`Scope` disappears, a renamed invented name is still invented). That forces a third answer the note did not ask for, because `resolveScopeGraph` and `resolveWriteGraph` took the same three literals and could not keep them.
**What the three literals actually are.** They are not this package's invention as VALUES: `local_broker::doc_create(session_id, crdt, class_name, destination, store_type, store_repo)` takes a `store_type` of exactly `"public" | "protected" | "private"` and turns it into a `StoreRepo` with `from_type_and_repo`. What was invented is publishing them under the name `Scope`, for a READ. So the type survives as `StoreType`, DEFINED in `model/types.ts` and published nowhere - the standing `ReadCap` and `InboxScope` already have.
**How an application names a store now.** By its REFERENCE, which is what `doc_create` takes and what upstream's `Session` hands out (`private_store_id`, `protected_store_id`, `public_store_id`). Three accessors, one per store, and `createEntityDoc(store?)` takes one of them - private when omitted, as upstream. A reference that is not one of the connected identity's three is refused rather than silently placed.
Verified obstacle, which is why the reference is the per-store INDEX document and not the session store NURI: in this emulation public and protected are co-located on one physical store id (`store-registry.test.ts` pins `resolveScopeGraph("public") === resolveScopeGraph("protected")`), so a session store NURI cannot tell the two apart.
**`resolveScopeGraph` unpublished, not re-parameterised.** Its stated use was a READ scope, and `knowledge_what-a-read-scope-is-upstream` establishes that a store's graph holds its containment triples and nothing else - so upstream that query matches nothing and says so with an empty set. `resolveWriteGraph` returned the very reference the three accessors return. Both stay internal; the e2e isolation probe reaches `resolveScopeGraph` by its internal path, which is what a harness may do.
**The translation, and why it is only half a translation.** `graphs` naming documents needs none - it IS the doc set. Only `did:ng:i` / `""` does, and it is served by enumerating the identity's documents across the three stores. `subjects` is applied literally to the union's subject IRIs, as `FILTER(?s IN (…))` is upstream. `[]` / `undefined` returns no objects at all, as `Scope`'s own documentation states.
**The example application is where the removal bites.** `myNotes(scope)` was `listMyEntityDocs` + `readUnion`, and there is no per-store read to replace it with. It now holds ONE `useShapeQuery` over `{ graphs: "did:ng:i" }` and writes an `rdf:type` triple so the shape can match. The shelf selector governs where the next note is WRITTEN and nothing else. Every list assertion in the applicative e2e is an `includes` positive or a cross-identity negative, so all of them survive a global list.
## Result
**Done.** `watchShape` is `useShapeQuery` and takes the SDK's `Scope` (`{ graphs, subjects }`, re-exported from `@ng-org/orm`); `ShapeQuery` and the capability filter are unchanged. The annotation is `announced-not-written`, copied verbatim from the rule. Our three-literal `Scope` is gone from the published surface: it is `StoreType`, defined in `model/types.ts`, published nowhere. A store is now named by its REFERENCE - `storeRegistry.privateStore() / protectedStore() / publicStore()` - and `createEntityDoc(store?)` takes one, private when omitted, as `doc_create` does. `listMyEntityDocs`, `resolveScopeGraph` and `resolveWriteGraph` are unpublished (still internal). Contract leaf rewritten with a symbol-by-symbol migration note; `1.0.0-dev.5`.
**Files.** `packages/polyfill/src/{index,model/types,surface/placement,surface/use-shape-query,shared-wallet/account-registry,emulated-verifier/caps,emulated-verifier/branch-registers}.ts`, 13 test files, `e2e/{polyfill-entry,run,notebook}.ts`, `examples/notebook/app.ts`, `docs/api-contract.md`, both READMEs, `contract_polyfill-surface.md`, `package.json`.
**Gate.** `bun test` 401 pass / 0 fail; both `tsc` exit 0; concept lint 0 ERROR; grep on `index.ts` no match.
**Surprises.** VERIFIED: the three literals are upstream's own `store_type`, so only the NAME was invented. VERIFIED: public and protected share one store id here, so the store reference must be the index document. VERIFIED: `listMyEntityDocs` is still the read path's - T01.d removes the last caller.
**Follow-ups.** The whole-dataset read now opens three store documents instead of one. `surface-mirrors-the-sdk.md` still calls the rename "not yet applied". `docs/internal-contract.md`, `docs/simulation.md` and the root `README.md` still name the removed symbols.