24 KiB
id, title, parent, depends_on, created, claimed_by, claimed_at, blocked
| id | title | parent | depends_on | created | claimed_by | claimed_at | blocked |
|---|---|---|---|---|---|---|---|
| T01 | Align the published surface on the NextGraph SDK | null | 2026-09-16 | null | null | false |
Description
The polyfill's published surface has drifted from the SDK's: invented names, invented parameters, and symbols with no upstream counterpart. This task brings it back and installs what stops it recurring.
The principle to apply — .project/rules/nextgraph/surface-mirrors-the-sdk.md, and its semantic sibling no-divergence-from-nextgraph.md. Publish the SDK's names, parameters and return values; a difference must be duly justified beside the code, and the best justification is that it is NextGraph's own direction as announced by its developers.
The facts this rests on — .project/concepts/app-contract/knowledge_what-a-read-scope-is-upstream.md. In particular: a graphs entry is one document's graph; a store's graph holds only its containment triples, so "search my private store" returns empty silently; did:ng:i means the whole local dataset with no permission narrowing; scoping a read by store is not expressible at all.
Four pieces of work:
watchShapebecomesuseShapeQuery, takes the SDK'sScope({ graphs?, subjects? }) instead of the three literals"public" | "protected" | "private", and reads through the SDK's shape query instead of enumerating documents. TheShapeQueryreturn shape (TanStack-like) and the capability filter both stay — that difference is the justified kind.- Wrap the ORM family the polyfill does not expose:
getObjectsandinsertObjectat identical signatures with the rights filter; arbitrateOrmSubscription,DiscreteOrmSubscription,ngSession; establish upstream's shape for deletion before specifying it. Today an application must import them straight from@ng-org/orm, and then receives every identity's data from the shared wallet with nothing signalling it — a rights hole, not a missing convenience. - Remove
readUnionentirely once nothing internal calls it. Three single-document reads become an anchored SPARQL query.ng-helpers' contract cites it and must be revised. - The annotation mechanism: a mandatory reference on every function, which must resolve; plus a form for "target announced by the developers, not yet written", which the current format cannot express — so an author holding the strongest justification is pushed to miscite or to understate.
Boundary that must not be lost: the store axis survives for creation (docCreate takes a destination upstream) and dies for reading.
Success criteria: every published symbol either mirrors an SDK symbol in name, parameters and return, or carries a justified annotation; no symbol without an upstream counterpart survives unjustified; gates green (bun test, both typechecks, lint.py 0 ERROR, e2e); and an adversarial review over the whole published surface finds nothing unaddressed.
Progress
Result
Subtasks
-
T01.a A provenance annotation for a target announced but not yet written
-
T01.b What the ORM offers for writing and deleting, and what the three remaining symbols are (reconnaissance)
-
T01.c watchShape becomes useShapeQuery and takes the SDK's Scope (deps: T01.a)
-
T01.d useShapeQuery reads through the SDK's shape query (deps: T01.c)
-
T01.e Publish the ORM family with the rights filter (deps: T01.b, T01.d)
-
T01.f Remove readUnion, and the documents that cite it (deps: T01.e)
-
T01.g Adversarial review of the whole published surface (deps: all)
-
T01.i Clear two mechanical leftovers from the removal (deps: T01.h)
-
T01.j A read that cannot reach its document must fail, not answer empty (deps: T01.i)
-
T01.l pnpm everywhere, and two dependency ranges that are looser than they look (deps: T01.j)
-
T01.m There must be ONE answer to whether a document is open in this session (deps: T01.j)
-
T01.n The polyfill's peer ranges exclude exactly the versions it needs (deps: T01.m)
-
T01.k A provenance citation must say which version it describes (deps: T01.n)
-
T01.o The read filter is bypassed by plain properties, and no fake shows it
-
T01.p Four minor faults on the published surface (deps: T01.o)
Learnings digest
-
T01.a — the taxonomy gains
announced-not-written: itsrefpoints at the.mdin THIS repo where the statement is on record, never a source file, plusannounced-by=<who>@<date>. Upstream citations now resolve-check only when the sibling checkout is present; announced records always. Two latent defects fell out: the test's closed set was missingreserved-name, and the rule said "five kinds" over six rows. All 92 existing refs already resolved. -
T01.b — deletion is answered at level 3 and fully wired:
DeepSignalSet.delete-> a remove patch ->graph_orm_update->SparqlBuilder::remove_object, which removes the SHAPE's predicates, not the subject; no document deletion exists at any level.getObjectsreturns a DETACHED clone;insertObjectthrows without@graph. Proposals: publishOrmSubscriptionas-is (the read filter applies tosignalObject), publish NEITHERDiscreteOrmSubscription(JSON-CRDT, no@graph, nothing for a per-document filter to read) NORngSession(hands out the SDK handle and the session). MEASURED:useShapeis still broken live, and the bug leaf's mechanism was FALSE - the deployment is AHEAD of our pinned@ng-org/*, itsorm_start_graphtakesconfigBEFORE the callback, so the five-argument client lands its callback in the wrong slot. T01.d's blocker STANDS. Also VERIFIED: ORM writes bypasssparql_updateentirely, so the emulated write guard never sees an ORM add or delete. -
T01.c -
useShapeQuerytakes the SDK'sScope, re-exported as a passthrough. Our three literals becameStoreType, internal only: VERIFIED they are upstream's ownstore_type, so only the NAME was invented. A store is now named by its REFERENCE (privateStore()/protectedStore()/publicStore()), andcreateEntityDoc(store?)takes one, private when omitted, asdoc_createdoes.listMyEntityDocs,resolveScopeGraphandresolveWriteGraphunpublished, still internal - T01.d removes the read path's last caller. VERIFIED: public and protected share one store id here, so the reference must be the index document.1.0.0-dev.5. -
Package refresh (2026-09-21) -
@ng-org/*moved to web alpha.14 / orm alpha.21 / shex-orm alpha.9 / alien-deepsignals alpha.13. The live ORM read now STARTS against the deployed broker:6 passed, 0 failed, 6 declared, ready in 16ms, delete reached the document.bug_useshape-live-read-needs-a-newer-brokerDELETED - the deployment was never behind, our packages were. T01.d's blocker LIFTS. Also surfaced:OrmSubscriptionis nowRdfOrmSubscription(T01.e's proposal names the old one);Scope.graphsis REQUIRED and its[]means the EMPTY set whilesubjects: []means NO filtering - the two axes read[]in opposite directions, which T01.d must not get wrong; six e2e files importedng-e2e-helperswithout the polyfill declaring it, fixed asworkspace:*. -
T01.d -
useShapeQueryreads throughRdfOrmSubscription.getOrCreate, injected asconfigure({ ormSubscription }); absent, the read publishesisErrornaming it. Scope resolution, theensureReposOpenbarrier,readUnionand the@typefilter are gone from that path;graphs/subjects/did:ng:ireach the engine verbatim. The observable, snapshot, state and capability filter stay ours.isErrorKEPT, wired to the one real failure (the query refusing to open). A start the ENGINE refuses is swallowed upstream, so it readsisPendingfor ever - recorded in code and contract, no timeout invented.userStoreDocandliveStartWithConfigdeleted. VERIFIED:listMyEntityDocsis NOT dead, its own suite and an e2e journey call it. The change silently brokeexamples/notebook/, repaired out of boundary and deliberately.1.0.0-dev.6, major, 42/42 e2e. -
T01.e -
getObjectsandinsertObjectpublished WRAPPED at upstream's names, parameters and returns. Read is capability-filtered per@graph; the write takesassertMayReach+assertMayWriteBEFORE the call, andgraph_orm_updatenow carries the same ownership guard, so the ORM rights hole is closed. NOT published, each with a checkable reason:RdfOrmSubscription(pooled with no identity in the key),DiscreteOrmSubscription,ngSession,getRaw,watch/effect,removeObject(in alpha.21 but absent from the pinnednextgraph-rs, so no citation resolves).1.0.0-dev.7, MAJOR. ROOT CAUSE FOUND for the consumer'sorm_start_graph is not a function:makeNg()'s passthrough did.bind(ng), and the SDK'sngis an async proxy whosegettrap answers EVERY key includingbindwith a path-carrying callable - so the publishedngreturned a PROMISE for all 88 members. NowReflect.apply, pinned bytest/ng-proxy.test.ts. The e2e only proves it sinceinitNgroutes the ORM through the wrapper - before,polyfill-entry.tshandedinitNgthe raw SDK object, which is why the suite stayed green while the documented path was dead. -
T01.f -
readUnionandUnionSubjectunpublished,read-model.tsdeleted, replaced by an internalreadDocumentSubjects(doc)doing ONE anchored query. The blocker recorded on this task was MISDIAGNOSED: it was stated as "no shape read serves a public index", but an index is read BY ANCHOR, not by shape, anddocs.sparqlQueryalready callsfetchReadCap(anchor)- so the public read already worked anduseShapeQuerynever needed touching. The barrier written for it was reverted. VERIFIED UPSTREAM (engine/verifier/src/request_processor.rs:269,let repo = self.repos.get(repo_id).ok_or(NgError::RepoNotFound)?): an anchored query on a repo the verifier does not know REJECTS, crossing to JS as a rejected Promise carrying"RepoNotFound"(sdk/js/lib-wasm/src/lib.rs:403,606). The observable partition is "inself.repos" vs "not in" - "does not exist" and "not known to me" are INDISTINGUISHABLE, while "known but unsynced" collides with "genuinely empty". No read-capability check exists on that path at all, which confirmsfetchReadCapis emulation machinery with no upstream counterpart. Consequence:readUnion'sonUnreadablewas an INVENTION, not a heritage, so shedding it is an alignment gain; the tolerance is RELOCATED to the callers, with a visiblecatch, and no tolerant batch helper replaced it. Two behaviour changes: an unreachable document is now refused out loud, and an application composing its own raw SPARQL sees theurn:ng-eventually:scaffolding thatreadUnionfiltered - no rights move (VERIFIED:exposeReadCapis called only forscope === "public",account-registry.ts:1205), but a MODEL LEAK the application must unlearn at migration.1.0.0-dev.8, major. Theng-helperscitation ofreadUnionlives in THEIR usage declaration, never in this repo's leaves - the parent's claim named the wrong document. Follow-ups left open:useShapeQueryopens nothing named inscope.graphs,ensureReposOpenis now dead,examples/notebook/tsconfig.jsonfails typecheck (pre-existing). -
T01.h - NO version moved: all four
@ng-org/*were already at their latest published version (web alpha.14, orm alpha.21, shex-orm alpha.9, alien-deepsignals alpha.13). The refresh this task was written for had already happened inside T01.d. What the install DID surface: the tree was BUN-installed, not pnpm-installed - every@ng-orgsymlink pointed intonode_modules/.bun/, which still held web alpha.13, shex-orm alpha.8 and alien-deepsignals alpha.11 BESIDE the current ones. Resolved versions were right, so the single-instance hazard was latent rather than active; reconciled byCI=true pnpm install --no-frozen-lockfile, lockfile unmoved. Gates:424 pass / 0 fail, both typechecks exit 0, lint 0 ERROR,orm-live-read5 passed / 0 failed, e2e43 passed / 0 failed. THE HEADLINE, VERIFIED INDEPENDENTLY: the pinnednextgraph-rscheckout (213338f6) declares orm0.1.2-alpha.18while this repo compiles against0.1.2-alpha.21- THREE releases behind, so everyref=citation in the provenance mechanism describes a version no longer used. Second-order consequence to watch: a claim about alpha.21 REFUTED by reading the pinned tree is refuted against alpha.18, which is not the same statement. Three doctrine leaves corrected (knowledge_what-the-orm-writes-and-deletes-upstream,knowledge_what-a-read-scope-is-upstream,knowledge_what-each-suite-judges), each carrying facts that had become false. Also found: web/shex-orm/alien-deepsignals publish a bare0.1.2that semver ranks ABOVE the alphas but dates from 2025 - exact pins exclude it, butng-e2e-helpers' peer range>=0.1.2-alpha.13would admit it. Follow-ups:pnpm-workspace.yamllists onlypackages/*while rootworkspacesalso listsexamples/*, soexamples/notebookis outside pnpm entirely; the dead.bunstore is still on disk and a futurebun installwould re-point symlinks back into it;playwright1.61.1 -> 1.63.0. -
T01.i -
ensureReposOpendeleted (dead, grep-verified); its 3 tests went with it and that is legitimate, they asserted the BATCH's own properties (parallelism, deduplication, empty input) which no longer have a subject - the 4th, exercisingensureRepoOpensingular, was kept and relocated.424 -> 421 pass, the gap fully accounted. THE LEAD ON THE EXAMPLE WAS WRONG and the agent disproved it: thepnpm-workspace.yamlomission was real but NOT the cause of theTS2345; the actual cause was aNoteShapeliteral missing itssatisfies ShapeType<BaseType>clause, sovalTypewidened from"iri"/"string"tostring. Proven by fixing only that, with the workspace untouched. The workspace gap was fixed anyway because item 3 needed it, and the duplication check came back clean -examples/notebooknow resolves@ng-org/*to the same store paths aspackages/polyfill. Bun store deleted (88M), and the RECURRENCE TRIGGER found:bun.lockwas tracked in git while.gitignoreonly covered the olderbun.lockb; now untracked, both ignored, andpackageManager: pnpm@10.26.0declared. Left alone, reported:docs/internal-contract.md's open-repo section was already stale (wrong line numbers, names a non-existentensurePhysicalRepoOpen) and now also names the deletedensureReposOpen; an orphaned@ng-org/web@0.1.2-alpha.13sits in the pnpm store with zero referencing symlinks. -
T01.j - SPLIT after six independent adversarial rounds. DELIVERED, none of it faulted across those six reviews: the four call sites propagate instead of collapsing (
docs.sparqlQuery,ensureRepoOpen,documentInboxAddress, andfetchReadCap, which stops turning a rejected download intofalse),fetchReadCapsdeleted (dead), theknowledge_what-a-failed-read-answers-upstreamleaf, the empty-is-success guards, e2e named-scope coverage run live by TWO independent reviewers, and the REACHABILITY half of the barrier - a holder of nothing getsisErrorwithdataLen: 0, a public document opens from a bare reference. NOT delivered, and now written into the contract as a non-guarantee pointing at T01.m: the ARRIVAL half holds only within one session, and fails acrossconfigure()and when a broker rejects after the open timeout.1.0.0-dev.9,449 pass / 0 fail, e2e46 passed / 46. THE ROOT CAUSE, which is T01.m's subject: "is this document open in this session" has THREE answers that can disagree -heldandsyncStateinopen-repo.ts, andfanOutsinsubscribe.ts, the one nobody counted. Round 6 replaced one of two disagreeing records; there were three. TWO PROCESS LESSONS worth more than the code. First, a round counter is the wrong stopping rule: rounds 2-5 each fixed the reported corner and opened another, and what finally decided the split was a review producing the COMMON CAUSE rather than one more corner - stop when a review explains, not when a counter trips. Second, the implementer asserted three times that a change had simplified or shrunk something and all three were contradicted by measurement (534->563, 664->727, and a numstat of 34 insertions / 8 deletions for "a condition was removed"); two were relayed to the owner before being measured. The rule now held, and written into T01.m: a structural claim carries itswc -lorgit show --numstatin the same sentence, or it is not written. Also found along the way, each fixed at its source:subscribe.tsalready offeredonSetupFailedfor a refused subscribe and nothing had wired it;resetOpenedReposabandoned in-flight opens without settling them, so a caller waited the full fallback and proceeded with sync state"unknown", whichindex-deposit.noRowsMeansAbsentaccepts as a definitive absence - a latent failure-as-absence bug found by instrumenting a one-in-four flaky test instead of re-running it. -
T01.l -
ng-e2e-helpers' peer range tightened to>=0.1.2-alpha.13 <0.1.2, MEASURED BY RESOLUTION: before, 4 versions admitted withmaxSatisfying= the bare0.1.2of 2025-10-19; after, 3, and pnpm resolves0.1.2-alpha.15. The premise it was handed was half wrong and it said so: pnpm never actually RESOLVED to0.1.2because it prefers thelatestdist-tag, which upstream happens to point at alpha.15 - so the trap was one dist-tag move from firing, and only resolution testing shows that either way.playwright1.61.1 -> 1.63.0 (raisesengines.nodeto>=20, dropsfsevents), e2e46 passed / 46on the first run. Bothtypecheckscripts moved frombunx tsctopnpm exec tsc, VERIFIED with~/.bun/binoff PATH andcommand -v bunempty. MEASURED about the optional peer: a consumer of the packed polyfill receives NO@ng-orgpackage under pnpm, so the oldbun installsemantics survived the tool change - but THIS checkout does get the ORM, throughpackages/polyfill/package.json:33's devDependency, so the runner's "install it in packages/polyfill" was a false lead and both texts now say the optional peer governs what a CONSUMER receives.pnpm linkPERFORMED and it works: the polyfill resolves to the checkout and stays at ONE copy, so the single-instance hazard is untripped - but@ng-org/weband@ng-org/ormend up at TWO copies when the app also imports the SDK, pnpm warning by name, andfile:gives one copy at the cost of repacking, which forfeits live editing. Left alone, reported: the polyfill's own four"*"peers admit ONLY the bare0.1.2of 16 published versions and EXCLUDE every alpha, because*matches no pre-release - inert while the polyfill is unpublished, certain to bite at first publish (now T01.n);examples/notebookhas notypecheckscript;bun buildatorm-live-read.ts:128andbun runinmint-walletare still bun-bound. -
T01.m - THE COUNT WAS ELEVEN, not the three the split had named. Deleted from
open-repo.ts:held,syncState,inFlight,settleInFlight,registryEpoch- the last two IMPLIED openness through a short-circuit and nobody had ever counted them, including six adversarial rounds of T01.j. Surviving as the one answer, insurface/subscribe.ts(NOTemulated-verifier/, which is what the task file wrongly said):fanOuts,establishing,realUnsub,lastState,attempt- andlastStateturned out to have been a second barrier record all along, the correct one. THE INVARIANT, written atopen-repo.ts:100-128and onDocChannel: "is this document open in this session" is answered in ONE place,docChannel(nuri), andopen-reporecords only the bootstrap open it ASKED FOR. Teardown is now safe BY CONSTRUCTION with no cross-module notification added, because nothing outside records openness any more;resetOpenedReposdisowns rather than forgets, since forgetting yields the lax"unknown". CONTRACT: both non-guarantees DELETED and the arrival guarantee unqualified,1.0.0-dev.10, guarantee and test in the same commit - and an INDEPENDENT review attacked that first and hardest and could not break it.462 pass / 0 failsix times, e2e46 passed / 46. My own spec was wrong twice and the agent said so instead of contorting to satisfy it: the module path, and failure B's acceptance test ("2 subscribes in-window"), which is UNACHIEVABLE because the fan-out correctly dedupes. Four adversarial passes found SIX real defects in the agent's own work, including two regressions and a test of its own that asserted the defect. THE LESSON WORTH KEEPING, from the last one: it had the right fix, could not make a test discriminate, and REVERTED it - correctly - but recorded "could not construct an ordering" as an OPEN QUESTION rather than a clean bill. That wording is what let a later pass finish it. The window is exactly ONE microtask hop, swept 0-6: at 0 the clear has not happened so delivery is legitimate, at 1 a forgotten barrier is delivered, at 2+lastStateis already null so nothing is queued. Its earlier fake put the rejection three hops behind the replay, so the replay always won - what it had wrong was the TEST, not the remedy. Also verified by the independent review, and worth knowing because the guarantee is right for a reason nobody had written down:"unknown"is unreachable where a barrier was expected NOT for the stated reason, but becausefetchReadCap->downloadReadCap->ensurePhysicalRepoOpenpopulates the open record BEFOREmustNotAttemptis evaluated, so even the refused path leaves a trace. Follow-ups left open: a never-settlingdoc_subscribeis unrecoverable;public-store.attemptedskips an open afterconfigure;forgetEverythingclears four maps never. -
T01.n - the four
@ng-orgpeer ranges fixed, VERIFIED BY RESOLUTION against the registry with the real node-semver engine rather than by reading. WORSE THAN THE TASK STATED:@ng-org/orm's"*"admitted ZERO of its 22 published versions - that library publishes no bare0.1.2at all, so the range was UNSATISFIABLE, not merely wrong. The other three admitted exactly 1, the stale bare0.1.2of 2025, and excluded every alpha. After: web>=0.1.2-alpha.14 <0.1.2(2 admitted, maxSatisfying alpha.15), orm>=0.1.2-alpha.21 <0.1.2(2), shex-orm>=0.1.2-alpha.9 <0.1.2(2), alien-deepsignals>=0.1.2-alpha.13 <0.1.2(2). The floor was taken PER PACKAGE from its own devDependencies pin rather than copying the sibling package'salpha.13that the task offered as an example - that floor predates its own devDep bump and is a stale artifact, not a rule.peerDependenciesMeta: optionalconfirmed correct for all four and VERIFIED rather than assumed: every@ng-org/*reference inpackages/polyfill/srcisimport typeonly, zero runtime imports, the application injecting the real functions throughbootstrap.ts.examples/notebookgained thetypecheckscript it lacked. Fresh install ends with ONE.pnpmstore entry per package and every consuming project's realpath matching.462 pass / 0 fail. It also caught an error of MINE: the task file I wrote carried a baseline of 449 while the measurement says 462 - written before T01.m added its tests and never refreshed. That is the second of my own specs to carry a wrong number, after T01.m's unachievable acceptance test. Left alone, reported:ng-e2e-helpers' own floor looks stale for the same reason (INFERRED, out of scope). -
T01.g - the closing review REFUTED the parent's success criterion, which is what it was for. 8 findings over 53 published symbols (not the 25 the orchestrator's naive grep counted - it measured export STATEMENTS), 41 clean. THE BLOCKING ONE IS A RIGHTS LEAK, and it is this batch's whole failure mode applied to access control:
read-filter.tsreturned non-function members RAW on the written ground that "a plain property carries no items" - true of the CITED tree (orm alpha.18, a bareDeepSignalSet), FALSE of the INSTALLED alpha.21, whoseuseShapereturns aUseShapeResultcarrying every identity's items on the plain propertiesdata,promiseandsubscription. A stranger read the owner's data through all three and nothing threw. It became T01.o. Three majors: the contract instructed an injection that does not resolve (@ng-org/orm's.maps tocore.d.ts, which has nouseShape); four present-tense claims that the SDK returns a bareDeepSignalSetare false of alpha.21, TWO of them load-bearing provenance grounds - thereadUnionfailure mode again, invisible toprovenance.test.tsby construction; and the only live gate for the publisheduseShapeinjects a HAND-WRITTEN substitute, so no fake in the repository returned the shipped shape, which is exactly why every gate was green. Four minors became T01.p. THE LESSON FOR THE ORCHESTRATOR, not the code: ten adversarial passes and every re-run gate of this batch were blind to this, because the only artifact exercising that path was a fake that had never been checked against what ships. A green suite is worth what its fakes are faithful to, and nobody had asked.