Skip to content

Commit d0c09e4

Browse files
feat(lint)!: the two page component rules run on the runtime publish door (ruling B) (#23418)
Fixes #23388 Clause-②: no (narrowing) Ruling of record `6161173492` (letter B), quoting the maintainer: 「23388 B 是不是最简单?」「23388 如果页面有问题,不是应该修改了再保存吗?」 ## What changes - **`packages/lint/src/authoring-rules.ts`:** `validateComponentProps` and `validateComponentTypes` go from `surfaces: CLI_ONLY` to `CLI_AND_RUNTIME`, with `runtimeTypes: ['page']` (ruling items 1 and 2). Both entry comments drop the #4716 stored-row false-refusal discipline and the two pending `surfaceReason`s, and cite this ruling as the ruling of record (item 3). The `validatePrintPageBlocks` entry comment said "the false-refusal budget `validateComponentTypes` is held on"; it now says that budget was replaced by this ruling. The rules' own code and `ComponentPropsMap` are not touched. - **Header lines (item 3):** the header lines the dispatch pointed at (~494-511, `RUNTIME_OBJECT_ADVISORY_VOLUME`) describe the #4716 split of the OBJECT rules, not these two page rules. They are still true and are not rewritten. No other header line in the file names these two rules. - **The crossing needed no mechanism edit.** `page` already maps to `pages` in `TYPE_TO_STACK_KEY`. The runtime gate judges a finding located on the written item against the baseline without the item, so the next save of a stored page with a finding is that save's own finding. The read and boot path runs no authoring rule. - **Pins (item 4)** are in `packages/metadata-protocol/src/protocol.runtime-authoring-gate.test.ts` and run through the real `saveMetaItem` / `publishMetaItem` / `loadMetaFromDb` / `getMetaItem`: - (a) An active save of a page whose `page:header` carries `titel` is refused with `422 INVALID_METADATA`, rule `component-props-unknown-key` at `pages[0].regions[0].components[0].properties.titel`. The message carries `` `titel` → `title` `` and the hint carries `` Remove `titel` ``. Nothing is stored. The draft-to-active promotion of the same page is refused the same way. - (c) A component typed `page:headr` is refused with `422`, rule `component-type-unknown`, hint exactly ``Rename `page:headr` → `page:header`.`` - Control: the declared spelling saves, and so does `acme:pipeline_board`, a type outside the reserved namespaces. - (b) A page row carrying both findings is seeded into the store. Then: `loadMetaFromDb` returns `loaded: 1, errors: 0, invalid: 0` and registers the page; `getMetaItem` serves the stored body; the next save of the same body is refused with both rule ids and the stored row is unchanged; the fixed page then saves. This mirrors the precedent sentence at `18.page-default-record-page-one-per-object-refused.ts:42`. - **Changeset (item 5):** `.changeset/23388-page-component-rules-runtime-door.md`. `@objectstack/lint` gets `minor` with the BREAKING banner, and `@objectstack/spec` gets `patch` (text only). It carries the ruling's sentence verbatim and the ADR-0087 disposition `not-required (already-registered …)`, listing the same D3 ids the 23147 changeset names. The card's step ① (a stored-row listing) is not built. ## Overlap with #23357 (ruling item 6, for the seat) Once this lands, #23357's narrow judge (the retired page data binding, in `packages/lint/src/validate-component-props.ts` / `packages/metadata-protocol/src/protocol.ts`) overlaps the general rule at the save door. One operation has two implementations, and the governed side wins. I did not touch it, because seat 1 holds those files. The same seat should fix one sentence while it is in those files: `validate-component-props.ts:111-116` ("The runtime publish door … does not run this rule") becomes false with this PR. ## File surface beyond the claim, and why Ruling item 3 limits the rewrite to `authoring-rules.ts`. My change also made the texts below false, and os-dev requires a published text that a change makes false to be fixed in that same change, so I corrected them here: - **`packages/lint/src/validate-print-page-blocks.ts`** and **`packages/lint/src/validate-component-types.ts`** headers: comment-only. Each named the props/types rules' pending stored-row budget or `surfaceReason`. - **24 step-18 D3 entries** under `packages/spec/src/migrations/entries/semantic/18.ui-*`, `18.element-data-source-and-object-block-filter-rule-array.ts`, and **`packages/spec/src/migrations/registry.ts.template`**: these are shipped upgrade-guide and `os migrate meta` text. Each said "a stored page still saves and loads, because a page component's `properties` is not parsed on the metadata save or load path", or for one entry "a re-save through the metadata API is not refused there". After this PR, the next save of such a page is refused by the component-props gate at the save door. Each now says the page still loads and that its next save meets the component-props gate at the metadata save door. Code comments that said "a stored page is never refused" now say "never refused at load". Text only: no key, schema or conversion changes. No committed generated artifact moves (`registry.ts` is gitignored, and `check:spec-changes` / `check:upgrade-guide` are green). - One more now-false sentence is NOT fixed, because #23332 holds that file: the doc comment on the `object-grid` export-options row at `packages/spec/src/ui/component.zod.ts:5991-5994` ("a stored page still saves and loads"). It is a comment, not runtime text. ## Deliberate correction of pending changesets *(Seat update at head `3301d5f2f`: `18.0.0-next.1` was cut on `main` (`0c52e4307`), which moved both notes into `.changeset/pre/`; they are retained for the GA `18.0.0` note that `changeset pre exit` assembles, so the correction still applies there. The shipped `18.0.0-next.1` CHANGELOG is history and is not edited. Paths below are the current ones.)* `check:empty-changeset` is **red by design**: this PR changes two pending changesets it did not add, and those sentences became false because of this PR's diff. I added no label. The seat decides at review, against the head sha. 1. **`.changeset/pre/23147-component-props-every-finding-refused.md`** - Before: "**Not judged here.** The runtime publish door (the Studio, REST `/meta`, MCP) does not run this rule, so a page saved there is not refused for any of these. `defineStack` and the stack schema keep the open `properties` bag as written. …" - After: "**Not judged here.** `defineStack` and the stack schema keep the open `properties` bag as written. …" - What makes the before false: `packages/lint/src/authoring-rules.ts`, the `validateComponentProps` entry, `- surfaces: CLI_ONLY,` becomes `+ surfaces: CLI_AND_RUNTIME,` / `+ runtimeTypes: ['page'],`. 2. **`.changeset/pre/23103-page-action-row-on-success-and-executor.md`** - Before: "… as a `component-props-invalid` finding (an error, like every finding of that gate). A stored page still saves and loads." - After: "… as a `component-props-invalid` finding (an error, like every finding of that gate). A stored page still loads; the metadata save door runs the same gate, so the next save of a page carrying a refused row is refused with the same text." - What makes the before false: the same `validateComponentProps` line in `authoring-rules.ts`. The gate's exact failing output (`node scripts/check-empty-changeset.mjs --base origin/main`, exit 1, at `ba20e780e`): ```text This PR changes a changeset it did not add: .changeset/pre/23103-page-action-row-on-success-and-executor.md present on the merge base and CHANGED by this PR -- this is somebody else's release note .changeset/pre/23147-component-props-every-finding-refused.md present on the merge base and CHANGED by this PR -- this is somebody else's release note ``` It ends with one `::error file=…` annotation for each of the two files: "… exists on the merge base and was not added by this PR … DELIBERATE CORRECTION -- your change may have made this PENDING release note false, and you rewrote it in the same stroke. Remedy: do NOT restore it -- say so on the PR and get it confirmed …" ## Acceptance notes - *(Seat update at head `3301d5f2f`: closed. PR #23485 (#23421, `8693110c4`) retired both conversions from the load path at 18, so both spellings are refused with the rename at every door and the save door and `os validate` agree. The measurement below describes the state before that landing.)* - **Door asymmetry, measured.** These two rules run at the CLI at the `normalized` tier, after ADR-0087 D2 conversions. The save door judges the body as sent. I ran both rules over every conversion fixture with a `pages` stack (31 of 136 conversions), raw against normalized: - 29 give identical verdicts. - Two live-window aliases differ: `page-header-subtitle-alias` (`page:header` `description`, 5 raw-only findings) and `inline-action-api-params-to-body-extra` (an inline api action's object `params`, 3 raw-only). - Both declare `toMajor: 17`, so their window runs out at 18 (`retiresIn = toMajor + 1`), but neither is `retiredFromLoadPath` yet. Until that happens, a save spelling them is refused with the canonical key (for example, "Did you mean `description` → `subtitle`?"), while `os validate` converts them. - The changeset says so. Stored rows are rehydrated through the conversions on read, so a Studio round-trip sends back the canonical spelling. - The objectui page designer writes no component props, per the director's reading in the ruling. ## Verification (head `ba20e780e`) - `pnpm --filter @objectstack/lint test`: 138 files, 6699 tests passed. `typecheck` passed. - `pnpm --filter @objectstack/metadata-protocol test`: 235 files passed, 3 skipped; 28368 tests passed, 19 skipped. `typecheck` passed. - Consumers that save pages through the door: `@objectstack/objectql` (`default-record-page-save-door`, `protocol-meta`) 113 passed; `@objectstack/rest` (`meta-alternate-door-read-gates`, `meta-history-diff-authoring-door`, `meta-plural-i18n`, `rest`) 667 passed; `@objectstack/runtime` (`domains/meta-list-projection-parity`) 739 passed. - `@objectstack/spec`: `vitest --project local` over `src/migrations`, `src/conversions` and `src/stack-conversions-record.test.ts`, 763 passed; `test:repo`, 59 files, 1011 tests passed. - **Ablation:** done with `scripts/ablation-replace.mjs` (anchor 1 → 0, blob changed), then a lint rebuild and `ablation-dist-preflight` (marker present in 4 built files). - `validateComponentProps` back on `CLI_ONLY`: (a), the draft promotion and (b) go red; (c) and the control stay green. - `validateComponentTypes` back on `CLI_ONLY`: (c) and (b) go red, and (a) goes red too (its `rulesRun` assertion names both rules). - Restore leg: `git checkout HEAD`, hash equal to the HEAD blob, rebuild, both markers `--absent`, and the 5 pins pass. - **Derived gates** (`dispatch-gates --ran`): 89 derived; 85 run, 4 NOT MEASURED, 0 unrun. - Red: `check-empty-changeset` (by design, above). - NOT MEASURED: - `check-plugin-teardown-shape --self-test`: shallow clone, so the pinned control commit is unreachable. - `check:dual-build-cjs-loads`: needs a whole-repo build. - `check:type-check-debt`: its re-measure build was OOM-killed (137) on the shared box. - `check:query-options-erasure`: the whole-repo eslint ratchet was killed by a 240 s timeout. - All other gates exit 0. --- _Generated by [Claude Code](https://claude.ai/code/session_01D6F9axfoPzR7t8G1J7Ytzz)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 94af3ae commit d0c09e4

36 files changed

Lines changed: 294 additions & 142 deletions

File tree

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
---
2+
'@objectstack/lint': minor
3+
'@objectstack/spec': patch
4+
---
5+
6+
The metadata save door runs the two page component rules that `objectstack validate`, `objectstack build` and `objectstack lint` run. A page saved through Studio, REST `/meta` or an MCP/AI author whose components carry an undeclared, wrong-typed or retired property (`component-props-unknown-key`, `component-props-invalid`), or a component `type` inside a spec-reserved namespace that the platform does not declare (`component-type-unknown`), is refused with `422 INVALID_METADATA`, and each issue carries the fix the CLI already prints. Before, the save answered `200` and the renderer ignored the property or drew a placeholder.
7+
8+
Clause-②: no (narrowing)
9+
10+
<!-- adr-0087: not-required (already-registered element-data-source-and-object-block-filter-rule-array, element-flat-data-binding-retired, element-text-variant-heading-subheading-retired, ui-action-group-menu-member-params-array-only, ui-action-group-menu-members-typed, ui-object-block-grouping-config-typed, ui-object-form-custom-fields-typed, ui-object-form-fields-names-typed, ui-object-form-members-typed, ui-object-form-sections-typed, ui-object-gantt-markers-typed, ui-object-grid-columns-typed, ui-object-grid-export-options-closed, ui-object-grid-kanban-calendar-list-members-typed, ui-object-grid-page-size-positive-integer-refused, ui-object-grid-row-members-typed, ui-object-kanban-conditional-formatting-typed, ui-object-map-gantt-tree-navigation-typed, ui-object-master-detail-form-details-closed, ui-object-metric-aggregate-trend-typed, ui-object-metric-compare-to-typed, ui-object-metric-drill-down-report-typed, ui-object-metric-drill-down-typed, ui-object-timeline-items-typed, ui-object-timeline-mapping-typed, ui-record-line-items-props-closed) Every props shape the save door now refuses was declared, and each narrowing of one recorded in its own D3 entry, before this change; the entries named here are the step's narrowings whose refusal the component-props gate carries, and this diff amends each one whose text said a stored page still saves, to say its next save meets that gate at the metadata save door. The change moves an existing rule onto one more door; it removes no key and rewrites no shape, so it adds no entry and no conversion: nothing on the load path refuses a page. -->
11+
12+
**BREAKING**: an accept-set narrowing on the metadata save door (`PUT /api/v1/meta/page/:name`, the draft promotion `POST /api/v1/meta/page/:name/publish`, the package publish, and every in-process `saveMetaItem` caller on an environment's authoring channel), shipped as `minor` under the launch-window convention for accept-set narrowings (`.changeset/pre.json` is open in `next` pre mode).
13+
14+
**The next save of a stored page whose components carry an undeclared, wrong-typed or retired property, or a reserved-namespace type the platform does not declare, is refused with the fix; stored pages keep serving until then.** The door judges writes only: a stored page row is not re-judged at boot, loads and is served as before, and a draft save is not judged until it is published.
15+
16+
## FROM → TO
17+
18+
| you saved | the door now answers | write instead |
19+
|:--|:--|:--|
20+
| `page:header` with `properties: { titel: 'Deals' }` | `422`, `component-props-unknown-key` at `…properties.titel`, offering `title` | `properties: { title: 'Deals' }` |
21+
| `record:related_list` with `properties: { limit: 'five' }` | `422`, `component-props-invalid`, naming the expected type | `properties: { limit: 5 }` |
22+
| a component typed `page:headr` | `422`, `component-type-unknown`: "Rename `page:headr` → `page:header`." | `type: 'page:header'` |
23+
| a component typed `acme:pipeline_board` (outside the reserved namespaces) | unchanged: saved | nothing |
24+
25+
**The one-line fix: apply what the issue names, then save again.** If a component really does read the key, the props row in `@objectstack/spec` is what is wrong, not the page.
26+
27+
**Who is affected.** A deployment whose stored pages carry such a finding: those pages keep loading and serving, and only their next save is refused until the finding is fixed. `objectstack migrate meta --stored` does not list these findings. The authored pages measured when the props findings became errors (37 pages in this repository and 8 in `objectstack-ai/hotcrm`) carried none, and the in-repo page corpus carries no `component-type-unknown` finding; deployed rows were not measured. The two former ADR-0087 D2 aliases on page component properties, `page:header` `description` (write `subtitle`) and an `element:button` inline `type: 'api'` action's object `params` (write `bodyExtra`), are retired from the load path at 18 and refused at every door with the rename (`objectstack validate`, load, and this save door), so the save door and `objectstack validate` agree on them.
28+
29+
The `@objectstack/spec` change is text only: the reason of each D3 migration entry named above (and the matching step-18 summaries) said a stored page still saves and loads, and now says it still loads and that its next save meets the component-props gate at the metadata save door.

‎.changeset/pre/23103-page-action-row-on-success-and-executor.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ The page action rows are `action:button` and `action:icon` (`properties`), and e
1515
- **`onSuccess`** was any value (`z.unknown()`). The runner reads only `ActionSchema`'s post-success block, `{ navigate, openIn }`, and classifies `navigate` with the url scheme set. So `onSuccess: { navigate: 'javascript:alert(1)' }` parsed clean, and so did a misspelled key or a callback shape, which the runner gives no reading at all. The rows now hold `ActionSchema.onSuccess`'s own block, the same instance by reference, so a row's verdict on any `onSuccess` value is the action's: the scheme set on `navigate`, the rename prescriptions for `url` / `redirectUrl` / `to` / `route` / `path` / `target` / `navigateTo` / `href`, and the closed `openIn` with its `'self'` default.
1616
- **The executor key** (`actionType` on a node, `type` on a member) is a free string, and the rows judged a url `target` only when it read `'url'`. The runner also dispatches `'navigation'`, its local alias of `'url'`, to the same navigator, so `actionType: 'navigation'` with `target: 'javascript:…'` parsed clean. The url executor now has one spelling on the rows: `'navigation'` is refused at the executor key, with the rename onto `'url'`. The key otherwise stays open: a handler a host registers under another name (`navigate_create`, `navigate_edit`, …) is still dispatched by name.
1717

18-
A page's `properties` bag is outside the stack parse. `os validate` judges it through its props gate, which parses the bag against these same rows, so it reports each refusal at the row's own path with the same text, as a `component-props-invalid` finding (an error, like every finding of that gate). A stored page still saves and loads.
18+
A page's `properties` bag is outside the stack parse. `os validate` judges it through its props gate, which parses the bag against these same rows, so it reports each refusal at the row's own path with the same text, as a `component-props-invalid` finding (an error, like every finding of that gate). A stored page still loads; the metadata save door runs the same gate, so the next save of a page carrying a refused row is refused with the same text.
1919

2020
### FROM → TO
2121

‎.changeset/pre/23147-component-props-every-finding-refused.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ Clause-②: no (narrowing)
1616
- `component-props-unknown-key`: a key the component's props row does not declare, at the bag's own level, on a strict object or union arm below it, or on a strip-mode object below it.
1717
- `component-props-invalid`: a value the props row rejects (a wrong type, a missing required prop, a closed-set refusal such as a url action's `target` scheme or a `record:details` section's `count`, a retired enum value), and a missing `dataSource.object` on `element:record_picker`, `element:number` and `element:repeater`.
1818

19-
**Not judged here.** The runtime publish door (the Studio, REST `/meta`, MCP) does not run this rule, so a page saved there is not refused for any of these. `defineStack` and the stack schema keep the open `properties` bag as written. A component `type` that `ComponentPropsMap` carries no row for (a `custom.*` or plugin type) is not judged.
19+
**Not judged here.** `defineStack` and the stack schema keep the open `properties` bag as written. A component `type` that `ComponentPropsMap` carries no row for (a `custom.*` or plugin type) is not judged.
2020

2121
`ComponentPropsSeverity`, the type `@objectstack/lint` exports for these findings, narrows from `'error' | 'warning'` to `'error'`.
2222

‎content/docs/deployment/validating-metadata.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -470,7 +470,7 @@ orthogonal to both, and no cell here can carry it; it is written out in
470470
| Chart bindings outside dashboards (#3583) | ✓ | ✓ | ✓ | — |
471471
| Navigation vs. granted access (ADR-0090 D6) | ✓ | ✓ | ✓ | — |
472472
| SDUI scoped styling (ADR-0065) | ✓ | ✓ | ✓ | — |
473-
| SDUI component props — every `ComponentPropsMap` verdict: an undeclared prop, a value its props schema rejects, a missing `dataSource.object` binding, and a retired prop (`retiredKey()`) refused with its prescription (#23026, #23147) | ✓ | ✓ | ✓ | — |
473+
| SDUI component props — every `ComponentPropsMap` verdict: an undeclared prop, a value its props schema rejects, a missing `dataSource.object` binding, and a retired prop (`retiredKey()`) refused with its prescription (#23026, #23147; at the save door #23388) | ✓ | ✓ | ✓ | ✓ᵖ |
474474
| JSX / React page source parses (ADR-0080/0081) | ✓ | ✓ | ✓ | — |
475475
| Approval-node approvers (ADR-0090 D3) | ✓ | ✓ | ✓ | ✓ᶠ |
476476
| Security posture (ADR-0090 — e.g. every custom object declares `sharingModel`) | ✓ | ✓ | ✓ | ✓ˢᵖᵉᵇᵒ |

‎packages/lint/src/authoring-rules.ts‎

Lines changed: 37 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1149,11 +1149,10 @@ export const AUTHORING_RULES: readonly AuthoringRule[] = [
11491149
//
11501150
// `normalized` for a reason worth stating, since the props bag survives the
11511151
// Zod parse UNCHANGED and both tiers would otherwise carry the same data: the
1152-
// ADR-0087 conversion layer runs inside `normalizeStackInput`, so a converted
1153-
// alias (`page-header-subtitle-alias` rewrites `properties.description` →
1154-
// `subtitle`) is already canonical here and is never reported as undeclared —
1155-
// while a schema error elsewhere in the stack cannot take these findings down
1156-
// with it.
1152+
// ADR-0087 conversion layer runs inside `normalizeStackInput`, so a prop
1153+
// spelling a live D2 conversion still rewrites on the load path is already
1154+
// canonical here and is never reported as undeclared — while a schema error
1155+
// elsewhere in the stack cannot take these findings down with it.
11571156
//
11581157
// Gating: every finding is an `error`, so `os validate`, `os build` and
11591158
// `os lint` refuse an undeclared prop, a value the props schema rejects, a
@@ -1172,24 +1171,28 @@ export const AUTHORING_RULES: readonly AuthoringRule[] = [
11721171
input: 'normalized',
11731172
commands: ALL,
11741173
source: 'packages/lint/src/validate-component-props.ts',
1175-
surfaces: CLI_ONLY,
1174+
// On the runtime door by the ruling of record on #23388 (record
1175+
// 6161173492, letter B), which replaced, for this rule and
1176+
// `validateComponentTypes`, the #4716 discipline both entries were held
1177+
// on (a gating rule reaches the Studio/REST/MCP door only behind a
1178+
// measured false-refusal budget over stored tenant page rows — a
1179+
// discipline a dispatch seat set, not a maintainer ruling). The ruling's
1180+
// principle: a page with a finding is fixed, then saved. No stored-row
1181+
// replay came first, by that ruling.
1182+
//
11761183
// Page-local, MEASURED (#23147): the rule reads `stack.pages` and nothing
11771184
// else — each page's components through `walkPageComponents`, judged
11781185
// against the static `ComponentPropsMap` — and a `page` write's per-write
11791186
// snapshot (`runtime-gate.ts`) carries the written page as `pages[0]`. Over
11801187
// the showcase's 28 pages, the rule's findings through that snapshot were
11811188
// identical to the whole-stack run's findings on the same page, as
11821189
// authored (0 = 0) and with an undeclared key and a wrong-typed prop
1183-
// planted (19 = 19). So RUNTIME_NEEDS_FULL_SNAPSHOT, which this entry
1184-
// carried until then, was false here. What holds the rule off the runtime
1185-
// door is the `validateComponentTypes` discipline: a measured false-refusal
1186-
// budget over STORED tenant page rows, which no population measured so far
1187-
// is. Crossing is its own rollout decision.
1188-
surfaceReason:
1189-
'Gating rule held off the runtime door pending the crossing discipline the gating object rules ' +
1190-
'went through: a measured false-refusal budget, here over stored tenant page rows. The rule is ' +
1191-
'page-local and a page write\'s snapshot carries everything it reads; the 0-finding inventories ' +
1192-
'cover authored config-file metadata only. Crossing is its own rollout card.',
1190+
// planted (19 = 19). So the crossing is this `surfaces`/`runtimeTypes`
1191+
// pair and nothing else. Stored pages keep loading and serving: the gate
1192+
// judges writes, never the read path, so only the next save of a page
1193+
// with a finding is refused, with the fix the finding carries.
1194+
surfaces: CLI_AND_RUNTIME,
1195+
runtimeTypes: ['page'],
11931196
run: (stack) => validateComponentProps(stack),
11941197
},
11951198
// The TYPE half of the same door (#12183's ruling): a component `type` inside
@@ -1204,29 +1207,29 @@ export const AUTHORING_RULES: readonly AuthoringRule[] = [
12041207
input: 'normalized',
12051208
commands: ALL,
12061209
source: 'packages/lint/src/validate-component-types.ts',
1207-
surfaces: CLI_ONLY,
1208-
// Page-local judgment, so the per-write snapshot IS sufficient — what the
1209-
// crossing owes is not a wider snapshot but the #4716 discipline: a gating
1210-
// rule reaches the Studio/REST/MCP door only behind a measured
1211-
// false-refusal budget over REAL stored page rows, and the population this
1212-
// rule was measured on (the in-repo corpus, 0 findings) is authored
1213-
// config-file metadata, not tenant rows. A tenant page carrying a
1214-
// reserved-namespace type their own plugin registers would be refused at
1215-
// the only door that tenant has. Crossing is its own rollout decision with
1216-
// that replay as its evidence, not a bare `runtimeTypes` edit.
1217-
surfaceReason:
1218-
'Gating rule held off the runtime door pending the crossing discipline the gating object rules ' +
1219-
'went through: a measured false-refusal budget, here over stored tenant page rows (the in-repo ' +
1220-
'0-finding measurement covers ' +
1221-
'authored config-file metadata only). Crossing is its own rollout card.',
1210+
// On the runtime door by the same ruling of record as
1211+
// `validateComponentProps` above (#23388, record 6161173492, letter B),
1212+
// which crossed this rule in the same change: the #4716 stored-row
1213+
// false-refusal budget it was held on was seat-set, and the maintainer's
1214+
// principle — fix the page, then save it — applies unchanged. A page
1215+
// carrying a type inside a spec-reserved namespace the platform does not
1216+
// declare ships a placeholder to end users, and the refusal names the fix
1217+
// (a declared type, or a type outside the reserved namespaces). Page-local
1218+
// judgment, so the per-write snapshot IS sufficient: the crossing is this
1219+
// `surfaces`/`runtimeTypes` pair and nothing else. Stored pages keep
1220+
// loading and serving; only their next save is judged.
1221+
surfaces: CLI_AND_RUNTIME,
1222+
runtimeTypes: ['page'],
12221223
run: (stack) => validateComponentTypes(stack),
12231224
},
12241225
// #22158, card ① of the ruling of record on #8346 (letter B′): a page that
12251226
// declares `print` is a document, and every block in it must come from the
12261227
// spec's printable block subset (`PRINTABLE_PAGE_COMPONENT_TYPES`). Gating,
1227-
// and on the runtime door from birth — the rule's header says why the
1228-
// false-refusal budget `validateComponentTypes` is held on is zero here by
1229-
// construction: no page could carry `print` before this rule landed.
1228+
// and on the runtime door from birth — the rule's header says why a
1229+
// stored-row false-refusal budget was zero here by construction: no page
1230+
// could carry `print` before this rule landed. (Its siblings
1231+
// `validateComponentProps` and `validateComponentTypes` crossed later, on
1232+
// the ruling of record on #23388, record 6161173492.)
12301233
{
12311234
name: 'validatePrintPageBlocks',
12321235
tier: 'gating',

‎packages/lint/src/validate-component-types.ts‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,9 @@
8585
* types as the lit control in the same query. Every in-tree occurrence of a
8686
* retired name is the map itself, a tombstone prescription, conversion or
8787
* migration registry data, a test, or prose. ⚠️ That reading covers authored
88-
* config-file metadata only — the same scope limit the rule's `surfaceReason`
89-
* records for stored tenant rows.
88+
* config-file metadata only, not stored tenant rows. The rule runs on the
89+
* runtime save door too (the ruling of record on #23388): a stored row is
90+
* judged on its next save, never on load.
9091
*/
9192

9293
import {

‎packages/lint/src/validate-print-page-blocks.ts‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,10 @@
4343
* declare before this rule landed — `PageSchema` is closed, so no stored page
4444
* row and no authored config file could carry it. The population of print
4545
* pages is therefore EMPTY at landing, in the repo and in every tenant's
46-
* stored rows alike: the false-refusal budget the sibling
47-
* `validateComponentTypes` is still waiting on (measured over stored tenant
48-
* rows) is zero by construction here, not by sampling. And the judgment is
46+
* stored rows alike: a false-refusal budget over stored tenant rows (the
47+
* one the sibling `validateComponentTypes` was held on until the ruling of
48+
* record on #23388 crossed it) is zero by construction here, not by
49+
* sampling. And the judgment is
4950
* page-local, so a `page` write's per-write snapshot — exactly one page, its
5051
* own — is all it reads. Hence `surfaces: CLI_AND_RUNTIME` with
5152
* `runtimeTypes: ['page']` in the registry: Studio, REST `/meta` and MCP

0 commit comments

Comments
 (0)