9.0 KiB
id, title, parent, depends_on, created, claimed_by, claimed_at, blocked
| id | title | parent | depends_on | created | claimed_by | claimed_at | blocked | ||
|---|---|---|---|---|---|---|---|---|---|
| T01.e | Publish the ORM family with the rights filter | T01 |
|
2026-09-16 | worker-T01.e | 2026-09-21 | false |
Description
Wrap and publish the ORM family this package does not expose: getObjects and insertObject at identical signatures, with the rights filter; deletion in whatever shape T01.b established; and whatever T01.b's arbitration settled for OrmSubscription, DiscreteOrmSubscription and ngSession.
Why the dependencies. On T01.b: the deletion shape and the three arbitrations are established there — read its ## Result and the knowledge_ leaf it names, rather than re-reading nextgraph-rs. On T01.d: both children change the published surface and edit the same contract, and they must not run concurrently.
The problem is a rights hole, not a missing convenience. Today an application has to import these straight from @ng-org/orm, and then receives every identity's data out of the shared wallet with nothing signalling it. The filter that closes it is makeReadFilteredView (packages/polyfill/src/emulated-verifier/read-filter.ts, annotated kind=aligned level=1 ref=engine/verifier/src/verifier.rs:load_repo_from_read_cap): it keeps only items whose document — their @graph — the current holder has a cap for, applied per document on what comes back. That is the emulation of what the broker will bound at delivery, done where the target does it. packages/polyfill/src/surface/use-shape.ts is the existing example of a passthrough that filters results.
Identical signature means passed through, not translated. If a parameter cannot be passed through, stop and say what would have to be built beneath it — that is the real decision, and it is being made by the parameter (.project/rules/nextgraph/surface-mirrors-the-sdk.md). Do not invent an ergonomic wrapper that an application would have to unlearn.
Open questions to carry or close — do not invent past them:
- If T01.b left the deletion shape unestablished, this child does not specify one. Report it and publish the rest.
- A write path cannot be rights-filtered the way a read is. The filter is a read emulation.
packages/polyfill/src/surface/ng-proxy.tsalready overridessparql_updatewith an emulated write guard — read it before deciding whatinsertObjectdoes with an unauthorized write. Do not invent a write guard the target does not have; if the answer is "the existing guard covers it", say which line does. - If T01.b's arbitration for
OrmSubscription/DiscreteOrmSubscription/ngSessionis a proposal the user has not confirmed, and acting on it would remove or rename something, stop and ask. Adding is reversible; removing a published symbol is a major that a caller pays for.
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 — ## Surface, ## Guarantees (what the filter promises about what comes back), ## Non-guarantees, ## Change policy. By the contract's own rules, symbols added with nothing existing moved is a minor; if the arbitration removes or narrows anything it is a major. Apply the rule, do not assume a level. No other child edits the contract for this change.
Also in scope: packages/polyfill/src/index.ts (the export and its @provenance lines — each new symbol needs one, in the form T01.a installed), test/vocabulary.test.ts and test/app-surface.test.ts (they pin the export inventory), test/provenance.test.ts coverage, docs/api-contract.md, packages/polyfill/README.md, packages/polyfill/docs/polyfill-reference.md. New behaviour needs new tests, including the rights case: a second identity's data must not come back.
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). A write is not rights-filtered like a read: upstream bounds a write by write capability, not by hiding results. So insertObject gets a GUARD BEFORE the write, following the package's existing pattern (assertOwnInbox, mustNotAttempt, assertMayReach) — never a filter over what it returns.
Resolved since planning (2026-09-21), and it overturns T01.b's proposal. NO SDK element is published as-is, ever. Re-exporting one hands the caller a path straight to the SDK that SHORT-CIRCUITS this package's emulation - the capability filter, and above all the shared-wallet identity boundary. T01.b proposed publishing OrmSubscription unchanged; that proposal is void. Every symbol this package offers from the ORM family is WRAPPED, at the same name and signature, with the emulation applied. If a symbol cannot be wrapped meaningfully, it is not published at all - that is a valid outcome, not a failure.
Note also: OrmSubscription was renamed RdfOrmSubscription in @ng-org/orm@0.1.2-alpha.21, and getOrCreate returns a narrowed RdfOrmSubscriptionFor<...> rather than the class. T01.b's proposal named the old symbol.
Progress
- Read the two governing rules, T01.b's and T01.d's
## Result, the parent's## Learnings digest, andknowledge_what-the-orm-writes-and-deletes-upstream.md. Baseline taken first:bun test408/0, bothtsc0, lint 0 ERROR. - Read the ORM family AT THE SOURCE and in the installed
@ng-org/orm@0.1.2-alpha.21:getObjects,insertObject,removeObject(NEW, absent from the pinnednextgraph-rs),RdfOrmSubscription,DiscreteOrmSubscription,ngSession, plusgetRaw/watch/effect. Decided each against the "nothing published as-is" rule; reasons recorded insrc/surface/orm.tsanddocs/api-contract.mdsection 6bis. - Published
getObjectsandinsertObjectwrapped, at upstream's own names, parameters and returns, injected throughconfigurelike the shape query beside them.getObjects's config parameter isRdfOrmConfig(alpha.21's widening ofScope | string), which forced publishing that type from the SDK's/allsubpath. - Closed the
graph_orm_updaterights hole in two pieces: the guard (an override inng-proxy.ts, ownership viaassertMayWrite, reading the document out of each patch'spath), and the door it sits on (initNgnow hands the ORM this package's wrappedng, because both consumers in the repo hand it the raw SDK object). - That substitution turned the e2e RED on the two
useShapeQueryjourneys; the reference run was green, so it was mine. Measured the cause:makeNg()'s passthrough didReflect.get(ng, prop).bind(ng), and@ng-org/web'sngis itself a Proxy answering every key -bindincluded - with a path-carrying callable returning a Promise. The publishedngtherefore answered all 88 members with a promise. Forwarded withReflect.applyinstead; pinned by a test whose fake is shaped like the real proxy, and which was verified to FAIL on the old line. - Contract leaf edited (Scope, Deployment requirements, Surface, Guarantees, Non-guarantees, Change policy), bumped to
1.0.0-dev.7as a MAJOR under the leaf's own policy, and inpackage.json.docs/api-contract.md(new section 6bis, sections 2/3/14 and the appendix),docs/internal-contract.md,README.md,docs/polyfill-reference.mdupdated.
Result
Done. getObjects and insertObject are published WRAPPED, at upstream's names, parameters and returns, injected through configure. The read is capability-filtered per @graph (collection shape preserved); the write takes assertMayReach + assertMayWrite BEFORE the call. graph_orm_update now carries the same ownership guard in ng-proxy.ts, and initNg substitutes this package's wrapped ng so the ORM knocks on that door - the rights hole is closed. NOT published, each with a checkable reason: RdfOrmSubscription (pooled with no identity in the key), DiscreteOrmSubscription, ngSession, getRaw, watch/effect, and removeObject (exists in alpha.21, absent from the pinned nextgraph-rs, so no citation resolves). 1.0.0-dev.7, MAJOR: an ORM write into a document you do not own is now refused.
Found and fixed, MEASURED. makeNg()'s passthrough did .bind(ng); the SDK's ng is itself a Proxy answering every key including bind, so the published ng returned a PROMISE for all 88 members - orm_start_graph is not a function, swallowed by the ORM, read pending for ever. Now Reflect.apply. The e2e proves it only since initNg routes the ORM through the wrapper.
Gate. bun test 424 pass / 0 fail; both tsc exit 0; lint 0 ERROR; == Polyfill e2e summary: 42 passed, 0 failed, 42 total - batch 3.3 min, slowest cold sync 27s ==.
Follow-ups. examples/notebook fails its OWN tsc on HEAD already (its shape literal needs satisfies). discrete_orm_update is unguarded. removeObject awaits a refreshed nextgraph-rs.