5.5 KiB
id, title, parent, depends_on, created, claimed_by, claimed_at, blocked
| id | title | parent | depends_on | created | claimed_by | claimed_at | blocked |
|---|---|---|---|---|---|---|---|
| T01.o | The read filter is bypassed by plain properties, and no fake shows it | T01 | 2026-09-24 | null | null | false |
Description
A rights leak on the published surface. The capability filter around the published useShape is bypassed by the SDK version this repo actually builds against, and every gate stays green because no fake anywhere returns the shape the SDK ships. Found by T01.g, the closing review.
The leak - VERIFIED from primary sources on both sides, do not re-derive
packages/polyfill/src/emulated-verifier/read-filter.ts does const v = Reflect.get(target, prop, target); if (typeof v !== "function") return v; - a non-function member is returned RAW, on the recorded ground that "a plain property carries no items".
That ground is true of the CITED tree (alpha.18, where useShape returns a bare DeepSignalSet) and FALSE of the INSTALLED @ng-org/orm@0.1.2-alpha.21. Its shipped dist/frontendAdapters/react/useShape.d.ts returns UseShapeResult, whose members include three PLAIN PROPERTIES that each carry the items: data: SUBSCRIPTION_DATA | undefined, promise: Promise<SUBSCRIPTION_DATA>, and subscription: RdfOrmSubscriptionFor<...>. Its own doc comment states that data IS the value returned by RdfOrmSubscription.signalObject.
Measured by T01.g with bob as holder and isEnforcing() === true: the alpha.18 shape iterates to ["bob-own"], while alpha.21's .data, .subscription.signalObject and await .promise each yield ["bob-own", "alice-private"], and nothing throws. That falsifies contract_polyfill-surface.md:256 ("anything else throws").
THE ASYMMETRY IS THE FAULT, and the code already argues against itself
For an unknown FUNCTION member the same file refuses rather than forwards, with the reason written beside it: forwarding is a silent leak, and this view's one job is that it cannot show more than the holder may read. That reasoning was never applied to non-function members. Naming the asymmetry is not the same as prescribing the repair - weigh what the right policy is, including what refusing data would cost a legitimate caller, and say what you rejected.
Do NOT fix this by filtering three member names. data, promise and subscription are alpha.21's shape; a name list is what breaks silently at alpha.22, and breaking silently on an SDK bump is exactly how this defect arrived. Whatever you build must be safe for a shape nobody has seen yet.
The line that must hold: a legitimate caller still gets its own data, through whichever member the SDK offers. A repair that makes the published useShape unusable has traded a leak for a break.
Why it was invisible, which must be fixed in the same task
packages/polyfill/e2e/orm-live-entry.ts:101-109 - the only live gate for the published useShape - injects a HAND-WRITTEN sub.signalObject substitute. So no fake in this repository returns the shape the SDK actually ships, and the leak cannot fail a test. Fixing the filter without fixing this leaves the next SDK shape change just as invisible.
Build a fake that returns the SHIPPED shape - read it from node_modules/@ng-org/orm/dist/frontendAdapters/react/useShape.d.ts, which is the artifact that settles it - and make it the one the gates use. A test that would not have caught this defect is not coverage.
Two documentation faults that ride with it
contract_polyfill-surface.mdinstructs injecting "the REAL @ng-org/orm useShape" (seedocs/api-contract.md:50), but@ng-org/orm's.export maps todist/core.d.ts, which exports NOuseShape- it lives at/react, or asreactUseShapefrom/all. The instruction does not resolve. CONFIRMED by T01.g.- Four present-tense claims that the SDK's
useShapereturns a bareDeepSignalSetare false of alpha.21, and TWO are load-bearing provenance grounds:use-shape-query.ts:284'sannounced-not-writtenpremise, and:193'sShapeQuery kind=invention level=none ref=none, justified as "no upstream type carries load state" whenUseShapeResult.isLoadingdoes. This is thereadUnionfailure mode again - a citation that resolves, attached to a claim that is false - andprovenance.test.tscannot see it by construction.
Verification
The leak's own test comes first and must fail on baseline. Bob holding no capability for alice's document must not reach her items through ANY member of the returned object. Name the test.
Hard gate: bun test -> 0 fail, baseline 465 pass / 0 fail which the orchestrator measured; pnpm exec tsc --noEmit -p packages/polyfill/tsconfig.json -> exit 0; the e2e tsconfig -> exit 0; python3 ~/.claude/skills/concept/lint.py -> 0 ERROR; and the polyfill e2e run and reported verbatim, since the injected fake changes. Broker login flakes roughly 3 runs in 10 before library code runs - re-run a flaked login.
Anti-cheat: no any, no @ts-ignore, no as unknown as, no deleted or skipped test, no weakened assertion. And specific to this one: no widening UseShapeLike to make a shape typecheck - src/model/types.ts:83 is already (...args:any[]) => any, which is why both typechecks stayed at 0 while the leak was open, and that is a finding in itself worth addressing.
A guarantee and the test that pins it land in the same commit. The contract's claim at :256 is currently false; it becomes true or it changes, in the same commit as its test.
An independent adversarial review runs on this afterwards. Rights defects get one whatever the gate says.