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