--- 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 ## Result