23 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)
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).