Files
ng-eventually/.tasks/done/T01.d-use-shape-query-reads-through-the-sdk.md
2026-09-21 11:41:31 +02:00

8.4 KiB

id, title, parent, depends_on, created, claimed_by, claimed_at, blocked
id title parent depends_on created claimed_by claimed_at blocked
T01.d useShapeQuery reads through the SDK's shape query T01
T01.c
2026-09-16 T01.d-worker 2026-09-21 false

Description

useShapeQuery stops resolving a logical scope, enumerating documents, awaiting a sync barrier and calling readUnion, and reads through the SDK's shape query instead. The ShapeQuery snapshot and the capability filter survive; the home-grown pipeline goes.

What "the pipeline" is, concretely. packages/polyfill/src/surface/watch-shape.ts owns four steps today: resolve the logical scope to a document set (listMyEntityDocs in shared-wallet/account-registry.ts), open them through ensureReposOpen / getSyncState (emulated-verifier/open-repo.ts) which is the sync barrier isPending waits on, call readUnion(docs), then filter the union by the shape's @type IRI. All four are the mechanism a translated parameter forced into existence, and all four are what this child removes.

A blocker to resolve BEFORE implementing, not after. .project/concepts/app-contract/bug_useshape-live-read-needs-a-newer-broker.md is an open MAJOR bug: the SDK's ORM subscription never starts against the broker deployed today — orm_start_graph is missing from two hand-maintained, independently-copied dispatch tables in any build made before commit d5ecd0fd — and the failure is an eternal pending set with one console.error as its only trace, indistinguishable from "this scope holds nothing". That leaf states explicitly that watchShape is not affected, because it is built on doc_subscribe (continuously present in both tables under a stable name), and the contract's ## Non-guarantees tells applications to use watchShape for a reactive read for exactly that reason.

So this change takes the one reactive read that works and puts it on the path that does not, and the workaround the contract publishes stops existing. Establish what happens to that guarantee — a broker redeployment, a documented narrowing of the contract, a fallback — before writing the change. If it cannot be settled from the leaves, stop and ask. Do not implement and discover the consequence afterwards.

isError has no counterpart at any level, and that is recorded rather than assumed: OrmSubscription's readiness promise is built with resolve alone, nothing rejects it, and resolveReady() runs on one path only — the arrival of initial data — so upstream's "I could not find out" is its "still pending" (annotation on ShapeQuery in watch-shape.ts; docs/api-contract.md § 6). Whatever replaces the pipeline must still be able to publish isError, or dropping it is a contract narrowing of its own that has to be declared.

Facts — cite them, do not re-derive them. .project/concepts/app-contract/knowledge_what-a-read-scope-is-upstream.md: the two axes and nothing else; the graph filter is literal; did:ng:i drops the graph filter entirely and runs over the whole local dataset with nothing added to narrow it by what the reader may open; the only bound is what the verifier has materialised locally; subjects filters the root shape only; OrmSubscription.readyPromise distinguishes not-ready from ready-and-empty at level 3 while the React useShape adapter drops that distinction. Also relevant: makeReadFilteredView (packages/polyfill/src/emulated-verifier/read-filter.ts) is the emulation of what the broker will bound at delivery, applied per document on what comes back — that is where the rights filter belongs, and packages/polyfill/src/surface/use-shape.ts already shows the shape of it.

Leave readUnion alone here. After this child watch-shape.ts no longer calls it, but packages/polyfill/src/emulated-verifier/index-deposit.ts:253 and :304 still do. Removing the symbol is T01.f's work.

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 — ## Guarantees and ## Non-guarantees first, because that is where a narrowing shows up, then ## Surface, then ## Change policy. Apply the contract's own rules to decide the level rather than assuming one. No other child edits the contract for this change.

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). useShapeQuery does NOT delegate to the SDK's useShape — that return type cannot carry the request state, which is the whole point. It KEEPS watchShape's implementation: the observable and the state tracking are ours. What changes is the PARAMETER (the SDK's Scope) and what is queried underneath (the SDK's shape query instead of readUnion). isError stays. The reasoning "it survives only if the SDK exposes an error channel" is wrong: the polyfill's mission is to ENRICH the SDK in its announced direction, not to be bounded by what it exposes today. Wire real errors if you can; otherwise the field stays and is filled later.

Progress

  • Blocker confirmed LIFTED before writing anything: the deleted bug leaf's premise was the pinned packages, and RdfOrmSubscription.getOrCreate is reachable at @ng-org/orm@0.1.2-alpha.21.
  • Read the doctrine first (surface-mirrors-the-sdk, no-divergence-from-nextgraph, knowledge_what-a-read-scope-is-upstream), then the SDK's installed typings to establish what the shape query offers: readyPromise (pending vs ready-and-empty), signalObject, addChangeListener, close.
  • Established, by reading dist/connector/RdfOrmSubscription.js, that orm_start_graph failing is caught and only console.error'd and that readyPromise is built with resolve alone. So the ONLY real error channel on this path is the query refusing to open; isError is wired to that and the rest is recorded rather than invented (no timeout was added).
  • Chose injection over a runtime import: the package never hard-imports the SDK, and useShape is React's hook, unusable from an observable. configure({ ormSubscription }) mirrors the ng / useShape fields beside it, and RdfOrmSubscription.getOrCreate is assignable to it with no wrapper (probed with tsc before committing to the type).
  • Rewrote both suites against a new test/orm-fake.ts - a small engine honouring the two scope axes the way schema_shape_to_sparql does, so the scope tests still prove something. Nothing deleted: 7 -> 11 and 12 -> 15 tests.
  • The e2e harness now injects the REAL ORM, so the two useShapeQuery journeys measure the deployed broker. First run died in the broker login (a transient before any assertion); the second passed 42/42.

Result

Done. useShapeQuery reads through the SDK's shape query (RdfOrmSubscription.getOrCreate, injected as configure({ ormSubscription }); absent, the read publishes isError naming it). Scope resolution, the ensureReposOpen barrier, readUnion and the @type filter are gone; graphs/subjects/did:ng:i reach the engine verbatim. The observable, snapshot, state tracking and capability filter stay ours. data items are the shape's own objects and shapeType is ShapeType<T>. isError stays, wired to the one real failure - the query refusing to open. A start the ENGINE refuses is swallowed upstream, so it reads isPending for ever: recorded in code and contract, no timeout invented. userStoreDoc deleted; liveStartWithConfig retired. 1.0.0-dev.6, major.

Files. packages/polyfill/ src (7), test (4, incl. new orm-fake.ts), e2e (4), package.json, README.md; docs/{api-contract,internal-contract}.md; the contract leaf. Out of boundary, deliberately: examples/notebook/ + bun.lock - the change silently broke the reference consumer.

Gate. bun test 408 pass / 0 fail; both tsc exit 0; lint 0 ERROR; 42 passed, 0 failed, 42 total - batch 3.3 min.

Surprises. VERIFIED: listMyEntityDocs is NOT dead - its own suite and an e2e journey call it. VERIFIED: bun.lock was stale before this; bun install synced it.

Follow-ups. readUnion keeps two index-deposit.ts callers (T01.f). knowledge_what-the-orm-writes-and-deletes-upstream.md links the deleted bug leaf. watchShape survives as a stale name in ~10 src comments.