fix(runtime)!: refuse the stored-metadata family evaluate shapes and serve write returns at the reader-context seams - #21539
Conversation
…metadata reader seam Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016GiHYRmLSNWTfbX9gVQkpz
…narrowing, execute reach Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016GiHYRmLSNWTfbX9gVQkpz
…erve (#21454) Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016GiHYRmLSNWTfbX9gVQkpz
…used helper) Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016GiHYRmLSNWTfbX9gVQkpz
… seam Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016GiHYRmLSNWTfbX9gVQkpz
…ispatch predicates; record pinned coverage Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016GiHYRmLSNWTfbX9gVQkpz
📓 Docs Drift CheckThis PR changes 2 package(s): 11 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 4 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 31 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin a5cb5c252ef93a5287b6407d006acbe10344a955 && git checkout a5cb5c252ef93a5287b6407d006acbe10344a955
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin fd5a1cd5973983bf8b1ad69a10148a85c74c9137 19b2cb6e50e576813431b22928aa1057ce4de0ef && git checkout -B drift-repro fd5a1cd5973983bf8b1ad69a10148a85c74c9137 && git merge --no-ff 19b2cb6e50e576813431b22928aa1057ce4de0ef
node scripts/docs-audit/affected-docs.mjs --json fd5a1cd5973983bf8b1ad69a10148a85c74c9137
|
Contract reviewServed-tier: Inputs: card #21454 (body and all 19 comments), PR #21539 (body, the 6-file list, the net diff against ① Derived judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: FAIL Generated by Claude Code |
…arrowing (#21454) Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016GiHYRmLSNWTfbX9gVQkpz
Contract reviewServed-tier: Inputs: card #21454 (body and all 22 comments, the earlier record ① Derived judgmentsThe code is byte-identical to head
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
Part of #21454
Clause-②: yes (narrowing)
The follow-on to PR #21513, on the same reader-context seam. #21513 served the stored-metadata-body family's reads (body projected, content hash keyed) at the in-process reader contexts. This PR closes the two positions triage routed to the card's next claim, through the generic data door's own code — no copy:
5964426684item 2, with5963299937). A filter, sort, grouping or search that would EVALUATE the stored body or a content-hash column of the family, arriving through a reader context (ctx.api.object(...)for action and hook bodies, a handler'sctx.api, andctx.engine.find), is now refused with the door's ownINVALID_FIELD/ 400 before the query runs. Acountcarrying such a predicate — the oracle verb — is refused too (it serves no row). A default search is NARROWED to the door's served field set (the body and content-hash columns removed, judged field by field by the door's own search predicate) rather than refused, so a reader context may still search a family table by its scalar columns exactly as the door serves it; a search that narrows to nothing is refused.5964426684item 4). A write whose return carries the family's body or content hash is served projected and keyed, the same way a read is — a returned row is a serve.Both are executed through the four refusal predicates the door uses, now exported from
@objectstack/metadata-protocol(storedMetadataBodyGroupingRefusal,storedMetadataBodyPredicateRefusal,storedMetadataHashEvaluateRefusal,storedMetadataSearchRefusal). The search narrowing consumes the spec's ownresolveSearchFieldResolutionand the door's search predicate as the authority on which columns a search may never scan. Field collection for the filter/sort refusals uses@objectstack/plugin-security'scollectConditionFields(the door's sibling collector; the door's owncollectFilterFieldKeysis internal toprotocol.ts, PR #21473's file, and unreachable here). That collector gates on a dotted head and reads a cross-field reference, so it refuses MORE than the door — a dotted or cross-field reference to a family column the door's collector would miss — strictly in the safe direction, never a legitimate scalar-column query.The engine action verb (
ScopedRepo.execute), measured (triage5964836549item 1)The reach reading:
executeis UNREACHABLE from a served body. The sandbox VM bridge exposes onlyfind/findOne/count/aggregateand the writes to a body — noexecute, nosudo, nowithRunAs— so a served body can never hand a raw scoped context to a nested action. The seam therefore leavesexecuteuntouched and records the reading (pinned: a body'stypeof ctx.api.object(...).executeisundefined).Where #21520's write-verb refusal attaches
The maintainer approved #21520 option A (an app-authored body may not write the family's tables). That refusal is a SEPARATE card and is NOT implemented here. It attaches on the same write verbs this seam wraps, in
serveRepository's write branch, as a throw BEFORE the write runs — it needs no reshaping of this seam. A code comment names that point. Measured onmaintoday (pre-#21520): an elevated body's family-table write is not refused and returns the stored row, so the write-return serve in this PR carries real content; it also covers the host-handler write path, which #21520's body refusal does not reach.Reverse verification (ablation)
Each new behavior ablated on disk (
scripts/ablation-replace.mjs, mutation proven by blob hash + anchor count, restored to the HEAD blob), src-resolved (the pins import the seam by relative path):Tests and gates
stored-metadata-reader-seam.test.ts(17) against a scoped-API double, andstored-metadata-reader-contexts.pin.test.ts(26) end to end through a booted kernel, for administrator and member alike.@objectstack/runtimesuite 4413 passed / 19 skipped;@objectstack/metadata-protocolsuite 3151 passed / 19 skipped; both typecheck clean.check:engine-double-contractpinned-ledger entry added through the gate's own--write)..mdand.jsonhave no matching eslint config;eslint.config.mjsenables no type-aware linting and no cross-file import rules, so this diff cannot move the verdict on any untouched file (the farm-widepnpm lintis CI's).Generated by Claude Code