Files
ng-eventually/.tasks/done/T01.e-publish-the-orm-family.md
T

59 lines
9.0 KiB
Markdown

---
id: T01.e
title: Publish the ORM family with the rights filter
parent: T01
depends_on: [T01.b, T01.d]
created: 2026-09-16
claimed_by: worker-T01.e
claimed_at: 2026-09-21
blocked: 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.ts` already overrides `sparql_update` with an emulated write guard — read it before deciding what `insertObject` does 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` / `ngSession` is 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`, and `knowledge_what-the-orm-writes-and-deletes-upstream.md`. Baseline taken first: `bun test` 408/0, both `tsc` 0, 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 pinned `nextgraph-rs`), `RdfOrmSubscription`, `DiscreteOrmSubscription`, `ngSession`, plus `getRaw`/`watch`/`effect`. Decided each against the "nothing published as-is" rule; reasons recorded in `src/surface/orm.ts` and `docs/api-contract.md` section 6bis.
- Published `getObjects` and `insertObject` wrapped, at upstream's own names, parameters and returns, injected through `configure` like the shape query beside them. `getObjects`'s config parameter is `RdfOrmConfig` (alpha.21's widening of `Scope | string`), which forced publishing that type from the SDK's `/all` subpath.
- Closed the `graph_orm_update` rights hole in two pieces: the guard (an override in `ng-proxy.ts`, ownership via `assertMayWrite`, reading the document out of each patch's `path`), and the door it sits on (`initNg` now hands the ORM this package's wrapped `ng`, because both consumers in the repo hand it the raw SDK object).
- That substitution turned the e2e RED on the two `useShapeQuery` journeys; the reference run was green, so it was mine. Measured the cause: `makeNg()`'s passthrough did `Reflect.get(ng, prop).bind(ng)`, and `@ng-org/web`'s `ng` is itself a Proxy answering every key - `bind` included - with a path-carrying callable returning a Promise. The published `ng` therefore answered all 88 members with a promise. Forwarded with `Reflect.apply` instead; 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.7` as a MAJOR under the leaf's own policy, and in `package.json`. `docs/api-contract.md` (new section 6bis, sections 2/3/14 and the appendix), `docs/internal-contract.md`, `README.md`, `docs/polyfill-reference.md` updated.
## 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`.