18 KiB
id, title, parent, depends_on, created, claimed_by, claimed_at, blocked
| id | title | parent | depends_on | created | claimed_by | claimed_at | blocked | |
|---|---|---|---|---|---|---|---|---|
| T01.f | Remove readUnion, and the documents that cite it | T01 |
|
2026-09-16 | T01.f-worker | 2026-09-21 | false |
Description
Remove readUnion — and UnionSubject with it, if no published signature still uses it — from the published surface entirely, once nothing internal calls it. The remaining internal single-document reads become anchored SPARQL queries.
Why it goes. It has no counterpart upstream: it is N calls plus a batch policy. Its head annotation reads kind=aligned level=2 ref=sdk/js/lib-wasm/src/lib.rs:sparql_query — a citation that is exact, attached to a claim that is false, which is the worst failure mode of the annotation mechanism because a reader checks it, finds it correct, and moves on (.project/rules/nextgraph/surface-mirrors-the-sdk.md). A composition cannot be aligned on a single function.
Call sites at planning time — verify, they may have moved. In src: packages/polyfill/src/emulated-verifier/index-deposit.ts:253 and :304, both single-document reads; packages/polyfill/src/surface/watch-shape.ts:351, which T01.d should have removed — confirm it is gone rather than assuming. Outside src: examples/notebook/app.ts:108 and :130; packages/polyfill/e2e/polyfill-entry.ts:402, 425, 427, 675. Tests that exercise it: test/read-model.test.ts, test/open-repo.test.ts, test/cross-user-access.test.ts, test/reload-own-document.test.ts, test/watch-shape.test.ts, test/inbox-dispatch.test.ts, test/listing-surfaces-failure.test.ts, test/deferred-inbox-processing.test.ts. The anti-cheat applies in full: a test that proved behaviour through readUnion is re-pointed at whatever replaces it, never deleted and never weakened. test/open-repo.test.ts in particular uses it as the only integration path onto ensureRepoOpen — that coverage has to survive by another route.
Anchored, not anchorless. The mechanics are level-1 VERIFIED and recorded in docs/api-contract.md § 6: an anchor restricts the query to that repo's graph as default graph (resolve_target_for_sparql, engine/verifier/src/request_processor.rs), while an anchorless query unions every named graph in the session store. Replacing several anchored reads with one anchorless query would be a silent widening of what a read can reach — do not do it.
Two documents cite it, and both must be revised.
.project/concepts/app-contract/polyfill-surface/contract_polyfill-surface.md—## Surface(the signature and theUnionSubjectinterface) and## Non-guarantees, where the entry for theuseShapebroker bug currently tells applications "UsewatchShapefor a reactive read andreadUnionfor a one-shot one — neither goes through that path". WithwatchShaperenamed (T01.c), possibly re-pathed (T01.d), andreadUniongone, that published workaround for an open MAJOR bug no longer exists. Say what replaces it, or say plainly that nothing does. Do not leave the sentence standing.- The parent task states that
ng-helpers' contract citesreadUnionand must be revised. At planning time a grep of.project/concepts/e2e-harness/ng-e2e-helpers/found no such citation — neithercontract_ng-e2e-helpers.mdnorusage_polyfill-e2e.mdnames it — while the e2e code underpackages/polyfill/e2e/uses it heavily. Find where the citation actually lives before revising anything; if it turns out to be only the code, record that in `## Result
Status: done. 1.0.0-dev.8, major. Two commits: 0c21ade (the removal) and 0a2b8b1 (the documents).
The opening barrier in useShapeQuery was NOT added
The orchestrator first framed the blocker as "no shape read serves a public index", then withdrew it: an index is read BY ANCHOR, not by shape, and docs.sparqlQuery already fetches the public store's key at its door (packages/polyfill/src/surface/docs.ts:155). The barrier had been written and verified against a new test; both were reverted, uncommitted, on instruction. use-shape-query.ts carries one comment edit in this task and nothing else.
What unblocks the removal, and how it was verified
VERIFIED: packages/polyfill/test/cross-user-access.test.ts — "a bare reference is enough for a PUBLIC document, and not for a protected one". It now asserts, under Bob, that he holds no capability for either document, reads the public one through the anchored docs.sparqlQuery, and holds its key afterwards — obtained from the document, not handed across by the test. That is the property readUnion was protecting, and it belongs to the primitive underneath it.
What was removed, and what each call site became
readUnion and UnionSubject are gone from the entry. packages/polyfill/src/surface/read-model.ts is deleted; packages/polyfill/src/surface/read-document.ts replaces it with readDocumentSubjects(doc) — ONE anchored query, INTERNAL, kind=invention (the anchored query itself is docs.sparqlQuery, annotated there; what is ours is the cold-start open and the row grouping).
index-deposit.ts(two sites) — theonUnreadablecallback became acatch; both still returnunreadable, andnoRowsMeansAbsentstill consultsgetSyncState, which is why theensureRepoOpenin front of the read cannot move to the caller.examples/notebook/app.ts,packages/polyfill/e2e/polyfill-entry.ts— each composes its own read over the publisheddocs.sparqlQuery. The two multi-document e2e sites carry a VISIBLEcatchand report what they skipped.- Tests re-pointed, none deleted or weakened.
open-repo.test.tskeeps its integration path ontoensureRepoOpenby the same route.read-model.test.ts→read-document.test.ts: three cases that tested the batch POLICY were re-pointed at what replaced it (a failed read throws; an empty document answers[]; two reads never merge), and the file header says which and why.
The per-document tolerance
RELOCATED to the caller, not lost — and shedding it is an alignment gain, since upstream an anchored query on a repo the verifier does not hold rejects (resolve_target_for_sparql → NgError::RepoNotFound). No tolerant batch helper was introduced anywhere.
Two behaviour changes, both recorded in the contract
- A document the holder has no capability for is REFUSED out loud instead of dropped from a batch.
- Subjects under the reserved
urn:ng-eventually:prefix are no longer filtered for an application. No rights move: the only registers an ordinary document carries are its inbox address and, for a public document, the key its store already serves to whoever asks; the cap-bearinginboxCapregister lives in its owner's private store.
Does our emulation reject, or answer empty?
VERIFIED: it REJECTS, and it does not swallow. docs.sparqlQuery has no catch — it forwards ng.sparql_query's rejection unchanged — and readDocumentSubjects has none either. The swallow that existed was readDoc inside the deleted module. One nuance, pre-existing: for a document whose public-store cap fetch failed transiently, fetchReadCap returns false and the rejection that reaches the caller is this package's "you hold no cap", not the broker's outage. That conflation is documented in public-store.ts and is not introduced here.
Where ng-helpers cites readUnion
In its OWN published usage_ng-helpers.md against polyfill-surface, under ## Consumed surface and ## Needs — not in any leaf of this repo, and not in contract_ng-e2e-helpers.md / usage_polyfill-e2e.md. The planner's grep was right about where it is NOT; the parent's claim that "ng-helpers' contract cites readUnion" was wrong about which document. Read through the declaration only, never their code.
Follow-ups (not fixed here)
useShapeQuerynever opens the documents named inscope.graphs, so an application naming an unopened or public-store document gets an empty result with no error. Real, out of scope, probably deserves abug_leaf inapp-contract.ensureReposOpennow has no production caller (onlyopen-repo.test.ts). Internal dead code.examples/notebook/tsconfig.jsondoes not typecheck — PRE-EXISTING, verified against the branch tip before any edit: the hand-writtenNoteShapedoes not satisfyShapeType<BaseType>.- The
ng-helpersdeclaration still reports1 passed / 29 failed-era breakage and three Needs this removal does not answer; theiragainst:pin is1.0.0-dev.6and the surface is now1.0.0-dev.8. .project/concepts/app-contract/_debt.mdcarries markers fororm.tsanduse-shape-query.tsraised before this task; left alone.- The dated
docs/briefs/were deliberately not rewritten: they describe a July state that the documents themselves date. - The e2e suite was NOT run (it needs a live broker); it is not part of this task's gate. ` so the parent's claim is corrected rather than silently dropped.
This child owns the contract edit and the version bump for its own change — a published symbol removed is a major by the contract's own ## Change policy. No other child edits the contract for this change.
Also in scope: packages/polyfill/src/surface/read-model.ts (the module goes if nothing else lives in it), packages/polyfill/src/index.ts, test/vocabulary.test.ts and test/app-surface.test.ts, docs/api-contract.md, packages/polyfill/README.md, packages/polyfill/docs/polyfill-reference.md, and the docs/ briefs only where they state something that is now false.
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). Do NOT read ng-helpers — not its code, not its tests. The isolation discipline says a counterpart is reached through its engagement and its usage declaration only. The planner's finding (that no contract leaf cites readUnion) was correct but obtained by a forbidden method. If this task needs to know who depends on readUnion, the answer belongs in a usage_ declaration; ng-helpers has never authored one, and producing that is the fix, not a workaround by reading.
Resolved since planning (2026-09-21) - readUnion HAS a declared consumer. ng-helpers published its usage declaration (polyfill-surface--ng-helpers) and it says YES: e2e/indexing-app.ts calls readUnion([doc]) twice, and src/index.ts REPUBLISHES it to ng-helpers' own consumers as the documented way to read an index. No ORM is wired there (useShape: () => undefined), so useShapeQuery is unreachable for them; their only fallback is an anchored docs.sparqlQuery. They declared this as a Need at full strength.
So the removal is not free. Before removing, decide with the owner what a consumer with no ORM reads with. Their declaration is the channel: read it at ng-helpers' repo through its published usage_ leaf, never by reading their code.
Also relevant, and worse than it looks. That declaration reports ng-helpers still calling createEntityDoc("public") and listMyEntityDocs("public"), both invalid since 1.0.0-dev.5, while its own test suite stays green because it runs against a fake that still models the superseded surface. A consumer can therefore be broken against us with nothing red on either side. Do not treat their green suite as evidence.
Update (2026-09-21) - wiring the ORM does NOT unblock the removal, and the reason is ours. ng-helpers wired the ORM as the contract prescribes and re-measured. Its verdict, recorded in its published usage declaration: neither shape read fetches a PUBLIC STORE's key, so a stranger reading a public index from its reference alone - the central case for an index - is served by no shape query at all. readUnion is the only thing that serves it today.
So the removal is blocked by a capability gap on OUR side, not by a consumer's reluctance. Before removing, either that gap closes or the removal does not happen. Read their declaration for the three secondary costs they list; do not read their code.
Their e2e also went from a measured 1 passed / 29 failed (they were already broken against us, with both suites green) to 28 passed / 2 failed, the two remaining being a provider defect: useShapeQuery never starts because orm_start_graph is not a function, swallowed, isPending for ever - measured on three consecutive runs, with the SDK's ng answering that member and initNg discarding its first argument.
Progress
Result
Status: done. 1.0.0-dev.8, major. Two commits: 0c21ade (the removal) and 0a2b8b1 (the documents).
The opening barrier in useShapeQuery was NOT added
The orchestrator first framed the blocker as "no shape read serves a public index", then withdrew it: an index is read BY ANCHOR, not by shape, and docs.sparqlQuery already fetches the public store's key at its door (packages/polyfill/src/surface/docs.ts:155). The barrier had been written and verified against a new test; both were reverted, uncommitted, on instruction. use-shape-query.ts carries one comment edit in this task and nothing else.
What unblocks the removal, and how it was verified
VERIFIED: packages/polyfill/test/cross-user-access.test.ts — "a bare reference is enough for a PUBLIC document, and not for a protected one". It now asserts, under Bob, that he holds no capability for either document, reads the public one through the anchored docs.sparqlQuery, and holds its key afterwards — obtained from the document, not handed across by the test. That is the property readUnion was protecting, and it belongs to the primitive underneath it.
What was removed, and what each call site became
readUnion and UnionSubject are gone from the entry. packages/polyfill/src/surface/read-model.ts is deleted; packages/polyfill/src/surface/read-document.ts replaces it with readDocumentSubjects(doc) — ONE anchored query, INTERNAL, kind=invention (the anchored query itself is docs.sparqlQuery, annotated there; what is ours is the cold-start open and the row grouping).
index-deposit.ts(two sites) — theonUnreadablecallback became acatch; both still returnunreadable, andnoRowsMeansAbsentstill consultsgetSyncState, which is why theensureRepoOpenin front of the read cannot move to the caller.examples/notebook/app.ts,packages/polyfill/e2e/polyfill-entry.ts— each composes its own read over the publisheddocs.sparqlQuery. The two multi-document e2e sites carry a VISIBLEcatchand report what they skipped.- Tests re-pointed, none deleted or weakened.
open-repo.test.tskeeps its integration path ontoensureRepoOpenby the same route.read-model.test.ts→read-document.test.ts: three cases that tested the batch POLICY were re-pointed at what replaced it (a failed read throws; an empty document answers[]; two reads never merge), and the file header says which and why.
The per-document tolerance
RELOCATED to the caller, not lost — and shedding it is an alignment gain, since upstream an anchored query on a repo the verifier does not hold rejects (resolve_target_for_sparql → NgError::RepoNotFound). No tolerant batch helper was introduced anywhere.
Two behaviour changes, both recorded in the contract
- A document the holder has no capability for is REFUSED out loud instead of dropped from a batch.
- Subjects under the reserved
urn:ng-eventually:prefix are no longer filtered for an application. No rights move: the only registers an ordinary document carries are its inbox address and, for a public document, the key its store already serves to whoever asks; the cap-bearinginboxCapregister lives in its owner's private store.
Does our emulation reject, or answer empty?
VERIFIED: it REJECTS, and it does not swallow. docs.sparqlQuery has no catch — it forwards ng.sparql_query's rejection unchanged — and readDocumentSubjects has none either. The swallow that existed was readDoc inside the deleted module. One nuance, pre-existing: for a document whose public-store cap fetch failed transiently, fetchReadCap returns false and the rejection that reaches the caller is this package's "you hold no cap", not the broker's outage. That conflation is documented in public-store.ts and is not introduced here.
Where ng-helpers cites readUnion
In its OWN published usage_ng-helpers.md against polyfill-surface, under ## Consumed surface and ## Needs — not in any leaf of this repo, and not in contract_ng-e2e-helpers.md / usage_polyfill-e2e.md. The planner's grep was right about where it is NOT; the parent's claim that "ng-helpers' contract cites readUnion" was wrong about which document. Read through the declaration only, never their code.
Follow-ups (not fixed here)
useShapeQuerynever opens the documents named inscope.graphs, so an application naming an unopened or public-store document gets an empty result with no error. Real, out of scope, probably deserves abug_leaf inapp-contract.ensureReposOpennow has no production caller (onlyopen-repo.test.ts). Internal dead code.examples/notebook/tsconfig.jsondoes not typecheck — PRE-EXISTING, verified against the branch tip before any edit: the hand-writtenNoteShapedoes not satisfyShapeType<BaseType>.- The
ng-helpersdeclaration still reports1 passed / 29 failed-era breakage and three Needs this removal does not answer; theiragainst:pin is1.0.0-dev.6and the surface is now1.0.0-dev.8. .project/concepts/app-contract/_debt.mdcarries markers fororm.tsanduse-shape-query.tsraised before this task; left alone.- The dated
docs/briefs/were deliberately not rewritten: they describe a July state that the documents themselves date. - The e2e suite was NOT run (it needs a live broker); it is not part of this task's gate.