Files
ng-eventually/.tasks/done/T01.g-adversarial-review-of-the-published-surface.md
T

14 KiB

id, title, parent, depends_on, created, claimed_by, claimed_at, blocked
id title parent depends_on created claimed_by claimed_at blocked
T01.g Adversarial review of the whole published surface T01
T01.a
T01.b
T01.c
T01.d
T01.e
T01.f
T01.h
T01.i
T01.j
T01.k
T01.l
T01.m
T01.n
2026-09-16 adversarial-reviewer 2026-09-24 false

Description

An adversarial pass over the whole published surface — every symbol exported from packages/polyfill/src/index.ts, not only the ones this batch touched — trying to refute the parent task's success criterion: every published symbol either mirrors an SDK symbol in name, parameters and return, or carries a justified annotation.

Method, in this order. For each published symbol ask the two questions of .project/rules/nextgraph/no-divergence-from-nextgraph.md in their stated order: does the target do this? first, verified in nextgraph-rs or stated by its author; would an application have to unlearn it? second, and only to rank severity — never to authorise a divergence. Then, for the name and the shape (.project/rules/nextgraph/surface-mirrors-the-sdk.md): is this the target's own name, does the verb belong to something else upstream, is the parameter passed through or translated, is the return the SDK's.

Check every @provenance line against the CLAIM, not just against the tree. Same granularity on both sides: a function may cite a function; a composition, a pipeline or a policy cites nothing at level 2 and must say so. "The reference resolves" is not "the annotation is true" — that is precisely how readUnion survived. Use the mechanism as T01.a left it, including whatever the new kind for an announced-but-unwritten target requires as evidence.

Known, and not findings. The three open bug_ leaves in .project/concepts/app-contract/: bug_useshape-live-read-needs-a-newer-broker (MAJOR), bug_a-lost-inbox-watch-is-never-retried (MAJOR), bug_an-empty-inbox-list-can-mean-a-failure. Do not re-report them as new. Do report it if a change in this batch altered their status, or invalidated a workaround they or the contract publish — the useShape bug leaf and the contract's ## Non-guarantees both name watchShape and readUnion as the unaffected paths, and both were changed here.

Check the contract as delivered, since three children edited it in sequence: ## Surface matches the actual exports symbol for symbol; ## Change policy records a version whose bump level follows the contract's own rules for what actually changed; no section was half-edited or edited twice for the same change; nothing published is left described by a sentence that is no longer true.

Findings are leads, not mandates, and this child fixes nothing. A finding without a reproducible failure scenario or a checkable citation does not count. Report symptom, precise location (file:line, the failing assertion, the offending extract) and, where it is already known, the cause — but do not instrument, bisect or repair. Each fix is a new task.

Deliverable. In ## Result: findings by severity (blocking / major / minor), atomic, numbered 1.1, 1.2, … so they can be answered precisely. Neutral tone, no first or second person. One line of verdict on the test suites, not a list of what passes.

Pass condition for this child (it writes no code): run bun test, bunx tsc --noEmit -p packages/polyfill/tsconfig.json, bunx tsc --noEmit -p packages/polyfill/e2e/tsconfig.json and python3 ~/.claude/skills/concept/lint.py to establish the delivered state and report it verbatim; the review covers every symbol exported from packages/polyfill/src/index.ts, with the ones found clean stated as a count rather than an inventory.

Progress

Result

Read-only pass. Nothing in the delivered code was changed; one throwaway probe was run and deleted, and git status shows only this task file's own move.

Delivered state, measured

  • bun test -> 465 pass / 0 fail, 1316 expect() calls, 40 files. MATCHES the figure on record.
  • pnpm exec tsc --noEmit -p packages/polyfill/tsconfig.json -> exit 0. MATCHES.
  • pnpm exec tsc --noEmit -p packages/polyfill/e2e/tsconfig.json -> exit 0. MATCHES.
  • python3 ~/.claude/skills/concept/lint.py -> 0 ERROR (11 items across 4 WARNING classes). MATCHES.
  • packages/polyfill/src/index.ts carries 21 export statements. MATCHES. It DIFFERS on the symbol count: those 21 statements name 37 top-level names, of which 3 are namespace groupings (docs, inbox, storeRegistry), so the published surface as test/provenance.test.ts:publishedSymbols itself computes it is 53 (34 direct + 3 docs + 11 inbox + 5 storeRegistry), which is also exactly what docs/api-contract.md's appendix lists. Not 25.
  • 123 @provenance annotations in src, covering the 53 published symbols plus 70 internal ones.

Blocking

1.1 The read filter on the published useShape is bypassed by the SDK version this repo builds against, and every identity's objects reach the caller out of the one shared wallet. PROVED.

src/surface/use-shape.ts:14-17 hands whatever the injected hook returned to makeReadFilteredView. read-filter.ts:179-180 passes any non-function member through untouched, on the header's stated ground that "a plain property carries no items".

That ground held for @ng-org/orm@0.1.2-alpha.18, the version in the cited nextgraph-rs checkout: sdk/js/orm/src/frontendAdapters/react/useShape.ts:123 ends return state as DeepSignalSet<T> - a bare set, whose item-yielding members are all functions the whitelist covers. It is FALSE of @ng-org/orm@0.1.2-alpha.21, which the ANCHOR calls the build and which is installed under packages/polyfill/node_modules: dist/frontendAdapters/react/useShape.d.ts declares the return UseShapeResult, an object carrying data, promise, subscription, isLoading, nextPage, previousPage. data, promise and subscription are plain PROPERTIES holding the items.

Repro run against the delivered read-filter.ts and caps.ts, with a CapRegistry in which alice and bob each minted one document, isEnforcing() === true, and bob the current holder, over an object of exactly the shape useShape.d.ts declares:

alpha.18 shape -> iterated:                            [ "bob-own" ]
alpha.21 shape -> .data items :                        [ "bob-own", "alice-private" ]
alpha.21 shape -> .subscription.signalObject items:    [ "bob-own", "alice-private" ]
alpha.21 shape -> await .promise items:                [ "bob-own", "alice-private" ]
did anything throw? no.

The contract instructs exactly the injection that opens this: docs/api-contract.md:50 reads useShape: UseShapeLike; // the REAL @ng-org/orm useShape, and contract_polyfill-surface.md:36 says the same. EventuallyConfig.useShape is not optional, so every application passes something, and UseShapeLike = (...args: any[]) => any (src/model/types.ts:83) accepts either return shape without a type error - which is why both typechecks stay at exit 0.

Two published statements are falsified by the run. contract_polyfill-surface.md:256: "No unfiltered read through useShape. Members that yield items are filtered and mutations pass through; anything else throws" - nothing throws, and the unfiltered items come back on three separate properties. And docs/api-contract.md:345's rationale for wrapping the ORM, "an unwrapped ORM read returns EVERY identity's data out of the shared wallet with nothing signalling it", describes the WRAPPED useShape's current behaviour on this version.

Major

2.1 @ng-org/orm does not export useShape at the specifier the contract names. CONFIRMED. The installed manifest maps "." to dist/core.d.ts, and core.d.ts exports initNg, ngSession, RdfOrmSubscription, DiscreteOrmSubscription, insertObject, removeObject, getObjects, the types.ts re-export and three alien-deepsignals members. No useShape. The hook is at @ng-org/orm/react (also /svelte, /svelte4, /vue), or as reactUseShape from @ng-org/orm/all. An application following contract_polyfill-surface.md:36 or docs/api-contract.md:50 literally fails to resolve the import. Note the neighbouring claim that IS correct and was checked: index.ts:162's reason for taking RdfOrmConfig from /all holds, since core.d.ts really does not carry it.

2.2 Four present-tense claims that the SDK's useShape returns a bare DeepSignalSet are false of the version built against, and two of them are the stated ground of a provenance ruling. CONFIRMED. The claim is at src/model/types.ts:124, src/surface/use-shape-query.ts:11 and :33, and contract_polyfill-surface.md:234 ("useShape returns a set you cannot tell from an empty one"). In alpha.21 the return carries isLoading and promise. The two that are load-bearing:

  • src/surface/use-shape-query.ts:284, the announced-not-written annotation, whose note reads "the SDK ships useShape returning a bare DeepSignalSet; the announced direction is a useShapeQuery returning a TanStack-shaped snapshot". The premise is false of what the SDK ships; the shipped hook already returns a load-state-carrying object.
  • src/surface/use-shape-query.ts:193, ShapeQuery kind=invention level=none ref=none, whose note opens "no upstream type carries load state". UseShapeResult.isLoading is an upstream load state at level 3. ref=none and level=none assert that nothing answers at any level.

This is the failure mode the mechanism exists to catch, in its documented form: the citations resolve, and the claims attached to them do not hold at the version a consumer compiles against. test/provenance.test.ts cannot see it by construction (it pins that a claim was made, not that it is true), and the ANCHOR's own comment predicted this class of error.

2.3 The one gate that exercises the published useShape against a live broker injects a hand-written substitute rather than the SDK's hook, which is why 1.1 is green. CONFIRMED. packages/polyfill/e2e/orm-live-entry.ts:101-105 defines realOrmUseShape as RdfOrmSubscription.getOrCreate(...) returning sub.signalObject, and :109 injects that. The file's header states the substitution and its reason ("without a React tree to mount"). The state it fabricates - a hook returning a bare set - is the one alpha.21 no longer produces, so the suite measures the alpha.18 shape exclusively. Every unit fake does the same (test/orm-fake.ts, e2e/polyfill-entry.ts:191).

Minor

3.1 Two published types have no published signature using them, against the entry's own rule. CONFIRMED. src/index.ts:78 states "A type is published only when a PUBLISHED SIGNATURE uses it", and :87-96 enumerates the warrants. Schema (index.ts:149) appears in no signature anywhere: grep -rn "\bSchema\b" over the working copy outside node_modules returns only its own export line, its annotation, and three document copies of that line. DeepSignalSet (index.ts:151) appears in src only inside prose and one annotation, never in a type position; no published signature returns one, since useShape returns unknown. This is the defect for which ReadCap and InboxScope were unpublished on 2026-08-10, with the rationale still at index.ts:79-86.

3.2 Seven published symbols carrying kind=invention level=none ref=none sit under the block headed "a target counterpart for every symbol". CONFIRMED. src/index.ts:45-46 heads the first block "SDK-SHAPED - a target counterpart for every symbol / At migration the build alias is removed and these resolve to the real SDK", and :166-169 heads the second "POLYFILL-ERA - THE DELETION LIST / nothing below has a target counterpart". Exported above the divider and annotated invention, which the taxonomy defines as "nothing at any level answers": NgLike, UseShapeLike, OrmSubscriptionLike, GetObjectsLike, InsertObjectLike (:111-120) and ShapeQuery, ShapeObservable (:123). The five ...Like types are used by exactly one published signature each, all of them fields of EventuallyConfig, which is itself on the deletion list. docs/api-contract.md:822 states the matching count as "exactly ONE is polyfill-era with no target counterpart".

3.3 A published function is absent from the contract's ## Surface. CONFIRMED. docChangeType is exported at src/index.ts:132 and listed in the appendix, and contract_polyfill-surface.md's ## Surface block shows only the type DocChangeType, inside the two subscribeDoc signatures (:97-98). Nothing pins that block: test/vocabulary.test.ts checks docs/api-contract.md, never the concept leaf.

3.4 A stale annotation claims a published namespace name for an internal symbol. CONFIRMED. src/shared-wallet/account-registry.ts:971 annotates storeRegistry.resolveScopeGraph, and the format's own rule (test/provenance.test.ts:22-24) reserves the qualified form for a published namespace member. resolveScopeGraph was unpublished in 1.0.0-dev.5 (src/surface/placement.ts:135,144; contract_polyfill-surface.md:325), surface/placement.ts does not re-export it, and it is absent from the 53-symbol set publishedSymbols() computes.

Found clean

41 of the 53 published symbols were walked against nextgraph-rs or the installed .d.ts and faulted on nothing. The 12 named above are useShape, useShapeQuery, ShapeQuery, ShapeObservable, Schema, DeepSignalSet, docChangeType, NgLike, UseShapeLike, OrmSubscriptionLike, GetObjectsLike, InsertObjectLike.

Checked and HOLDING, listed only because each was a plausible refutation that failed: getObjects and insertObject mirror alpha.21's declared signatures exactly, including Omit<RdfOrmConfig<T>, "pageSize" | "maxActivePages">, and their passthrough notes disclose the filter and the guard; RdfOrmConfig's /all subpath justification is true of the installed manifest; Scope is genuinely on core.d.ts via export * from "./types.ts"; ng, NG, init, initNg, subscribeDoc, subscribeDocs, the three docs members, the eleven inbox members and the five storeRegistry members carry annotations whose claims match their code. ## Change policy records 1.0.0-dev.10, which packages/polyfill/package.json:3 pins, and its "Nothing is added, renamed or removed from the entry" is true: git diff de9b8a0..HEAD -- packages/polyfill/src/index.ts moves no export line.

Verdict on the suites

Green as delivered and reproduced verbatim above, and 1.1 shows the green is not evidence: no fake in test/ or e2e/ returns the shape the installed SDK returns, so the suites exercise a hook shape that @ng-org/orm@0.1.2-alpha.21 does not produce.