You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 31f0f27
Browse filesBrowse the repository at this point in the historyBrowse files
fix(lint)!: os build and the page save door refuse a page component props CEL slot over the platform parse bounds (#25002) (#25088)
Closes#25002
Clause-②: no (narrowing: a page component props CEL slot over the
platform parse bounds is refused at `os build` and the page door; it
ships now under the amended 「甲」, #6015 `6303865025`)
## What changes
`page-predicate-over-budget` (`validatePagePredicateBounds`,
`packages/lint/src/validate-page-predicate-bounds.ts`) reports at
`error` instead of `warning`. In the same edit, its entry in
`packages/lint/src/authoring-rules.ts` moves from the `advisory` tier to
the `gating` tier. A page carrying a CEL slot inside a component's
`properties` bag whose source overruns `DEFAULT_LIMITS` (`maxAstNodes`
256, `maxDepth`, `maxListElements`) now fails `os validate`, `os build`
and `os lint`. An active write of that page, or the promotion of its
draft, is refused at the page save door. This is the refusal half of
#24953, re-timed to 18.x by triage (6305866852) under the amended 「甲」
(#6015 `6303865025`).
- The message drops "A future major release refuses it." The docblock's
"Why a warning" section becomes "Why an error" and cites the amended
ruling. The registry entry's comment says why the tier is `gating`.
- `PagePredicateBoundsFinding.severity` is typed `'error'`.
- No runtime or door code moves: the door already splits findings by
severity, `error` to the refusal and the rest to `advisories`.
## The door's code, measured
The card says `422 VALIDATION_ERROR`. The page save door actually
answers **`422 INVALID_METADATA`**, the author-time refusal envelope
every runtime-gate refusal uses. The new pin asserts `{ code:
'INVALID_METADATA', status: 422 }`, the same as the #23388 and #24491
page blocks next to it. The changeset says `INVALID_METADATA`.
## Tests
- `packages/lint/src/validate-page-predicate-bounds.test.ts`: the
warning assertions are now error assertions. The finding is `severity:
'error'`, and each of `os validate` / `os build` / `os lint` reports it
as the page's only error. The page door's `errors` channel carries the
build's finding verbatim, with nothing on `advisories`. The tier is
`gating`. The message no longer contains "future major". The
within-bounds CONTROL is unchanged and green.
-
`packages/metadata-protocol/src/protocol.runtime-authoring-gate.test.ts`:
a new block, "runtime authoring gate on PAGE writes — a props-bag CEL
slot over the parse bounds (#25002)", runs through the REAL
`saveMetaItem` / `publishMetaItem`. It is inserted after the #24491 page
block, not at the end of the file, which keeps it textually apart from
open PR #25052's append. It pins:
- (a) the card's ~390-node (78-term) `page:tabs` item `visibleWhen`
answers `{ code: 'INVALID_METADATA', status: 422 }`, with one
`page-predicate-over-budget` issue at
`pages[0].regions[0].components[0].properties.items[1].visibleWhen`
naming "`maxAstNodes` budget (platform limit 256)", and no active row is
stored;
- (a) the draft → active promotion is refused the same way;
- (b) the door's finding equals `os build`'s, key by key;
- (c) CONTROL: a within-bounds tab predicate saves, and the row lands.
- `runtime-gate.page-visibility-predicate-writes.test.ts`: one comment
now says "refusal" instead of "warning". The dispatch-order pin is
unchanged.
Evidence (run on head `53ae2b07d2`, after `pnpm --filter
'@objectstack/lint...' build` and `pnpm --filter
'@objectstack/metadata-protocol^...' build`):
- `pnpm --filter @objectstack/lint exec vitest run --maxWorkers=2
src/validate-page-predicate-bounds.test.ts
src/runtime-gate.page-visibility-predicate-writes.test.ts
src/authoring-rule-wiring.test.ts` → `Test Files 3 passed (3)`, `Tests
59 passed (59)`.
- `pnpm --filter @objectstack/metadata-protocol exec vitest run
--maxWorkers=2 src/protocol.runtime-authoring-gate.test.ts` → `Test
Files 1 passed (1)`, `Tests 180 passed (180)`.
- `pnpm --filter @objectstack/lint typecheck` (tsc plus
`check:test-typecheck`) → exit 0.
- `pnpm --filter @objectstack/lint exec vitest run --maxWorkers=2` (the
whole package) → `Test Files 153 passed (153)`, `Tests 7056 passed
(7056)`. `pnpm --filter @objectstack/metadata-protocol typecheck` → exit
0 (in the same `&&` chain, lock `VERDICT command-exit 0`).
### Ablation (one-shot, nothing left in the tree)
Committed first, then mutated through `scripts/ablation-replace.mjs`
(anchor `severity: 'error',` above `rule: PAGE_PREDICATE_OVER_BUDGET,`,
hit x1 -> x0, blob `e4a15923c57d` -> `51d908070c3a`) to the pre-change
severity, spelled `['warning', 'ablation25002'][0] as 'error'` so the
marker survives the bundle. `pnpm --filter @objectstack/lint build`,
then `scripts/ablation-dist-preflight.mjs @objectstack/lint
ablation25002` → marker present in 4 built files.
- Mutant, lint: `validate-page-predicate-bounds.test.ts` → `Tests 5
failed | 13 passed (18)` (the error finding, `os validate` / `os build`
/ `os lint`, the door's errors channel).
- Mutant, door: `protocol.runtime-authoring-gate.test.ts -t '#25002'` →
`Tests 3 failed | 1 passed | 176 skipped` ((a) refusal, (a) draft
promotion, (b) parity fail; (c) the within-bounds control stays green,
as it must).
- Restore: `git checkout HEAD -- PATH`, `git diff HEAD` empty, hash
`e4a15923c57d` equals the HEAD blob; rebuilt; `ablation-dist-preflight
--absent` → marker absent from all 20 built files, tree clean. Restored
run: lint file `18 passed (18)`, door file `180 passed (180)`.
## Producers, measured before crossing
0 in-repo producers carry such a slot, so this PR fixes none:
- **Page modules:** every `*.page.ts` and `pages/*.ts` module under
`examples/` and `packages/` (30 files, 65 page values), loaded with tsx
and judged by the built `validatePagePredicateBounds`, gives 0 findings.
- **Composed stacks:** the `app-crm`, `app-multi-package` and `app-todo`
stacks also give 0. The `app-showcase` config did not load here, because
an unbuilt `@objectstack/connector-mcp` was missing; its pages are
covered by the page-module pass.
- **Literal scan:** every tracked `.ts/.tsx/.mts/.js/.mjs/.json/.yaml`
file under `examples/`, `packages/` (dogfood and the package pages
included), `skills/`, `apps/` and `content/docs/` was scanned (9,919
files). Each string literal or `+`-chain of 120 or more characters
(13,840) was judged by `validateExpression`'s bounds verdict, and 0 are
`cel-too-large`.
- **HotCRM:** the same literal scan over `objectstack-ai/hotcrm` at
`c8a7a70` (611 files) finds 0.
- **Positive controls:** a planted 78-term predicate is found by both
instruments.
- **Residual:** a predicate assembled at run time (template
substitution, `.join`) is outside the literal scan. The page-module pass
covers it for the in-repo pages.
## Gates
`node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` derived 66 commands at `53ae2b07d2`, and all
66 were run. 63 exit 0, including `check-adr-0087-registration --base
origin/main` (it reads the `not-required (no-migration-prescription)`
marker), `check-changeset-no-major`, `check-empty-changeset`,
`check:changeset-gate-self-tests`, `check:nul-bytes`,
`check:cross-package-test-inputs` and `check:test-source-alias`. Three
are NOT MEASURED, each exiting 3 on its own prerequisite and not on a
finding:
- `check:dual-build-cjs-loads`: needs a whole-workspace `dist/`.
- `check:lean-entry-closure`: needs a built `@objectstack/objectql`.
- `check:type-check-debt`: its closure build hit the shared verify
lock's queue budget.
CI runs all three. `dispatch-gates --ran` with every exit code recorded:
`66 derived, 63 run, 3 NOT-MEASURED, 0 UNRUN`.
## Acceptance notes
- The pending changeset
`.changeset/24953-page-predicate-bounds-warning.md` (from #24999, not
yet released) still says "A future major release refuses it." If both
ship in one release, the two CHANGELOG entries for `@objectstack/lint`
disagree. This PR's changeset says it supersedes the warning-only
behaviour. The other file is outside this claim's file surface, so it is
not edited here. The seat can amend that one sentence in a follow-up if
wanted.
- The card's `VALIDATION_ERROR` is not what the door answers. See "The
door's code, measured" above.
- No generated docs reference this rule, and the CLI's rule count
(`Running author-time rules (50)`) does not move. Nothing was
regenerated.
## Release
`@objectstack/lint` `minor`, BREAKING (FROM a warning with the page
saved, TO an error with the page refused). The one-line fix: shrink the
predicate, or precompute it into a formula or roll-up field. ADR-0087:
`not-required (no-migration-prescription)`, following the shape of
bb4f5cc. `content/docs/releases/` is untouched.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01D6F9axfoPzR7t8G1J7Ytzz)_
---------
Co-authored-by: Claude <noreply@anthropic.com>
fix(lint)!: `os build`, `os validate`, `os lint` and the page save door refuse a page component props CEL slot over the platform's parse bounds (#25002)
6
+
7
+
Clause-②: no (narrowing: a page component props CEL slot over the platform parse bounds is refused at `os build` and the page door; it ships now under the amended 「甲」, #6015`6303865025`)
8
+
9
+
A CEL source over `DEFAULT_LIMITS` (`maxAstNodes` 256, `maxDepth`, `maxListElements`) does not parse anywhere on the platform. It faults on every evaluation, and the console fails open, so the element it was written to gate draws for everyone. `page-predicate-over-budget` (`validatePagePredicateBounds`) finds such a source in the CEL slots declared inside a page component's `properties` bag: a `page:tabs` item `visibleWhen`, an `object-form` section or inline field `visibleWhen` / `readonlyWhen` / `requiredWhen`, `record:alert``visible`, and the `visible` / `disabled` of the `action:*` blocks. The finding was a warning. It is now an error, and its authoring-rules entry moves from the advisory tier to the gating tier. This supersedes the warning-only behaviour described for the same rule earlier in this release line; its message no longer says "A future major release refuses it."
10
+
11
+
**BREAKING — what moves for consumers.**
12
+
13
+
- FROM: a warning at `os validate`, `os build` and `os lint`, and an entry on the page save door's `advisories` channel while the page saved. TO: an error. The three commands fail, and an active page write answers `422 INVALID_METADATA` with a `page-predicate-over-budget` issue located at the slot and naming the bound it overruns. The page is not stored. This covers `saveMetaItem` in publish mode (Studio, REST `/meta`, MCP) and the promotion of a draft (`publishMetaItem`).
14
+
- The page door answers `INVALID_METADATA`, its existing author-time refusal envelope, not `VALIDATION_ERROR`.
15
+
16
+
**Fix (one line).** Shrink the predicate (collapse an `||` chain into one `in ['a', 'b', …]` test), or precompute the heavy part into a formula or roll-up field on the object and test that one field.
17
+
18
+
**Unchanged.**
19
+
20
+
- A within-bounds source draws nothing at any door.
21
+
- The component node's own `visibleWhen` keeps its existing verdict (`visibility-predicate-over-budget`), and this rule does not report it twice.
22
+
- Stored pages are not migrated and are not refused on read. A stored page with such a slot keeps loading and serving until it is next saved, and that save is judged. Saving a page as a draft is still allowed; publishing the draft is judged.
23
+
-`OS_ALLOW_UNLINTED_METADATA_WRITES=1` still turns a refusal into a logged write.
24
+
- No public export or signature moves; `PagePredicateBoundsFinding.severity` is now `'error'`.
25
+
- Measured before crossing, through the built `@objectstack/lint`: 0 in-repo producers carry such a slot. Every page module under `examples/` and `packages/` (30 files, 65 page values) draws 0 findings, and so do the `app-crm`, `app-multi-package` and `app-todo` composed stacks. A literal scan of 9,919 tracked source files (13,840 string literals of 120+ characters) finds 0 sources over the bounds, and HotCRM at `c8a7a70` (611 files) finds 0. A planted 78-term control is found by both instruments.
26
+
27
+
<!-- adr-0087: not-required (no-migration-prescription) a refusal at `os build`, `os validate`, `os lint` and the page save door of a page component props CEL slot whose source overruns the platform's parse bounds: no authorable key, spelling, export or stored shape moves, and no stored row is read, rewritten or converted. A stored page whose slot is refused keeps loading until it is next saved, and the repair is the author's rewrite of the predicate (shrinking it, or precomputing it into a formula or roll-up field), which no ledger entry can derive. The other categories are closed on facts: the package publishes (not unpublished); no ADR-0087 id covers this verdict (not already-registered); and the change is a build and door verdict, not a declaration (not runtime-interface-only or type-surface-only). -->
0 commit comments