Commit 2f837a5
fix(runtime)!: refuse the stored-metadata family evaluate shapes and serve write returns at the reader-context seams (#21539)
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:
- **Evaluate-shape refusals** (triage `5964426684` item 2, with
`5963299937`). 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's `ctx.api`, and `ctx.engine.find`), is now refused with the
door's own `INVALID_FIELD` / 400 before the query runs. A `count`
carrying 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.
- **Serve what a write verb returns** (triage `5964426684` item 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 own `resolveSearchFieldResolution`
and 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`'s `collectConditionFields` (the
door's sibling collector; the door's own `collectFilterFieldKeys` is
internal to `protocol.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 (triage
`5964836549` item 1)
The reach reading: `execute` is UNREACHABLE from a served body. The
sandbox VM bridge exposes only `find` / `findOne` / `count` /
`aggregate` and the writes to a body — no `execute`, no `sudo`, no
`withRunAs` — so a served body can never hand a raw scoped context to a
nested action. The seam therefore leaves `execute` untouched and records
the reading (pinned: a body's `typeof ctx.api.object(...).execute` is
`undefined`).
### 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 on `main` today (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):
- disabling the evaluate refusal turned the 6 refusal / narrowing pins
red; the serve, write-return, scalar and unreachability pins stayed
green;
- disabling the write-return serve turned exactly the write-return pin
red; all else green.
### Tests and gates
- New pins: `stored-metadata-reader-seam.test.ts` (17) against a
scoped-API double, and `stored-metadata-reader-contexts.pin.test.ts`
(26) end to end through a booted kernel, for administrator and member
alike.
- `@objectstack/runtime` suite 4413 passed / 19 skipped;
`@objectstack/metadata-protocol` suite 3151 passed / 19 skipped; both
typecheck clean.
- All 70 dispatch-derived gate families green
(`check:engine-double-contract` pinned-ledger entry added through the
gate's own `--write`).
- Lint narrowing: the 4 changed TS files lint 0 errors / 0 warnings; the
`.md` and `.json` have no matching eslint config; `eslint.config.mjs`
enables no type-aware linting and no cross-file import rules, so this
diff cannot move the verdict on any untouched file (the farm-wide `pnpm
lint` is CI's).
---
_Generated by [Claude
Code](https://claude.ai/code/session_016GiHYRmLSNWTfbX9gVQkpz)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent e901c27 commit 2f837a5
6 files changed
Lines changed: 520 additions & 46 deletions
File tree
- .changeset
- packages
- metadata-protocol/src
- runtime/src
- scripts
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
207 | 207 | | |
208 | 208 | | |
209 | 209 | | |
| 210 | + | |
| 211 | + | |
| 212 | + | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
210 | 224 | | |
211 | 225 | | |
212 | 226 | | |
| |||
Lines changed: 135 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
118 | 118 | | |
119 | 119 | | |
120 | 120 | | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
121 | 179 | | |
122 | 180 | | |
123 | 181 | | |
| |||
167 | 225 | | |
168 | 226 | | |
169 | 227 | | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
170 | 235 | | |
171 | 236 | | |
172 | 237 | | |
| |||
461 | 526 | | |
462 | 527 | | |
463 | 528 | | |
| 529 | + | |
| 530 | + | |
| 531 | + | |
| 532 | + | |
| 533 | + | |
| 534 | + | |
| 535 | + | |
| 536 | + | |
| 537 | + | |
| 538 | + | |
| 539 | + | |
| 540 | + | |
| 541 | + | |
| 542 | + | |
| 543 | + | |
| 544 | + | |
| 545 | + | |
| 546 | + | |
| 547 | + | |
| 548 | + | |
| 549 | + | |
| 550 | + | |
| 551 | + | |
| 552 | + | |
| 553 | + | |
| 554 | + | |
| 555 | + | |
| 556 | + | |
| 557 | + | |
| 558 | + | |
| 559 | + | |
| 560 | + | |
| 561 | + | |
| 562 | + | |
| 563 | + | |
| 564 | + | |
| 565 | + | |
| 566 | + | |
| 567 | + | |
| 568 | + | |
| 569 | + | |
| 570 | + | |
| 571 | + | |
| 572 | + | |
| 573 | + | |
| 574 | + | |
| 575 | + | |
| 576 | + | |
| 577 | + | |
| 578 | + | |
| 579 | + | |
| 580 | + | |
| 581 | + | |
| 582 | + | |
| 583 | + | |
| 584 | + | |
| 585 | + | |
| 586 | + | |
| 587 | + | |
| 588 | + | |
| 589 | + | |
| 590 | + | |
| 591 | + | |
| 592 | + | |
| 593 | + | |
| 594 | + | |
| 595 | + | |
| 596 | + | |
| 597 | + | |
| 598 | + | |
0 commit comments