From 99eb19cbe0a0729f12d4a80422f4c3d074b3c9e5 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 14 Oct 2026 05:42:49 +0000 Subject: [PATCH 1/4] feat(lint)!: validateComponentProps and validateComponentTypes run on the runtime publish door for page writes Ruling of record 6161173492 (letter B): both page component rules cross to the Studio/REST/MCP save door with runtimeTypes ['page']; the #4716 stored-row discipline the entries cited is replaced by that ruling. Claude-Session: https://claude.ai/code/session_01D6F9axfoPzR7t8G1J7Ytzz Co-authored-by: Claude --- packages/lint/src/authoring-rules.ts | 62 ++++++++++--------- packages/lint/src/validate-component-types.ts | 5 +- .../lint/src/validate-print-page-blocks.ts | 7 ++- 3 files changed, 40 insertions(+), 34 deletions(-) diff --git a/packages/lint/src/authoring-rules.ts b/packages/lint/src/authoring-rules.ts index 84239cbee73..ac678fa771e 100644 --- a/packages/lint/src/authoring-rules.ts +++ b/packages/lint/src/authoring-rules.ts @@ -1172,7 +1172,15 @@ export const AUTHORING_RULES: readonly AuthoringRule[] = [ input: 'normalized', commands: ALL, source: 'packages/lint/src/validate-component-props.ts', - surfaces: CLI_ONLY, + // On the runtime door by the ruling of record on #23388 (record + // 6161173492, letter B), which replaced, for this rule and + // `validateComponentTypes`, the #4716 discipline both entries were held + // on (a gating rule reaches the Studio/REST/MCP door only behind a + // measured false-refusal budget over stored tenant page rows — a + // discipline a dispatch seat set, not a maintainer ruling). The ruling's + // principle: a page with a finding is fixed, then saved. No stored-row + // replay came first, by that ruling. + // // Page-local, MEASURED (#23147): the rule reads `stack.pages` and nothing // else — each page's components through `walkPageComponents`, judged // against the static `ComponentPropsMap` — and a `page` write's per-write @@ -1180,16 +1188,12 @@ export const AUTHORING_RULES: readonly AuthoringRule[] = [ // the showcase's 28 pages, the rule's findings through that snapshot were // identical to the whole-stack run's findings on the same page, as // authored (0 = 0) and with an undeclared key and a wrong-typed prop - // planted (19 = 19). So RUNTIME_NEEDS_FULL_SNAPSHOT, which this entry - // carried until then, was false here. What holds the rule off the runtime - // door is the `validateComponentTypes` discipline: a measured false-refusal - // budget over STORED tenant page rows, which no population measured so far - // is. Crossing is its own rollout decision. - surfaceReason: - 'Gating rule held off the runtime door pending the crossing discipline the gating object rules ' + - 'went through: a measured false-refusal budget, here over stored tenant page rows. The rule is ' + - 'page-local and a page write\'s snapshot carries everything it reads; the 0-finding inventories ' + - 'cover authored config-file metadata only. Crossing is its own rollout card.', + // planted (19 = 19). So the crossing is this `surfaces`/`runtimeTypes` + // pair and nothing else. Stored pages keep loading and serving: the gate + // judges writes, never the read path, so only the next save of a page + // with a finding is refused, with the fix the finding carries. + surfaces: CLI_AND_RUNTIME, + runtimeTypes: ['page'], run: (stack) => validateComponentProps(stack), }, // The TYPE half of the same door (#12183's ruling): a component `type` inside @@ -1204,29 +1208,29 @@ export const AUTHORING_RULES: readonly AuthoringRule[] = [ input: 'normalized', commands: ALL, source: 'packages/lint/src/validate-component-types.ts', - surfaces: CLI_ONLY, - // Page-local judgment, so the per-write snapshot IS sufficient — what the - // crossing owes is not a wider snapshot but the #4716 discipline: a gating - // rule reaches the Studio/REST/MCP door only behind a measured - // false-refusal budget over REAL stored page rows, and the population this - // rule was measured on (the in-repo corpus, 0 findings) is authored - // config-file metadata, not tenant rows. A tenant page carrying a - // reserved-namespace type their own plugin registers would be refused at - // the only door that tenant has. Crossing is its own rollout decision with - // that replay as its evidence, not a bare `runtimeTypes` edit. - surfaceReason: - 'Gating rule held off the runtime door pending the crossing discipline the gating object rules ' + - 'went through: a measured false-refusal budget, here over stored tenant page rows (the in-repo ' + - '0-finding measurement covers ' + - 'authored config-file metadata only). Crossing is its own rollout card.', + // On the runtime door by the same ruling of record as + // `validateComponentProps` above (#23388, record 6161173492, letter B), + // which crossed this rule in the same change: the #4716 stored-row + // false-refusal budget it was held on was seat-set, and the maintainer's + // principle — fix the page, then save it — applies unchanged. A page + // carrying a type inside a spec-reserved namespace the platform does not + // declare ships a placeholder to end users, and the refusal names the fix + // (a declared type, or a type outside the reserved namespaces). Page-local + // judgment, so the per-write snapshot IS sufficient: the crossing is this + // `surfaces`/`runtimeTypes` pair and nothing else. Stored pages keep + // loading and serving; only their next save is judged. + surfaces: CLI_AND_RUNTIME, + runtimeTypes: ['page'], run: (stack) => validateComponentTypes(stack), }, // #22158, card ① of the ruling of record on #8346 (letter B′): a page that // declares `print` is a document, and every block in it must come from the // spec's printable block subset (`PRINTABLE_PAGE_COMPONENT_TYPES`). Gating, - // and on the runtime door from birth — the rule's header says why the - // false-refusal budget `validateComponentTypes` is held on is zero here by - // construction: no page could carry `print` before this rule landed. + // and on the runtime door from birth — the rule's header says why a + // stored-row false-refusal budget was zero here by construction: no page + // could carry `print` before this rule landed. (Its siblings + // `validateComponentProps` and `validateComponentTypes` crossed later, on + // the ruling of record on #23388, record 6161173492.) { name: 'validatePrintPageBlocks', tier: 'gating', diff --git a/packages/lint/src/validate-component-types.ts b/packages/lint/src/validate-component-types.ts index f6e002a49cc..2c55d59b847 100644 --- a/packages/lint/src/validate-component-types.ts +++ b/packages/lint/src/validate-component-types.ts @@ -85,8 +85,9 @@ * types as the lit control in the same query. Every in-tree occurrence of a * retired name is the map itself, a tombstone prescription, conversion or * migration registry data, a test, or prose. ⚠️ That reading covers authored - * config-file metadata only — the same scope limit the rule's `surfaceReason` - * records for stored tenant rows. + * config-file metadata only, not stored tenant rows. The rule runs on the + * runtime save door too (the ruling of record on #23388): a stored row is + * judged on its next save, never on load. */ import { diff --git a/packages/lint/src/validate-print-page-blocks.ts b/packages/lint/src/validate-print-page-blocks.ts index 7e3eae2d633..33f53798158 100644 --- a/packages/lint/src/validate-print-page-blocks.ts +++ b/packages/lint/src/validate-print-page-blocks.ts @@ -43,9 +43,10 @@ * declare before this rule landed — `PageSchema` is closed, so no stored page * row and no authored config file could carry it. The population of print * pages is therefore EMPTY at landing, in the repo and in every tenant's - * stored rows alike: the false-refusal budget the sibling - * `validateComponentTypes` is still waiting on (measured over stored tenant - * rows) is zero by construction here, not by sampling. And the judgment is + * stored rows alike: a false-refusal budget over stored tenant rows (the + * one the sibling `validateComponentTypes` was held on until the ruling of + * record on #23388 crossed it) is zero by construction here, not by + * sampling. And the judgment is * page-local, so a `page` write's per-write snapshot — exactly one page, its * own — is all it reads. Hence `surfaces: CLI_AND_RUNTIME` with * `runtimeTypes: ['page']` in the registry: Studio, REST `/meta` and MCP From ba20e780ec9f344104d6a17c19f47f38b68a74ab Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 14 Oct 2026 05:59:58 +0000 Subject: [PATCH 2/4] fix(spec,lint): the save-door pins for the two page component rules, the D3 texts that said a stored page still saves, and the changeset Claude-Session: https://claude.ai/code/session_01D6F9axfoPzR7t8G1J7Ytzz Co-authored-by: Claude --- ...page-action-row-on-success-and-executor.md | 2 +- ...7-component-props-every-finding-refused.md | 2 +- ...23388-page-component-rules-runtime-door.md | 29 ++++ .../protocol.runtime-authoring-gate.test.ts | 136 ++++++++++++++++++ ...urce-and-object-block-filter-rule-array.ts | 4 +- ...ion-group-menu-member-params-array-only.ts | 5 +- .../18.ui-action-group-menu-members-typed.ts | 5 +- .../18.ui-object-form-custom-fields-typed.ts | 6 +- .../18.ui-object-form-fields-names-typed.ts | 3 +- .../18.ui-object-form-members-typed.ts | 6 +- .../18.ui-object-form-sections-typed.ts | 6 +- .../18.ui-object-gantt-markers-typed.ts | 5 +- .../18.ui-object-grid-columns-typed.ts | 5 +- ...18.ui-object-grid-export-options-closed.ts | 6 +- ...grid-kanban-calendar-list-members-typed.ts | 6 +- ...grid-page-size-positive-integer-refused.ts | 6 +- .../18.ui-object-grid-row-members-typed.ts | 6 +- ...ect-kanban-conditional-formatting-typed.ts | 6 +- ...-object-map-gantt-tree-navigation-typed.ts | 6 +- ...bject-master-detail-form-details-closed.ts | 6 +- ....ui-object-metric-aggregate-trend-typed.ts | 5 +- .../18.ui-object-metric-compare-to-typed.ts | 5 +- ...i-object-metric-drill-down-report-typed.ts | 6 +- .../18.ui-object-metric-drill-down-typed.ts | 6 +- .../18.ui-object-timeline-items-typed.ts | 5 +- .../18.ui-object-timeline-mapping-typed.ts | 4 +- ...e-action-row-name-leg-url-target-judged.ts | 3 +- ...-action-row-navigation-executor-refused.ts | 3 +- ...-row-on-success-beside-executor-refused.ts | 3 +- ...page-action-row-on-success-shape-closed.ts | 3 +- .../18.ui-record-line-items-props-closed.ts | 6 +- .../spec/src/migrations/registry.ts.template | 46 +++--- 32 files changed, 249 insertions(+), 102 deletions(-) create mode 100644 .changeset/23388-page-component-rules-runtime-door.md diff --git a/.changeset/23103-page-action-row-on-success-and-executor.md b/.changeset/23103-page-action-row-on-success-and-executor.md index 72caaeb46e5..0a93691a00d 100644 --- a/.changeset/23103-page-action-row-on-success-and-executor.md +++ b/.changeset/23103-page-action-row-on-success-and-executor.md @@ -15,7 +15,7 @@ The page action rows are `action:button` and `action:icon` (`properties`), and e - **`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. - **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. -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. +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. ### FROM → TO diff --git a/.changeset/23147-component-props-every-finding-refused.md b/.changeset/23147-component-props-every-finding-refused.md index a1f6eade048..6a2cbbbbc19 100644 --- a/.changeset/23147-component-props-every-finding-refused.md +++ b/.changeset/23147-component-props-every-finding-refused.md @@ -16,7 +16,7 @@ Clause-②: no (narrowing) - `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. - `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`. -**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. +**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. `ComponentPropsSeverity`, the type `@objectstack/lint` exports for these findings, narrows from `'error' | 'warning'` to `'error'`. diff --git a/.changeset/23388-page-component-rules-runtime-door.md b/.changeset/23388-page-component-rules-runtime-door.md new file mode 100644 index 00000000000..2af4bf4b462 --- /dev/null +++ b/.changeset/23388-page-component-rules-runtime-door.md @@ -0,0 +1,29 @@ +--- +'@objectstack/lint': minor +'@objectstack/spec': patch +--- + +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. + +Clause-②: no (narrowing) + + + +**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). + +**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. + +## FROM → TO + +| you saved | the door now answers | write instead | +|:--|:--|:--| +| `page:header` with `properties: { titel: 'Deals' }` | `422`, `component-props-unknown-key` at `…properties.titel`, offering `title` | `properties: { title: 'Deals' }` | +| `record:related_list` with `properties: { limit: 'five' }` | `422`, `component-props-invalid`, naming the expected type | `properties: { limit: 5 }` | +| a component typed `page:headr` | `422`, `component-type-unknown`: "Rename `page:headr` → `page:header`." | `type: 'page:header'` | +| a component typed `acme:pipeline_board` (outside the reserved namespaces) | unchanged: saved | nothing | + +**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. + +**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. Two live-window ADR-0087 D2 aliases on page component properties are converted at `objectstack validate` and at load but not at this door, so a save that spells one is refused with the canonical key: `page:header` `description` (write `subtitle`) and an `element:button` inline `type: 'api'` action's object `params` (write `bodyExtra`). + +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. diff --git a/packages/metadata-protocol/src/protocol.runtime-authoring-gate.test.ts b/packages/metadata-protocol/src/protocol.runtime-authoring-gate.test.ts index 0bfeb1d313e..4747a670c8b 100644 --- a/packages/metadata-protocol/src/protocol.runtime-authoring-gate.test.ts +++ b/packages/metadata-protocol/src/protocol.runtime-authoring-gate.test.ts @@ -3061,3 +3061,139 @@ describe('runtime authoring gate on OBJECT writes — the object action predicat } }); }); + +// #23388 — the two page component rules at the runtime save door, ruling of +// record 6161173492 (letter B): `validateComponentProps` and +// `validateComponentTypes` run on a `page` write (`runtimeTypes: ['page']`), +// so a page saved through Studio, REST `/meta` or MCP meets the refusal +// `os validate` / `os build` / `os lint` give, with the same fix. The +// maintainer's principle: a page with a finding is fixed, then saved. Stored +// pages keep loading and serving — the gate judges writes, never the read +// path — and only the next save of a page with a finding is refused. +describe('runtime authoring gate on PAGE writes — the component props and type rules (#23388)', () => { + const page = (components: unknown[]) => ({ + name: 'deals_home', + label: 'Deals', + type: 'app', + regions: [{ name: 'main', components }], + }); + /** Clean: every prop is one `page:header` declares. */ + const cleanPage = () => page([{ type: 'page:header', properties: { title: 'Deals', subtitle: 'Open pipeline' } }]); + /** An undeclared prop: the near-miss `titel` of the declared `title`. */ + const undeclaredPropPage = () => page([{ type: 'page:header', properties: { titel: 'Deals' } }]); + /** A type inside the reserved `page:` namespace the platform does not declare. */ + const reservedTypePage = () => page([{ type: 'page:headr', properties: { title: 'Deals' } }]); + + const savePage = (protocol: any, item: unknown, extra: Record = {}) => + protocol.saveMetaItem({ type: 'page', name: 'deals_home', item, ...extra }); + const activePageRows = (rows: Map) => + Array.from(rows.values()).filter((r) => r.type === 'page' && r.state === 'active'); + + let warn: ReturnType; + beforeEach(() => { + warn = vi.spyOn(console, 'warn').mockImplementation(() => {}); + delete process.env.OS_ALLOW_UNLINTED_METADATA_WRITES; + }); + afterEach(() => { + warn.mockRestore(); + delete process.env.OS_ALLOW_UNLINTED_METADATA_WRITES; + }); + + it('(a) refuses a page carrying an undeclared component prop, and the refusal carries the fix', async () => { + const { protocol, rows } = makeProtocol(); + + const err = await savePage(protocol, undeclaredPropPage()).catch((e: any) => e); + + expect(err).toBeInstanceOf(Error); + expect(err.status).toBe(422); + expect(err.code).toBe('INVALID_METADATA'); + expect(err.message).toMatch(/^page\/deals_home failed author-time validation: 1 issue — /); + const issue = err.issues.find((i: any) => i.rule === 'component-props-unknown-key'); + expect(issue, `issues: ${JSON.stringify(err.issues)}`).toBeDefined(); + expect(issue.path).toBe('pages[0].regions[0].components[0].properties.titel'); + // The fix: the declared spelling, named in the verdict, and the remedy line. + expect(issue.message).toContain('`titel` → `title`'); + expect(issue.hint).toContain('Remove `titel`'); + expect(err.rulesRun).toEqual(expect.arrayContaining(['validateComponentProps', 'validateComponentTypes'])); + // Nothing landed. + expect(activePageRows(rows)).toEqual([]); + }); + + it('(a) gates the draft → active promotion of the same page, so the draft door is no bypass', async () => { + const { protocol } = makeProtocol(); + await expect(savePage(protocol, undeclaredPropPage(), { mode: 'draft' })) + .resolves.toMatchObject({ success: true }); + + const err = await protocol.publishMetaItem({ type: 'page', name: 'deals_home' }).catch((e: any) => e); + + expect(err?.status).toBe(422); + expect(err.code).toBe('INVALID_METADATA'); + expect(err.issues.map((i: any) => i.rule)).toContain('component-props-unknown-key'); + }); + + it('(c) refuses a page carrying an undeclared type inside a reserved namespace, naming the declared type', async () => { + const { protocol, rows } = makeProtocol(); + + const err = await savePage(protocol, reservedTypePage()).catch((e: any) => e); + + expect(err).toBeInstanceOf(Error); + expect(err.status).toBe(422); + expect(err.code).toBe('INVALID_METADATA'); + const issue = err.issues.find((i: any) => i.rule === 'component-type-unknown'); + expect(issue, `issues: ${JSON.stringify(err.issues)}`).toBeDefined(); + expect(issue.path).toBe('pages[0].regions[0].components[0].type'); + expect(issue.hint).toBe('Rename `page:headr` → `page:header`.'); + expect(activePageRows(rows)).toEqual([]); + }); + + it('the control: the same page with its declared prop and type saves', async () => { + const { protocol, rows } = makeProtocol(); + + await expect(savePage(protocol, cleanPage())).resolves.toMatchObject({ success: true }); + + expect(activePageRows(rows)).toHaveLength(1); + // A type outside the spec's reserved namespaces is the open arm's story + // and stays untouched (custom/registered component types). + await expect(savePage(protocol, page([{ type: 'acme:pipeline_board' }]))) + .resolves.toMatchObject({ success: true }); + }); + + it('(b) a stored page with a finding keeps loading and serving; its next save is refused until fixed', async () => { + const { engine, rows } = makeStubEngine(); + const registered: Array<{ type: string; name: unknown }> = []; + engine.registry.registerItem = (type: string, item: { name?: unknown }) => { + registered.push({ type, name: item?.name }); + }; + const protocol = new ObjectStackProtocolImplementation(engine, () => new Map(), 'env_test') as any; + // Stored before the crossing: both findings at once, seeded straight + // into the store because the save door now refuses this body. + const stored = page([ + { type: 'page:header', properties: { titel: 'Deals' } }, + { type: 'page:headr' }, + ]); + rows.set(keyOf({ type: 'page', name: 'deals_home', organization_id: null, state: 'active' }), { + id: 'r_seed', type: 'page', name: 'deals_home', organization_id: null, state: 'active', + metadata: JSON.stringify(stored), + }); + + // Loads: the boot path runs no authoring rule. + const result = await protocol.loadMetaFromDb(); + expect(result).toMatchObject({ loaded: 1, errors: 0, invalid: 0 }); + expect(registered).toContainEqual({ type: 'page', name: 'deals_home' }); + // Serves: the read door hands the stored body back as stored. + const served: any = (await protocol.getMetaItem({ type: 'page', name: 'deals_home' })).item; + expect(served?.regions?.[0]?.components?.map((c: any) => c.type)).toEqual(['page:header', 'page:headr']); + + // The next save of the same body is a write, and is judged. + const err = await savePage(protocol, stored).catch((e: any) => e); + expect(err?.status).toBe(422); + expect(err.code).toBe('INVALID_METADATA'); + expect(err.issues.map((i: any) => i.rule).sort()).toEqual(['component-props-unknown-key', 'component-type-unknown']); + // The stored row is untouched by the refusal. + expect(JSON.parse(activePageRows(rows)[0]!.metadata)).toEqual(stored); + + // Fixed, then saved. + await expect(savePage(protocol, cleanPage())).resolves.toMatchObject({ success: true }); + expect(JSON.parse(activePageRows(rows)[0]!.metadata)).toEqual(cleanPage()); + }); +}); diff --git a/packages/spec/src/migrations/entries/semantic/18.element-data-source-and-object-block-filter-rule-array.ts b/packages/spec/src/migrations/entries/semantic/18.element-data-source-and-object-block-filter-rule-array.ts index 6651d710c50..bc15571dfb9 100644 --- a/packages/spec/src/migrations/entries/semantic/18.element-data-source-and-object-block-filter-rule-array.ts +++ b/packages/spec/src/migrations/entries/semantic/18.element-data-source-and-object-block-filter-rule-array.ts @@ -87,8 +87,8 @@ export const entry: SemanticMigration = { + 'contract), and its `filter` door refuses the form: at `dataSource.filter` on the page\'s ' + 'next save; at a block\'s `properties.filter` — like `properties.defaultFilters`, a key of ' + 'the open `properties` bag — only as the component-props gate\'s error ' - + '(os validate, os build, os lint), since a re-save through the metadata API is not ' - + 'refused there. For a combinator record that refusal names the combinator and says why no ' + + '(os validate, os build, os lint), and at the metadata save door, which runs that gate too. ' + + 'For a combinator record that refusal names the combinator and says why no ' + 'rule spells it. `os migrate meta --stored` lists each such filter as a TODO under its ' + 'row, naming the block and what blocks the rewrite (a value that is not a record or AST ' + 'form at all — a bare string or number one of the former `z.unknown()` doors took — is ' diff --git a/packages/spec/src/migrations/entries/semantic/18.ui-action-group-menu-member-params-array-only.ts b/packages/spec/src/migrations/entries/semantic/18.ui-action-group-menu-member-params-array-only.ts index 673a84c125f..188f4d8afc2 100644 --- a/packages/spec/src/migrations/entries/semantic/18.ui-action-group-menu-member-params-array-only.ts +++ b/packages/spec/src/migrations/entries/semantic/18.ui-action-group-menu-member-params-array-only.ts @@ -11,7 +11,7 @@ import type { SemanticMigration } from '../../types.js'; // member has no static-values spelling and the container drops a non-array // `params` on any other type at run time. D3 only: page-component `properties` // is not parsed on the metadata save or load path, so a stored page is never -// refused; there is no D2 conversion, because the only home for static values +// refused at load; there is no D2 conversion, because the only home for static values // is a different node (an `action:button`), which no rewrite can build in the // author's place; and the census found no writer to respell. export const entry: SemanticMigration = { @@ -34,8 +34,7 @@ export const entry: SemanticMigration = { + '`actions.N.params`, with that prescription. The `api` member\'s object `params` is unchanged. It is read ' + 'where every page component\'s props are: the component-props gate reports the refusal as a ' + '`component-props-invalid` error on `objectstack validate`, `objectstack build` and `objectstack lint`, and a ' - + 'stored page still saves and loads, because a page component\'s `properties` is not parsed on the metadata ' - + 'save or load path. No conversion is registered: the static values belong on a different node, and the ' + + 'stored page still loads, because a page component\'s `properties` is not parsed on the metadata load path, and the next save of the page meets the component-props gate at the metadata save door. No conversion is registered: the static values belong on a different node, and the ' + 'census found no writer. Deployed metadata NOT MEASURED.', acceptanceCriteria: 'Every `action:group` and `action:menu` node validates: `objectstack validate` reports no ' + '`component-props-invalid` finding at `properties.actions.N.params`. Each member whose action needs static ' diff --git a/packages/spec/src/migrations/entries/semantic/18.ui-action-group-menu-members-typed.ts b/packages/spec/src/migrations/entries/semantic/18.ui-action-group-menu-members-typed.ts index e1a268ca806..963f3d7c171 100644 --- a/packages/spec/src/migrations/entries/semantic/18.ui-action-group-menu-members-typed.ts +++ b/packages/spec/src/migrations/entries/semantic/18.ui-action-group-menu-members-typed.ts @@ -9,7 +9,7 @@ import type { SemanticMigration } from '../../types.js'; // with the rows' prescriptions; `outcomeMessages`, a member `className` and // `properties.params` refused; `outcomeMessages` undeclared on all four action // blocks. D3 only: page-component `properties` is not parsed on the metadata -// save or load path, so a stored page is never refused; and the authored census +// save or load path, so a stored page is never refused at load; and the authored census // found no working member to respell — the refused values are objectui's probes // of the very reads the ruling refuses (a member `className`, `outcomeMessages`, // `properties.params`) and of the host's `autoTrigger` flag. @@ -34,8 +34,7 @@ export const entry: SemanticMigration = { + '`className`, a member `properties.params` — are refused, and `outcomeMessages` stays undeclared on all four ' + 'action blocks as one decision. It is read where every page component\'s props are: the component-props ' + 'gate reports a refused value as a `component-props-invalid` / `component-props-unknown-key` ' - + 'error on `objectstack validate`, `objectstack build` and `objectstack lint`, and a stored page still saves ' - + 'and loads, because a page component\'s `properties` is not parsed on the metadata save or load path. No ' + + 'error on `objectstack validate`, `objectstack build` and `objectstack lint`, and a stored page still loads, because a page component\'s `properties` is not parsed on the metadata load path, and the next save of the page meets the component-props gate at the metadata save door. No ' + 'conversion is registered: nothing on the load path refuses the shape, and the authored census found no ' + 'working member to respell. Deployed metadata NOT MEASURED.', acceptanceCriteria: 'Every `action:group` and `action:menu` node validates: `objectstack validate` reports no ' diff --git a/packages/spec/src/migrations/entries/semantic/18.ui-object-form-custom-fields-typed.ts b/packages/spec/src/migrations/entries/semantic/18.ui-object-form-custom-fields-typed.ts index 2835653a4d8..2cf54c516b0 100644 --- a/packages/spec/src/migrations/entries/semantic/18.ui-object-form-custom-fields-typed.ts +++ b/packages/spec/src/migrations/entries/semantic/18.ui-object-form-custom-fields-typed.ts @@ -8,7 +8,7 @@ import type { SemanticMigration } from '../../types.js'; // snake_case. The maintainer ruled on #21704 (fork 2, letter B) that the spec // declares a closed runtime form field of the members the form draws, in // camelCase, keyed by `name`. D3 only: page-component `properties` is not -// parsed on the metadata save or load path, so a stored page is never refused; +// parsed on the metadata save or load path, so a stored page is never refused at load; // and the authored census, with every inline option list evaluated, found no // working member to respell — the refused values are a type-level test's // `visibleOn` and a fixture pinning that an inline `defaultValue` seeds @@ -36,9 +36,7 @@ export const entry: SemanticMigration = { + 'no object column, so a stored field\'s lowercase identifier rule does not apply to it. It is read where ' + 'every page component\'s props are: the component-props gate ' + 'reports a refused value as a `component-props-invalid` / `component-props-unknown-key` ' - + 'error on `objectstack validate`, `objectstack build` and `objectstack lint`, and a stored page still ' - + 'saves and loads, because a page component\'s `properties` is not parsed on the metadata save or load ' - + 'path. No conversion is registered: nothing on the load path refuses the shape, and the authored census, ' + + 'error on `objectstack validate`, `objectstack build` and `objectstack lint`, and a stored page still loads, because a page component\'s `properties` is not parsed on the metadata load path, and the next save of the page meets the component-props gate at the metadata save door. No conversion is registered: nothing on the load path refuses the shape, and the authored census, ' + 'with every inline option list evaluated, found no working member to respell. Deployed metadata NOT ' + 'MEASURED.', acceptanceCriteria: 'Every `object-form` node validates: `objectstack validate` reports no ' diff --git a/packages/spec/src/migrations/entries/semantic/18.ui-object-form-fields-names-typed.ts b/packages/spec/src/migrations/entries/semantic/18.ui-object-form-fields-names-typed.ts index 12aa4871bbe..a7264db5f32 100644 --- a/packages/spec/src/migrations/entries/semantic/18.ui-object-form-fields-names-typed.ts +++ b/packages/spec/src/migrations/entries/semantic/18.ui-object-form-fields-names-typed.ts @@ -32,8 +32,7 @@ export const entry: SemanticMigration = { + 'the form skips at the top level with a console warning — is its bare name or belongs in a section. It is ' + 'read where every page component\'s props are: the component-props gate reports a refused value as a ' + '`component-props-invalid` error on `objectstack validate`, `objectstack build` and ' - + '`objectstack lint`, and a stored page still saves and loads, because a page component\'s `properties` is ' - + 'not parsed on the metadata save or load path. No conversion is registered: nothing on the load path ' + + '`objectstack lint`, and a stored page still loads, because a page component\'s `properties` is not parsed on the metadata load path, and the next save of the page meets the component-props gate at the metadata save door. No conversion is registered: nothing on the load path ' + 'refuses the shape, the form already draws a stored `{ name }` entry by its name, and an override written ' + 'beside it has no rewrite that keeps it — moving it onto a section is the judgment this entry leaves to ' + 'the upgrader. Deployed metadata NOT MEASURED.', diff --git a/packages/spec/src/migrations/entries/semantic/18.ui-object-form-members-typed.ts b/packages/spec/src/migrations/entries/semantic/18.ui-object-form-members-typed.ts index b384035c35e..573e0acb556 100644 --- a/packages/spec/src/migrations/entries/semantic/18.ui-object-form-members-typed.ts +++ b/packages/spec/src/migrations/entries/semantic/18.ui-object-form-members-typed.ts @@ -12,7 +12,7 @@ import type { SemanticMigration } from '../../types.js'; // field inside a section, which the typed shapes would refuse), and // `customFields` waits for the spec to declare objectui's runtime form field. // D3 only: page-component `properties` is not parsed on the metadata save or -// load path, so a stored page is never refused; an off-shape value has no +// load path, so a stored page is never refused at load; an off-shape value has no // rewrite that says what the author meant; and the authored census found no // authored value to respell — the refused values are fixtures probing that the // form refuses them. @@ -41,8 +41,8 @@ export const entry: SemanticMigration = { + 'runtime form field its entries are. It is read where every page component\'s props are: the ' + 'component-props gate reports a refused value as a `component-props-invalid` / ' + '`component-props-unknown-key` error on `objectstack validate`, `objectstack build` and ' - + '`objectstack lint`, and a stored page still saves and loads, because a page component\'s ' - + '`properties` is not parsed on the metadata save or load path. No conversion is registered: nothing ' + + '`objectstack lint`, and a stored page still loads, because a page component\'s ' + + '`properties` is not parsed on the metadata load path, and the next save of the page meets the component-props gate at the metadata save door. No conversion is registered: nothing ' + 'on the load path refuses the shape, and an off-shape value has no rewrite that both keeps what the ' + 'form shows today and honours what the author wrote — which is the judgment this entry leaves to the ' + 'upgrader. Deployed metadata NOT MEASURED.', diff --git a/packages/spec/src/migrations/entries/semantic/18.ui-object-form-sections-typed.ts b/packages/spec/src/migrations/entries/semantic/18.ui-object-form-sections-typed.ts index 3d571cf6c23..c55cf19053d 100644 --- a/packages/spec/src/migrations/entries/semantic/18.ui-object-form-sections-typed.ts +++ b/packages/spec/src/migrations/entries/semantic/18.ui-object-form-sections-typed.ts @@ -10,7 +10,7 @@ import type { SemanticMigration } from '../../types.js'; // keys plus the three entry arms the form reads, canonical spellings only — and // the stored form view is unchanged. D3 only: page-component `properties` is // not parsed on the metadata save or load path, so a stored page is never -// refused; and the authored census, with every inline option list evaluated, +// refused at load; and the authored census, with every inline option list evaluated, // found no working section to respell — the refused values are the two probes // in objectui's `formSectionGroupReference-7051.test.tsx` (a section declaring // neither `fields` nor `group`, and a group-owned `label` / `collapsible` @@ -35,9 +35,7 @@ export const entry: SemanticMigration = { + 'canonical spellings only, a label a plain string because the form draws it as it stands, and the form ' + 'view\'s group-reference rule. It is read where every page component\'s props are: the component-props ' + 'gate reports a refused value as a `component-props-invalid` / `component-props-unknown-key` ' - + 'error on `objectstack validate`, `objectstack build` and `objectstack lint`, and a stored page still ' - + 'saves and loads, because a page component\'s `properties` is not parsed on the metadata save or load ' - + 'path. No conversion is registered: nothing on the load path refuses the shape, and the authored census, ' + + 'error on `objectstack validate`, `objectstack build` and `objectstack lint`, and a stored page still loads, because a page component\'s `properties` is not parsed on the metadata load path, and the next save of the page meets the component-props gate at the metadata save door. No conversion is registered: nothing on the load path refuses the shape, and the authored census, ' + 'with every inline option list evaluated, found no working section to respell. Deployed metadata NOT ' + 'MEASURED.', acceptanceCriteria: 'Every `object-form` and `object-master-detail-form` node validates: `objectstack ' diff --git a/packages/spec/src/migrations/entries/semantic/18.ui-object-gantt-markers-typed.ts b/packages/spec/src/migrations/entries/semantic/18.ui-object-gantt-markers-typed.ts index f1a93a3ef78..7c2edacd364 100644 --- a/packages/spec/src/migrations/entries/semantic/18.ui-object-gantt-markers-typed.ts +++ b/packages/spec/src/migrations/entries/semantic/18.ui-object-gantt-markers-typed.ts @@ -9,7 +9,7 @@ import type { SemanticMigration } from '../../types.js'; // spec now declares objectui's own authoring declaration of a marker, // `{ date, label?, color? }` with `date` a string, and the row takes it. D3 // only: page-component `properties` is not parsed on the metadata save or load -// path, so a stored page is never refused; a misspelled member has no rewrite +// path, so a stored page is never refused at load; a misspelled member has no rewrite // that says which of the three the author meant; and the authored census found // no authored value to respell — the one refused value is objectui's own // compile-time refusal probe. @@ -29,8 +29,7 @@ export const entry: SemanticMigration = { + '`Date`), closed as every element shape on that map is. It is read where every page component\'s props ' + 'are: the component-props gate reports a refused value as a `component-props-invalid` / ' + '`component-props-unknown-key` error on `objectstack validate`, `objectstack build` and `objectstack ' - + 'lint`, and a stored page still saves and loads, because a page component\'s `properties` is not parsed ' - + 'on the metadata save or load path. No conversion is registered: nothing on the load path refuses the ' + + 'lint`, and a stored page still loads, because a page component\'s `properties` is not parsed on the metadata load path, and the next save of the page meets the component-props gate at the metadata save door. No conversion is registered: nothing on the load path refuses the ' + 'shape, and a misspelled member has no rewrite that says which member the author meant — which is the ' + 'judgment this entry leaves to the upgrader. Deployed metadata NOT MEASURED.', acceptanceCriteria: 'Every `object-gantt` node validates: `objectstack validate` reports no ' diff --git a/packages/spec/src/migrations/entries/semantic/18.ui-object-grid-columns-typed.ts b/packages/spec/src/migrations/entries/semantic/18.ui-object-grid-columns-typed.ts index 8f4c8ca9bd1..bb288999ace 100644 --- a/packages/spec/src/migrations/entries/semantic/18.ui-object-grid-columns-typed.ts +++ b/packages/spec/src/migrations/entries/semantic/18.ui-object-grid-columns-typed.ts @@ -12,7 +12,7 @@ import type { SemanticMigration } from '../../types.js'; // object field's `options` only), so the member takes the list view's own // `columns` by reference — the reference it was held from. D3 only: // page-component `properties` is not parsed on the metadata save or load path, so -// a stored page is never refused; a refused column has no rewrite that both keeps +// a stored page is never refused at load; a refused column has no rewrite that both keeps // what the grid draws today and honours what the author wrote; and the authored // census found no authored value to respell — every refused value is a fixture // whose refused key the grid does not draw. @@ -36,8 +36,7 @@ export const entry: SemanticMigration = { + 'the row takes the list view\'s own `columns` by reference — the column entry a list view already ' + 'refuses an undeclared key on. It is read where every page component\'s props are: the component-props ' + 'gate reports a refused value as a `component-props-invalid` / `component-props-unknown-key` ' - + 'error on `objectstack validate`, `objectstack build` and `objectstack lint`, and a stored page still ' - + 'saves and loads, because a page component\'s `properties` is not parsed on the metadata save or load path. ' + + 'error on `objectstack validate`, `objectstack build` and `objectstack lint`, and a stored page still loads, because a page component\'s `properties` is not parsed on the metadata load path, and the next save of the page meets the component-props gate at the metadata save door. ' + 'No conversion is registered: nothing on the load path refuses the shape, and a refused column has no ' + 'rewrite that both keeps what the grid draws today and honours what the author wrote — which is the ' + 'judgment this entry leaves to the upgrader. Deployed metadata NOT MEASURED.', diff --git a/packages/spec/src/migrations/entries/semantic/18.ui-object-grid-export-options-closed.ts b/packages/spec/src/migrations/entries/semantic/18.ui-object-grid-export-options-closed.ts index 7e6d6f1a4e9..9532014e6e7 100644 --- a/packages/spec/src/migrations/entries/semantic/18.ui-object-grid-export-options-closed.ts +++ b/packages/spec/src/migrations/entries/semantic/18.ui-object-grid-export-options-closed.ts @@ -8,7 +8,7 @@ import type { SemanticMigration } from '../../types.js'; // `exportOptions.formats` and lifts nothing. The row now takes the list view's // five-member export options OBJECT by identity, not the list view's union. D3 // only: page-component `properties` is not parsed on the metadata save or load -// path, so a stored page is never refused and there is no load-path refusal for +// path, so a stored page is never refused at load and there is no load-path refusal for // a conversion to pre-empt; the bare array never worked here, and lifting it // would change the export menu a deployed grid shows today; the authored census // found nothing to respell. @@ -35,8 +35,8 @@ export const entry: SemanticMigration = { + 'object does not declare is named. It is read where every page component\'s props are: the ' + 'component-props gate reports these as `component-props-invalid` / ' + '`component-props-unknown-key` errors on `objectstack validate`, `objectstack build` and ' - + '`objectstack lint`, and a stored page still saves and loads, because a page component\'s ' - + '`properties` is not parsed on the metadata save or load path. No conversion is registered: ' + + '`objectstack lint`, and a stored page still loads, because a page component\'s ' + + '`properties` is not parsed on the metadata load path, and the next save of the page meets the component-props gate at the metadata save door. No conversion is registered: ' + 'nothing on the load path refuses the shape; a bare array has no rewrite that both keeps ' + 'what the grid shows today and honours what the author wrote, which is the judgment this ' + 'entry leaves to the upgrader; and the authored census found nothing to respell. Population ' diff --git a/packages/spec/src/migrations/entries/semantic/18.ui-object-grid-kanban-calendar-list-members-typed.ts b/packages/spec/src/migrations/entries/semantic/18.ui-object-grid-kanban-calendar-list-members-typed.ts index 6c6a40257f3..0881dc7697f 100644 --- a/packages/spec/src/migrations/entries/semantic/18.ui-object-grid-kanban-calendar-list-members-typed.ts +++ b/packages/spec/src/migrations/entries/semantic/18.ui-object-grid-kanban-calendar-list-members-typed.ts @@ -11,7 +11,7 @@ import type { SemanticMigration } from '../../types.js'; // `columns` is held at `z.unknown()`: the grid draws a column's `options`, // which the list view's column entry does not declare. D3 only: // page-component `properties` is not parsed on the metadata save or load path, -// so a stored page is never refused; an off-shape value has no rewrite that +// so a stored page is never refused at load; an off-shape value has no rewrite that // says what the author meant; and the authored census found no authored value // to respell — the refused values are fixtures probing that the renderer drops // them. @@ -43,8 +43,8 @@ export const entry: SemanticMigration = { + 'open until that read is ruled. It is read where every page component\'s props are: the ' + 'component-props gate reports a refused value as a `component-props-invalid` / ' + '`component-props-unknown-key` error on `objectstack validate`, `objectstack build` and ' - + '`objectstack lint`, and a stored page still saves and loads, because a page component\'s ' - + '`properties` is not parsed on the metadata save or load path. No conversion is registered: nothing ' + + '`objectstack lint`, and a stored page still loads, because a page component\'s ' + + '`properties` is not parsed on the metadata load path, and the next save of the page meets the component-props gate at the metadata save door. No conversion is registered: nothing ' + 'on the load path refuses the shape, and an off-shape value has no rewrite that both keeps what the ' + 'block shows today and honours what the author wrote — which is the judgment this entry leaves to ' + 'the upgrader. Deployed metadata NOT MEASURED.', diff --git a/packages/spec/src/migrations/entries/semantic/18.ui-object-grid-page-size-positive-integer-refused.ts b/packages/spec/src/migrations/entries/semantic/18.ui-object-grid-page-size-positive-integer-refused.ts index 6d8f9b85b14..43bb5dd09fb 100644 --- a/packages/spec/src/migrations/entries/semantic/18.ui-object-grid-page-size-positive-integer-refused.ts +++ b/packages/spec/src/migrations/entries/semantic/18.ui-object-grid-page-size-positive-integer-refused.ts @@ -39,9 +39,9 @@ export const entry: SemanticMigration = { + 'flat shorthand — carries a positive integer. Well-formed values (`10`, `25`, `50`) ' + 'parse byte-identically to before, a `pagination` bag carrying sibling keys parses ' + 'and keeps them, and absence stays absence. A stored page whose `object-grid` node ' - + 'carries `pagination: { pageSize: 0 }` still saves and loads — `properties` on a page ' - + 'component is not parsed on the metadata save path — and the component-props gate ' - + 'reports it as a `component-props-invalid` error at `pagination.pageSize` ' + + 'carries `pagination: { pageSize: 0 }` still loads — `properties` on a page ' + + 'component is not parsed on the metadata load path — and the component-props gate, which the ' + + 'metadata save door also runs on the next save of the page, reports it as a `component-props-invalid` error at `pagination.pageSize` ' + 'on `os validate`, `os build` and `os lint`; a `pageSizeOptions` entry and the flat ' + '`pageSize` shorthand are reported the same way at their own paths. The author deletes ' + 'the key or writes the page size they meant, and `os validate` then reports no ' diff --git a/packages/spec/src/migrations/entries/semantic/18.ui-object-grid-row-members-typed.ts b/packages/spec/src/migrations/entries/semantic/18.ui-object-grid-row-members-typed.ts index cf4599c5500..2408594dba9 100644 --- a/packages/spec/src/migrations/entries/semantic/18.ui-object-grid-row-members-typed.ts +++ b/packages/spec/src/migrations/entries/semantic/18.ui-object-grid-row-members-typed.ts @@ -7,7 +7,7 @@ import type { SemanticMigration } from '../../types.js'; // with a fixed shape, so an off-shape value passed every door and the grid // substituted a default or dropped it in silence. The row now takes the shape // each read point takes. D3 only: page-component `properties` is not parsed on -// the metadata save or load path, so a stored page is never refused and there is +// the metadata save or load path, so a stored page is never refused at load and there is // no load-path refusal for a conversion to pre-empt; an off-shape value has no // rewrite that says what the author meant; and the authored census found // nothing in either repository's corpora to respell. @@ -36,8 +36,8 @@ export const entry: SemanticMigration = { + '`operations`, so one value is judged the same way on both doors. It is read where every page ' + 'component\'s props are: the component-props gate reports a refused value as a ' + '`component-props-invalid` / `component-props-unknown-key` error on `objectstack validate`, ' - + '`objectstack build` and `objectstack lint`, and a stored page still saves and loads, because a ' - + 'page component\'s `properties` is not parsed on the metadata save or load path. No conversion ' + + '`objectstack build` and `objectstack lint`, and a stored page still loads, because a ' + + 'page component\'s `properties` is not parsed on the metadata load path, and the next save of the page meets the component-props gate at the metadata save door. No conversion ' + 'is registered: nothing on the load path refuses the shape, and an off-shape value has no ' + 'rewrite that both keeps what the grid shows today and honours what the author wrote — which ' + 'is the judgment this entry leaves to the upgrader. Deployed metadata NOT MEASURED.', diff --git a/packages/spec/src/migrations/entries/semantic/18.ui-object-kanban-conditional-formatting-typed.ts b/packages/spec/src/migrations/entries/semantic/18.ui-object-kanban-conditional-formatting-typed.ts index d8621393178..def3eb0785c 100644 --- a/packages/spec/src/migrations/entries/semantic/18.ui-object-kanban-conditional-formatting-typed.ts +++ b/packages/spec/src/migrations/entries/semantic/18.ui-object-kanban-conditional-formatting-typed.ts @@ -8,7 +8,7 @@ import type { SemanticMigration } from '../../types.js'; // dialect from objectui's authoring faces, so the row now takes the list view's // own member, by reference, as `object-grid` does. D3 only: page-component // `properties` is not parsed on the metadata save or load path, so a stored page -// is never refused; and the authored census found no working rule to respell — +// is never refused at load; and the authored census found no working rule to respell — // the refused values are objectui's own refusal probes and one no-predicate // fixture in this package, respelled in the same change. export const entry: SemanticMigration = { @@ -27,8 +27,8 @@ export const entry: SemanticMigration = { + 'member, by reference, as `object-grid` does, so one rule is judged the same way on every door. It is ' + 'read where every page component\'s props are: the component-props gate reports a refused value as a ' + '`component-props-invalid` / `component-props-unknown-key` error on `objectstack validate`, ' - + '`objectstack build` and `objectstack lint`, and a stored page still saves and loads, because a page ' - + 'component\'s `properties` is not parsed on the metadata save or load path. No conversion is ' + + '`objectstack build` and `objectstack lint`, and a stored page still loads, because a page ' + + 'component\'s `properties` is not parsed on the metadata load path, and the next save of the page meets the component-props gate at the metadata save door. No conversion is ' + 'registered: nothing on the load path refuses the shape, and the authored census found no working rule ' + 'to respell. Deployed metadata NOT MEASURED.', acceptanceCriteria: 'Every `object-kanban` node validates: `objectstack validate` reports no ' diff --git a/packages/spec/src/migrations/entries/semantic/18.ui-object-map-gantt-tree-navigation-typed.ts b/packages/spec/src/migrations/entries/semantic/18.ui-object-map-gantt-tree-navigation-typed.ts index 10c34116d99..d0a694e6b28 100644 --- a/packages/spec/src/migrations/entries/semantic/18.ui-object-map-gantt-tree-navigation-typed.ts +++ b/packages/spec/src/migrations/entries/semantic/18.ui-object-map-gantt-tree-navigation-typed.ts @@ -9,7 +9,7 @@ import type { SemanticMigration } from '../../types.js'; // the record page in silence. The three rows now take that schema by reference, // the carrier `object-grid`, `object-kanban`, `object-calendar` and // `object-timeline` already take. D3 only: page-component `properties` is not -// parsed on the metadata save or load path, so a stored page is never refused; +// parsed on the metadata save or load path, so a stored page is never refused at load; // an off-shape value has no rewrite that says what the author meant; and the // authored census found nothing in either repository's corpora to respell. export const entry: SemanticMigration = { @@ -29,8 +29,8 @@ export const entry: SemanticMigration = { + 'every door that carries it. It is read where every page component\'s props are: the ' + 'component-props gate reports a refused value as a `component-props-invalid` / ' + '`component-props-unknown-key` error on `objectstack validate`, `objectstack build` and ' - + '`objectstack lint`, and a stored page still saves and loads, because a page component\'s ' - + '`properties` is not parsed on the metadata save or load path. No conversion is registered: ' + + '`objectstack lint`, and a stored page still loads, because a page component\'s ' + + '`properties` is not parsed on the metadata load path, and the next save of the page meets the component-props gate at the metadata save door. No conversion is registered: ' + 'nothing on the load path refuses the shape, and an off-shape value has no rewrite that both keeps ' + 'what the block shows today (the record page) and honours what the author wrote — which is the ' + 'judgment this entry leaves to the upgrader. Deployed metadata NOT MEASURED.', diff --git a/packages/spec/src/migrations/entries/semantic/18.ui-object-master-detail-form-details-closed.ts b/packages/spec/src/migrations/entries/semantic/18.ui-object-master-detail-form-details-closed.ts index 3fe236bb253..1d7690f71cf 100644 --- a/packages/spec/src/migrations/entries/semantic/18.ui-object-master-detail-form-details-closed.ts +++ b/packages/spec/src/migrations/entries/semantic/18.ui-object-master-detail-form-details-closed.ts @@ -9,7 +9,7 @@ import type { SemanticMigration } from '../../types.js'; // `InlineGridColumnSchema`; the entry is now a strict shape of the twelve keys // objectui's `MasterDetailForm` reads, and its `columns` REFERENCES that schema. // D3 only: page-component `properties` is not parsed on the metadata save or -// load path, so a stored page is never refused and there is no load-path +// load path, so a stored page is never refused at load and there is no load-path // refusal for a conversion to pre-empt; the authored census found no `field` // spelling to respell. `defineStack`'s identity-only check reaches this carrier // too, the reach `inline-grid-column-identity-only-currency-scale-refused` @@ -41,9 +41,7 @@ export const entry: SemanticMigration = { + 'that schema holds applies here too, with its own prescription. The entry half is read where ' + 'every page component\'s props are: the component-props gate reports a failing entry or ' + 'column as a `component-props-unknown-key` / `component-props-invalid` error on ' - + '`objectstack validate`, `objectstack build` and `objectstack lint`, and a stored page still ' - + 'saves and loads, because a page component\'s `properties` is not parsed on the metadata save ' - + 'or load path. The identity-only half is `defineStack`\'s cross-reference check, which already ' + + '`objectstack validate`, `objectstack build` and `objectstack lint`, and a stored page still loads, because a page component\'s `properties` is not parsed on the metadata load path, and the next save of the page meets the component-props gate at the metadata save door. The identity-only half is `defineStack`\'s cross-reference check, which already ' + 'judged the other two carriers: it now reaches the block wherever a page carries it and ' + 'refuses an identity-only column over a `currency` child field that carries `scale`, with the ' + 'column schema\'s own message; reach: the child object must be declared in the same stack, ' diff --git a/packages/spec/src/migrations/entries/semantic/18.ui-object-metric-aggregate-trend-typed.ts b/packages/spec/src/migrations/entries/semantic/18.ui-object-metric-aggregate-trend-typed.ts index f072e718af4..77cdaf21ac6 100644 --- a/packages/spec/src/migrations/entries/semantic/18.ui-object-metric-aggregate-trend-typed.ts +++ b/packages/spec/src/migrations/entries/semantic/18.ui-object-metric-aggregate-trend-typed.ts @@ -14,7 +14,7 @@ import type { SemanticMigration } from '../../types.js'; // draws; the dashboard widget's comparison declares `dimension`, never read on // this path), so each waits on a ruling. D3 only: page-component `properties` // is not parsed on the metadata save or load path, so a stored page is never -// refused; an off-shape value has no rewrite that says what the author meant; +// refused at load; an off-shape value has no rewrite that says what the author meant; // and the authored census found no authored value to respell — every refused // value is a fixture probing that the tile does not draw it. export const entry: SemanticMigration = { @@ -43,8 +43,7 @@ export const entry: SemanticMigration = { + 'comparison declares a `dimension` this path never reads, so each waits on a ruling between the ' + 'reference and the read. It is read where every page component\'s props are: the component-props gate ' + 'reports a refused value as a `component-props-invalid` / `component-props-unknown-key` error ' - + 'on `objectstack validate`, `objectstack build` and `objectstack lint`, and a stored page still saves and ' - + 'loads, because a page component\'s `properties` is not parsed on the metadata save or load path. No ' + + 'on `objectstack validate`, `objectstack build` and `objectstack lint`, and a stored page still loads, because a page component\'s `properties` is not parsed on the metadata load path, and the next save of the page meets the component-props gate at the metadata save door. No ' + 'conversion is registered: nothing on the load path refuses the shape, and an off-shape value has no ' + 'rewrite that both keeps what the tile shows today and honours what the author wrote — which is the ' + 'judgment this entry leaves to the upgrader. Deployed metadata NOT MEASURED.', diff --git a/packages/spec/src/migrations/entries/semantic/18.ui-object-metric-compare-to-typed.ts b/packages/spec/src/migrations/entries/semantic/18.ui-object-metric-compare-to-typed.ts index e393db8d928..47eeb7fa1a9 100644 --- a/packages/spec/src/migrations/entries/semantic/18.ui-object-metric-compare-to-typed.ts +++ b/packages/spec/src/migrations/entries/semantic/18.ui-object-metric-compare-to-typed.ts @@ -11,7 +11,7 @@ import type { SemanticMigration } from '../../types.js'; // reference, and `dimension` refused by name with a prescription (the inline // tile shifts the date macros in its own `filter` and never reads a dataset // time dimension). D3 only: page-component `properties` is not parsed on the -// metadata save or load path, so a stored page is never refused; a `dimension` +// metadata save or load path, so a stored page is never refused at load; a `dimension` // has no rewrite that keeps the window the author meant; and the authored census // found no authored value to respell — the one refused value is a fixture // probing that the tile does not read `dimension`. @@ -32,8 +32,7 @@ export const entry: SemanticMigration = { + 'prescription rather than accepting a key the tile ignores. It is read where every page component\'s props ' + 'are: the component-props gate reports a refused value as a `component-props-invalid` / ' + '`component-props-unknown-key` error on `objectstack validate`, `objectstack build` and `objectstack ' - + 'lint`, and a stored page still saves and loads, because a page component\'s `properties` is not parsed on ' - + 'the metadata save or load path. No conversion is registered: nothing on the load path refuses the shape, ' + + 'lint`, and a stored page still loads, because a page component\'s `properties` is not parsed on the metadata load path, and the next save of the page meets the component-props gate at the metadata save door. No conversion is registered: nothing on the load path refuses the shape, ' + 'and a `dimension` has no rewrite that keeps the window the author meant — which is the judgment this entry ' + 'leaves to the upgrader. Deployed metadata NOT MEASURED.', acceptanceCriteria: 'Every `object-metric` node validates: `objectstack validate` reports no ' diff --git a/packages/spec/src/migrations/entries/semantic/18.ui-object-metric-drill-down-report-typed.ts b/packages/spec/src/migrations/entries/semantic/18.ui-object-metric-drill-down-report-typed.ts index 9867aac4929..94b04c57727 100644 --- a/packages/spec/src/migrations/entries/semantic/18.ui-object-metric-drill-down-report-typed.ts +++ b/packages/spec/src/migrations/entries/semantic/18.ui-object-metric-drill-down-report-typed.ts @@ -9,7 +9,7 @@ import type { SemanticMigration } from '../../types.js'; // `ReportSchema` by reference, once a joined report refuses a block that binds // no dataset (#21702), so a report the member admits is one the drawer draws. // D3 only: page-component `properties` is not parsed on the metadata save or -// load path, so a stored page is never refused; and the authored census found +// load path, so a stored page is never refused at load; and the authored census found // no drawn report to respell — the refused values are objectui's probes of the // values the drawer does NOT draw. export const entry: SemanticMigration = { @@ -27,8 +27,8 @@ export const entry: SemanticMigration = { + 'names for the member — and, since a joined report refuses a block that binds no `dataset`, every report it ' + 'admits is one the drawer draws. It is read where every page component\'s props are: the component-props gate ' + 'reports a refused value as a `component-props-invalid` / `component-props-unknown-key` error on ' - + '`objectstack validate`, `objectstack build` and `objectstack lint`, and a stored page still saves and loads, ' - + 'because a page component\'s `properties` is not parsed on the metadata save or load path. No conversion is ' + + '`objectstack validate`, `objectstack build` and `objectstack lint`, and a stored page still loads, ' + + 'because a page component\'s `properties` is not parsed on the metadata load path, and the next save of the page meets the component-props gate at the metadata save door. No conversion is ' + 'registered: nothing on the load path refuses the shape, and the authored census found no drawn report to ' + 'respell. Deployed metadata NOT MEASURED.', acceptanceCriteria: 'Every `object-metric` node validates: `objectstack validate` reports no ' diff --git a/packages/spec/src/migrations/entries/semantic/18.ui-object-metric-drill-down-typed.ts b/packages/spec/src/migrations/entries/semantic/18.ui-object-metric-drill-down-typed.ts index 41e35d01152..3c6c608b327 100644 --- a/packages/spec/src/migrations/entries/semantic/18.ui-object-metric-drill-down-typed.ts +++ b/packages/spec/src/migrations/entries/semantic/18.ui-object-metric-drill-down-typed.ts @@ -13,7 +13,7 @@ import type { SemanticMigration } from '../../types.js'; // open). The drill `report` is held open, not typed: the tile draws a // dataset-bound report, but the spec declares no drill report yet. D3 only: // page-component `properties` is not parsed on the metadata save or load path, so -// a stored page is never refused; a drill `filter` has no rewrite that keeps the +// a stored page is never refused at load; a drill `filter` has no rewrite that keeps the // scope the author meant; and the authored census found no authored value to // respell — the two refused values are fixtures probing the refusal. export const entry: SemanticMigration = { @@ -35,8 +35,8 @@ export const entry: SemanticMigration = { + 'dataset-bound report through the shared drawer, but no spec drill shape declares a `report` member yet. ' + 'It is read where every page component\'s props are: the component-props gate reports a refused value as ' + 'a `component-props-invalid` / `component-props-unknown-key` error on `objectstack validate`, ' - + '`objectstack build` and `objectstack lint`, and a stored page still saves and loads, because a page ' - + 'component\'s `properties` is not parsed on the metadata save or load path. No conversion is registered: ' + + '`objectstack build` and `objectstack lint`, and a stored page still loads, because a page ' + + 'component\'s `properties` is not parsed on the metadata load path, and the next save of the page meets the component-props gate at the metadata save door. No conversion is registered: ' + 'nothing on the load path refuses the shape, and a drill `filter` has no rewrite that keeps the scope the ' + 'author meant — which is the judgment this entry leaves to the upgrader. Deployed metadata NOT MEASURED.', acceptanceCriteria: 'Every `object-metric` node validates: `objectstack validate` reports no ' diff --git a/packages/spec/src/migrations/entries/semantic/18.ui-object-timeline-items-typed.ts b/packages/spec/src/migrations/entries/semantic/18.ui-object-timeline-items-typed.ts index 3e3eb2caa85..53d3ba9cf02 100644 --- a/packages/spec/src/migrations/entries/semantic/18.ui-object-timeline-items-typed.ts +++ b/packages/spec/src/migrations/entries/semantic/18.ui-object-timeline-items-typed.ts @@ -10,7 +10,7 @@ import type { SemanticMigration } from '../../types.js'; // opaque, a row refinement pairing each entry with the arm `variant` selects, // and a gantt bar's dates a string or a number. D3 only: page-component // `properties` is not parsed on the metadata save or load path, so a stored page -// is never refused; and the authored census found no drawn entry to respell — +// is never refused at load; and the authored census found no drawn entry to respell — // the refused values are objectui's probes of its render-time gantt date // diagnostic. export const entry: SemanticMigration = { @@ -31,8 +31,7 @@ export const entry: SemanticMigration = { + 'held unjudged until a writer appears. It is read where every page component\'s props are: the ' + 'component-props gate reports a refused value as a `component-props-invalid` / ' + '`component-props-unknown-key` error on `objectstack validate`, `objectstack build` and `objectstack lint`, ' - + 'and a stored page still saves and loads, because a page component\'s `properties` is not parsed on the ' - + 'metadata save or load path. No conversion is registered: nothing on the load path refuses the shape, and ' + + 'and a stored page still loads, because a page component\'s `properties` is not parsed on the metadata load path, and the next save of the page meets the component-props gate at the metadata save door. No conversion is registered: nothing on the load path refuses the shape, and ' + 'the authored census found no drawn entry to respell. Deployed metadata NOT MEASURED.', acceptanceCriteria: 'Every `object-timeline` node validates: `objectstack validate` reports no ' + '`component-props-invalid` / `component-props-unknown-key` finding under `properties.items`. Each timeline ' diff --git a/packages/spec/src/migrations/entries/semantic/18.ui-object-timeline-mapping-typed.ts b/packages/spec/src/migrations/entries/semantic/18.ui-object-timeline-mapping-typed.ts index 373e386ac16..3a1ba81bce7 100644 --- a/packages/spec/src/migrations/entries/semantic/18.ui-object-timeline-mapping-typed.ts +++ b/packages/spec/src/migrations/entries/semantic/18.ui-object-timeline-mapping-typed.ts @@ -27,8 +27,8 @@ export const entry: SemanticMigration = { + 'of the binding record, four optional field names, closed as every element shape on that map is. It is ' + 'read where every page component\'s props are: the component-props gate reports a refused value as a ' + '`component-props-invalid` / `component-props-unknown-key` error on `objectstack validate`, ' - + '`objectstack build` and `objectstack lint`, and a stored page still saves and loads, because a page ' - + 'component\'s `properties` is not parsed on the metadata save or load path. No conversion is ' + + '`objectstack build` and `objectstack lint`, and a stored page still loads, because a page ' + + 'component\'s `properties` is not parsed on the metadata load path, and the next save of the page meets the component-props gate at the metadata save door. No conversion is ' + 'registered: nothing on the load path refuses the shape, and the authored census found nothing to ' + 'respell. Deployed metadata NOT MEASURED.', acceptanceCriteria: 'Every `object-timeline` node validates: `objectstack validate` reports no ' diff --git a/packages/spec/src/migrations/entries/semantic/18.ui-page-action-row-name-leg-url-target-judged.ts b/packages/spec/src/migrations/entries/semantic/18.ui-page-action-row-name-leg-url-target-judged.ts index fd6c1ae181d..ff6293659a2 100644 --- a/packages/spec/src/migrations/entries/semantic/18.ui-page-action-row-name-leg-url-target-judged.ts +++ b/packages/spec/src/migrations/entries/semantic/18.ui-page-action-row-name-leg-url-target-judged.ts @@ -23,8 +23,7 @@ export const entry: SemanticMigration = { + 'reach the url navigator. The rows judged `target` only when the executor KEY read `\'url\'`, so such a row ' + 'carried a target the click refuses past the parse. The rows now judge `target` against the executor the ' + 'runner runs, whichever key spelled it, with the one url-target grammar. `name` is not refused as an ' - + 'executor spelling: a row whose `name` names any other handler is untouched. A page\'s `properties` is not ' - + 'parsed on the metadata save or load path; `os validate`\'s props gate parses it against these same rows ' + + 'executor spelling: a row whose `name` names any other handler is untouched. A page\'s `properties` is not parsed on the metadata load path, and the next save of the page meets the component-props gate at the metadata save door; `os validate`\'s props gate parses it against these same rows ' + 'and reports the refusal at the row\'s `target`. No measured producer writes such a row: no page action row ' + 'in this repository, in objectui or in hotcrm is named `url` or `navigation` — so the entry is owed for rows ' + 'authored outside them.', diff --git a/packages/spec/src/migrations/entries/semantic/18.ui-page-action-row-navigation-executor-refused.ts b/packages/spec/src/migrations/entries/semantic/18.ui-page-action-row-navigation-executor-refused.ts index 90dc5a7c085..07caeea2ec7 100644 --- a/packages/spec/src/migrations/entries/semantic/18.ui-page-action-row-navigation-executor-refused.ts +++ b/packages/spec/src/migrations/entries/semantic/18.ui-page-action-row-navigation-executor-refused.ts @@ -16,8 +16,7 @@ export const entry: SemanticMigration = { + '`\'navigation\'` to the same navigator — so a row spelled with the alias carried a target the click refuses ' + 'past the parse. The url executor now has one spelling on the rows, and the alias is refused at the executor ' + 'key with the rename. The key otherwise stays open: a handler a host registers under another name ' - + '(`navigate_create`, `navigate_edit`, …) is still dispatched by name. A page\'s `properties` is not parsed on ' - + 'the metadata save or load path, so a stored row written with the alias keeps working only while the runner ' + + '(`navigate_create`, `navigate_edit`, …) is still dispatched by name. A page\'s `properties` is not parsed on the metadata load path, and the next save of the page meets the component-props gate at the metadata save door, so a stored row written with the alias keeps working only while the runner ' + 'keeps it; `os validate`\'s props gate reports the alias as a `component-props-invalid` finding. The rename ' + 'is lossless, so the D2 conversion `page-action-row-navigation-executor-to-url` performs it — retired from ' + 'the load path, so an author meets the refusal while stored rows and `os migrate meta --write` are rewritten. ' diff --git a/packages/spec/src/migrations/entries/semantic/18.ui-page-action-row-on-success-beside-executor-refused.ts b/packages/spec/src/migrations/entries/semantic/18.ui-page-action-row-on-success-beside-executor-refused.ts index 6e7d0081f90..9e88841e847 100644 --- a/packages/spec/src/migrations/entries/semantic/18.ui-page-action-row-on-success-beside-executor-refused.ts +++ b/packages/spec/src/migrations/entries/semantic/18.ui-page-action-row-on-success-beside-executor-refused.ts @@ -25,7 +25,8 @@ export const entry: SemanticMigration = { + '`script`; the rows now carry that rule on the vocabulary the action\'s closed `ActionType` can see, with ' + 'the same refusal text. A name outside the runner\'s built-in table is a host-registered handler and stays ' + 'open: the runner hops after it too, and only the host knows whether its handler navigates. A page\'s ' - + '`properties` is not parsed on the metadata save or load path; `os validate`\'s props gate parses it against ' + + '`properties` is not parsed on the metadata load path, so a stored page keeps loading; `os validate`\'s props ' + + 'gate, which the metadata save door also runs on the next save of the page, parses it against ' + 'these same rows and reports the refusal at the row\'s `onSuccess`. Measured producers: none in this ' + 'repository or in hotcrm; objectui\'s one is a test row with no executor asserted accepted.', acceptanceCriteria: diff --git a/packages/spec/src/migrations/entries/semantic/18.ui-page-action-row-on-success-shape-closed.ts b/packages/spec/src/migrations/entries/semantic/18.ui-page-action-row-on-success-shape-closed.ts index b6ad205238d..032f48a498c 100644 --- a/packages/spec/src/migrations/entries/semantic/18.ui-page-action-row-on-success-shape-closed.ts +++ b/packages/spec/src/migrations/entries/semantic/18.ui-page-action-row-on-success-shape-closed.ts @@ -24,8 +24,7 @@ export const entry: SemanticMigration = { + 'with the url scheme set, opening a new tab only on `openIn: \'newTab\'`. Anything else gets no reading, and ' + 'a refused `navigate` performs no navigation. The rows declared `z.unknown()`, so all of it parsed clean and ' + 'did nothing after the action succeeded. The rows now hold the action\'s own block by reference — one shape, ' - + 'and the one url grammar (`navigation-target.ts`). A page\'s `properties` is not parsed on the metadata save ' - + 'or load path, so a stored page still saves and loads; `os validate`\'s props gate parses the bag against the ' + + 'and the one url grammar (`navigation-target.ts`). A page\'s `properties` is not parsed on the metadata load path, and the next save of the page meets the component-props gate at the metadata save door, so a stored page still loads; `os validate`\'s props gate parses the bag against the ' + 'rows and reports a refused value as a `component-props-invalid` finding. No conversion is registered: the ' + 'authored census finds no row `onSuccess` outside the block in this repository or in objectui (its row tests ' + 'write `{ navigate: \'/app/records/${result.id}\' }`, admitted). Deployed metadata NOT MEASURED.', diff --git a/packages/spec/src/migrations/entries/semantic/18.ui-record-line-items-props-closed.ts b/packages/spec/src/migrations/entries/semantic/18.ui-record-line-items-props-closed.ts index db812e4bfc4..2290f99977e 100644 --- a/packages/spec/src/migrations/entries/semantic/18.ui-record-line-items-props-closed.ts +++ b/packages/spec/src/migrations/entries/semantic/18.ui-record-line-items-props-closed.ts @@ -10,7 +10,7 @@ import type { SemanticMigration } from '../../types.js'; // declares the fifteen keys objectui's `LineItemsPanel` reads, and its // `columns` REFERENCES `InlineGridColumnSchema`. D3 only: page-component // `properties` is not parsed on the metadata save or load path, so a stored -// page is never refused and there is no load-path refusal for a conversion to +// page is never refused at load and there is no load-path refusal for a conversion to // pre-empt; the one `field`-keyed producer the census found is respelled in the // same change. export const entry: SemanticMigration = { @@ -41,8 +41,8 @@ export const entry: SemanticMigration = { + 'too, with its own prescription. It is read where every page component\'s props are: the ' + 'component-props gate reports a failing key or column as a ' + '`component-props-unknown-key` / `component-props-invalid` error on `objectstack validate`, ' - + '`objectstack build` and `objectstack lint`, and a stored page still saves and loads, because a ' - + 'page component\'s `properties` is not parsed on the metadata save or load path. `defineStack`\'s ' + + '`objectstack build` and `objectstack lint`, and a stored page still loads, because a ' + + 'page component\'s `properties` is not parsed on the metadata load path, and the next save of the page meets the component-props gate at the metadata save door. `defineStack`\'s ' + 'identity-only column check does not reach this block: the panel hands its columns to the grid ' + 'as authored, so there is no hydrated type to judge. No conversion is registered: nothing on the ' + 'load path refuses the shape, and the one `field`-keyed producer was respelled in the same ' diff --git a/packages/spec/src/migrations/registry.ts.template b/packages/spec/src/migrations/registry.ts.template index 5f195ccac38..98f9320d5c7 100644 --- a/packages/spec/src/migrations/registry.ts.template +++ b/packages/spec/src/migrations/registry.ts.template @@ -2782,7 +2782,7 @@ const STEP18_RATIONALE: readonly RationaleFragment[] = [ + 'carries one shape and no second value-bag key is declared, so an object `params` on any other member, which ' + 'parsed and then reached no action, is refused at `actions.N.params` with the prescription to author an action ' + 'with static parameter values as its own `action:button` node. Read by the component-props gate (refused as an error); a ' - + 'stored page still saves and loads, so no conversion is registered. Its D3 record is the semantic entry ' + + 'stored page still loads, so no conversion is registered. Its D3 record is the semantic entry ' + '`ui-action-group-menu-member-params-array-only`.', }, { @@ -2796,7 +2796,7 @@ const STEP18_RATIONALE: readonly RationaleFragment[] = [ + 'reads — an `action:menu` item reads no `size` and declares none — with the rows\' prescriptions; ' + '`outcomeMessages`, a member `className` and a member `properties.params` are refused, and ' + '`outcomeMessages` stays undeclared on all four action blocks as one decision. Read by the ' - + 'component-props gate (refused as an error); a stored page still saves and loads, so no conversion is registered. ' + + 'component-props gate (refused as an error); a stored page still loads, so no conversion is registered. ' + 'Its D3 record is the semantic entry `ui-action-group-menu-members-typed`.', }, { @@ -2841,8 +2841,7 @@ const STEP18_RATIONALE: readonly RationaleFragment[] = [ + '`name` or a misspelled member passed every door and the form drew the field without it. The spec now ' + 'declares a closed runtime form field of the members the form draws, in camelCase, keyed by `name` — ' + 'the `grid` widget\'s snake_case keys stay out until the widget reads a camelCase spelling — and the ' - + 'row takes a list of it. Read by the component-props gate (refused as an error); a stored page still saves and ' - + 'loads, so no conversion is registered. Its D3 record is the semantic entry ' + + 'row takes a list of it. Read by the component-props gate (refused as an error); a stored page still loads, so no conversion is registered. Its D3 record is the semantic entry ' + '`ui-object-form-custom-fields-typed`.', }, { @@ -2856,7 +2855,7 @@ const STEP18_RATIONALE: readonly RationaleFragment[] = [ + 'authoring face, drawing only a stored one by its name. So both rows take field names, objectui\'s own ' + 'declaration of the member, and refuse an object entry with what to write instead — a `{ name }` entry ' + 'is its bare name, and a `{ field }` entry belongs in a section. Read by the component-props gate ' - + '(refused as an error); a stored page still saves and loads, so no conversion is registered. Its D3 record is the ' + + '(refused as an error); a stored page still loads, so no conversion is registered. Its D3 record is the ' + 'semantic entry `ui-object-form-fields-names-typed`.', }, { @@ -2872,7 +2871,7 @@ const STEP18_RATIONALE: readonly RationaleFragment[] = [ + 'stay open — the form draws a `{ name }` field entry and an inline runtime field inside a section, ' + 'which the typed shapes would refuse — and `customFields` stays open until the spec declares the ' + 'runtime form field its entries are. Read by the component-props gate (refused as an error); a stored page ' - + 'still saves and loads, so no conversion is registered. Its D3 record is the semantic entry ' + + 'still loads, so no conversion is registered. Its D3 record is the semantic entry ' + '`ui-object-form-members-typed`.', }, { @@ -2887,7 +2886,7 @@ const STEP18_RATIONALE: readonly RationaleFragment[] = [ + 'form field — in canonical spellings only: a page block\'s `properties` is never parsed on the way to ' + 'the form, so a deprecated section `visibleOn` or a string `columns`, which a form view folds at ' + 'parse, was dropped, and is refused with the canonical spelling. The stored form view is unchanged. ' - + 'Read by the component-props gate (refused as an error); a stored page still saves and loads, so no conversion is ' + + 'Read by the component-props gate (refused as an error); a stored page still loads, so no conversion is ' + 'registered. Its D3 record is the semantic entry `ui-object-form-sections-typed`.', }, { @@ -2898,8 +2897,7 @@ const STEP18_RATIONALE: readonly RationaleFragment[] = [ + '`z.unknown()` because the marker contract lived only in objectui, so a marker with no `date`, a numeric ' + '`date` or a misspelled member passed every door and the chart drew no line, or drew it unlabelled. The ' + 'spec now declares objectui\'s own authoring declaration of a marker, `{ date, label?, color? }` with ' - + '`date` a string, and the row takes it. Read by the component-props gate (refused as an error); a stored page still ' - + 'saves and loads, so no conversion is registered. Its D3 record is the semantic entry ' + + '`date` a string, and the row takes it. Read by the component-props gate (refused as an error); a stored page still loads, so no conversion is registered. Its D3 record is the semantic entry ' + '`ui-object-gantt-markers-typed`.', }, { @@ -2913,7 +2911,7 @@ const STEP18_RATIONALE: readonly RationaleFragment[] = [ + 'takes the list view\'s own `columns` by reference — all field names or all column entries — and a ' + 'column keyed `accessorKey` / `header` / `name`, a mixed list or an undeclared column key (`editable`, ' + '`options`, `reference`), which passed every door and drew no column or was ignored, is refused. Read by ' - + 'the component-props gate (refused as an error); a stored page still saves and loads, so no conversion is ' + + 'the component-props gate (refused as an error); a stored page still loads, so no conversion is ' + 'registered. Its D3 record is the semantic entry `ui-object-grid-columns-typed`.', }, { @@ -2928,8 +2926,8 @@ const STEP18_RATIONALE: readonly RationaleFragment[] = [ + 'options object by identity, not the list view\'s union, and refuses a bare array with the ' + 'object form named, a format outside the enum and an undeclared key. Page-component ' + '`properties` is read by the component-props gate, which reports these as ' - + 'errors, and is not parsed on the metadata save or load path, so a stored page still saves ' - + 'and loads and no conversion is registered: the bare array never worked here, and lifting it ' + + 'errors, and is not parsed on the metadata load path, and the next save of the page meets the component-props gate at the metadata save door, so a stored page still loads ' + + ' and no conversion is registered: the bare array never worked here, and lifting it ' + 'would change the menu a deployed grid shows. The authored census found nothing to respell. ' + 'Its D3 record is the semantic entry `ui-object-grid-export-options-closed`.', }, @@ -2946,7 +2944,7 @@ const STEP18_RATIONALE: readonly RationaleFragment[] = [ + '(`batchActions`, the spelling the grid reads first, takes `bulkActions`\'s); the grid\'s `fields` ' + 'and `selectable` and the kanban lane take the measured shape. The grid\'s `columns` stays open: its ' + 'group headers draw an authored column\'s `options`, which the list view\'s column entry does not ' - + 'declare. Read by the component-props gate (refused as an error); a stored page still saves and loads, so no ' + + 'declare. Read by the component-props gate (refused as an error); a stored page still loads, so no ' + 'conversion is registered. Its D3 record is the semantic entry ' + '`ui-object-grid-kanban-calendar-list-members-typed`.', }, @@ -2962,7 +2960,7 @@ const STEP18_RATIONALE: readonly RationaleFragment[] = [ + '`aggregations` takes the measured `[{ field, type }]` with the query AST\'s aggregation ' + 'functions, and `operations` the four booleans a grid read point names (`create`, ' + '`update`, `delete`, `export`), refusing `read` and `import`, which nothing reads. Read by ' - + 'the component-props gate (refused as an error); a stored page still saves and loads, so no conversion ' + + 'the component-props gate (refused as an error); a stored page still loads, so no conversion ' + 'is registered. Its D3 record is the semantic entry `ui-object-grid-row-members-typed`.', }, { @@ -2975,7 +2973,7 @@ const STEP18_RATIONALE: readonly RationaleFragment[] = [ + 'passed every door and the board painted no card for it. objectui has since made the list view\'s ' + '`{ condition, style }` rule the member\'s only authoring dialect, and the board evaluates it with the ' + 'grid\'s evaluator, so the row takes the list view\'s own member by reference, as `object-grid` does. ' - + 'Read by the component-props gate (refused as an error); a stored page still saves and loads, so no conversion is ' + + 'Read by the component-props gate (refused as an error); a stored page still loads, so no conversion is ' + 'registered. Its D3 record is the semantic entry `ui-object-kanban-conditional-formatting-typed`.', }, { @@ -2988,7 +2986,7 @@ const STEP18_RATIONALE: readonly RationaleFragment[] = [ + 'so `navigation: 42` and a bare mode string passed every door and opened the record page. The ' + 'three rows now take the list view\'s `NavigationConfigSchema` by reference, the carrier the grid, ' + 'kanban, calendar and timeline blocks already take. Read by the component-props gate ' - + '(refused as an error); a stored page still saves and loads, so no conversion is registered. Its D3 record ' + + '(refused as an error); a stored page still loads, so no conversion is registered. Its D3 record ' + 'is the semantic entry `ui-object-map-gantt-tree-navigation-typed`.', }, { @@ -3003,8 +3001,8 @@ const STEP18_RATIONALE: readonly RationaleFragment[] = [ + 'Each detail entry is now a strict shape of the twelve keys the renderer reads, and its ' + '`columns` references `InlineGridColumnSchema`. Page-component `properties` is read by the ' + 'component-props gate, which reports a failing entry or column as an error, ' - + 'and is not parsed on the metadata save or load path, so a stored page still saves and ' - + 'loads and no conversion is registered; the authored census found nothing to respell. ' + + 'and is not parsed on the metadata load path, and the next save of the page meets the component-props gate at the metadata save door, so a stored page still loads ' + + ' and no conversion is registered; the authored census found nothing to respell. ' + '`defineStack`\'s identity-only check reaches the block wherever a page carries it, with ' + 'the reach `inline-grid-column-identity-only-currency-scale-refused` records for the ' + 'other two carriers. Its D3 record is the semantic entry ' @@ -3023,7 +3021,7 @@ const STEP18_RATIONALE: readonly RationaleFragment[] = [ + 'badge\'s measured shape. `drillDown` and `compareTo` stay open: each by-reference candidate declares a key ' + 'the tile never reads (the chart drill-down\'s `filter`, the dashboard comparison\'s `dimension`), and ' + 'the chart drill-down refuses the `report` the tile draws, so each waits on a ruling. Read by the ' - + 'component-props gate (refused as an error); a stored page still saves and loads, so no conversion is ' + + 'component-props gate (refused as an error); a stored page still loads, so no conversion is ' + 'registered. Its D3 record is the semantic entry `ui-object-metric-aggregate-trend-typed`.', }, { @@ -3036,7 +3034,7 @@ const STEP18_RATIONALE: readonly RationaleFragment[] = [ + 'reference, and `dimension` refused by name, because this inline tile shifts the date macros in its own ' + '`filter` and never reads a dataset time dimension. A bare kind string, a kind outside the two and a ' + '`dimension` passed every door and compared the wrong window. Read by the component-props gate ' - + '(refused as an error); a stored page still saves and loads, so no conversion is registered. Its D3 record is the ' + + '(refused as an error); a stored page still loads, so no conversion is registered. Its D3 record is the ' + 'semantic entry `ui-object-metric-compare-to-typed`.', }, { @@ -3048,7 +3046,7 @@ const STEP18_RATIONALE: readonly RationaleFragment[] = [ + 'reference (fork 1, letter B): it waited until a joined report refused a block that binds no dataset, ' + 'and since then every report the member admits is one the drill drawer draws — a report with no ' + '`dataset`, a bare report name or a `{ name }` reference, which the drawer answered by listing the ' - + 'records, is refused. Read by the component-props gate (refused as an error); a stored page still saves and loads, ' + + 'records, is refused. Read by the component-props gate (refused as an error); a stored page still loads, ' + 'so no conversion is registered. Its D3 record is the semantic entry ' + '`ui-object-metric-drill-down-report-typed`.', }, @@ -3062,7 +3060,7 @@ const STEP18_RATIONALE: readonly RationaleFragment[] = [ + 'metric tile has no click event for a drill filter to resolve against and no row for `mode` to open; ' + 'both passed every door and were ignored. The drill `report` stays open: the tile draws a ' + 'dataset-bound report, but the spec declares no drill report yet, and declares that contract first. ' - + 'Read by the component-props gate (refused as an error); a stored page still saves and loads, so no conversion is ' + + 'Read by the component-props gate (refused as an error); a stored page still loads, so no conversion is ' + 'registered. Its D3 record is the semantic entry `ui-object-metric-drill-down-typed`.', }, { @@ -3075,7 +3073,7 @@ const STEP18_RATIONALE: readonly RationaleFragment[] = [ + 'epoch milliseconds — and a row refinement pairs each entry with the kind the block\'s `variant` selects, ' + 'so a feed entry with no `title`, or a gantt row on a feed timeline, is refused instead of drawn empty. ' + 'A feed entry\'s `content` (child components) is held unjudged until a writer appears. Read by the ' - + 'component-props gate (refused as an error); a stored page still saves and loads, so no conversion is registered. ' + + 'component-props gate (refused as an error); a stored page still loads, so no conversion is registered. ' + 'Its D3 record is the semantic entry `ui-object-timeline-items-typed`.', }, { @@ -3089,7 +3087,7 @@ const STEP18_RATIONALE: readonly RationaleFragment[] = [ + 'declaration of it, and the row takes it. The stage\'s other members — the metric drill-down\'s ' + '`report`, the form\'s `customFields` and both forms\' `sections`, the timeline\'s `items` and the ' + 'action containers\' members — stay open: each contract has more than one viable shape that no ruling ' - + 'decides yet. Read by the component-props gate (refused as an error); a stored page still saves and loads, so no ' + + 'decides yet. Read by the component-props gate (refused as an error); a stored page still loads, so no ' + 'conversion is registered. Its D3 record is the semantic entry `ui-object-timeline-mapping-typed`.', }, { From 0b788de33a5bc19e10cd4c91ba5b629b653780cd Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 14 Oct 2026 13:49:46 +0000 Subject: [PATCH 3/4] fix(lint): the two now-retired page prop aliases are refused at every door; the normalized-tier comment and the changeset say so Claude-Session: https://claude.ai/code/session_01D6F9axfoPzR7t8G1J7Ytzz Co-authored-by: Claude --- .changeset/23388-page-component-rules-runtime-door.md | 2 +- packages/lint/src/authoring-rules.ts | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/.changeset/23388-page-component-rules-runtime-door.md b/.changeset/23388-page-component-rules-runtime-door.md index 2af4bf4b462..9822c2711c9 100644 --- a/.changeset/23388-page-component-rules-runtime-door.md +++ b/.changeset/23388-page-component-rules-runtime-door.md @@ -24,6 +24,6 @@ Clause-②: no (narrowing) **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. -**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. Two live-window ADR-0087 D2 aliases on page component properties are converted at `objectstack validate` and at load but not at this door, so a save that spells one is refused with the canonical key: `page:header` `description` (write `subtitle`) and an `element:button` inline `type: 'api'` action's object `params` (write `bodyExtra`). +**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. 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. diff --git a/packages/lint/src/authoring-rules.ts b/packages/lint/src/authoring-rules.ts index a24eb209e8e..a3bb3f5cd7b 100644 --- a/packages/lint/src/authoring-rules.ts +++ b/packages/lint/src/authoring-rules.ts @@ -1149,11 +1149,10 @@ export const AUTHORING_RULES: readonly AuthoringRule[] = [ // // `normalized` for a reason worth stating, since the props bag survives the // Zod parse UNCHANGED and both tiers would otherwise carry the same data: the - // ADR-0087 conversion layer runs inside `normalizeStackInput`, so a converted - // alias (`page-header-subtitle-alias` rewrites `properties.description` → - // `subtitle`) is already canonical here and is never reported as undeclared — - // while a schema error elsewhere in the stack cannot take these findings down - // with it. + // ADR-0087 conversion layer runs inside `normalizeStackInput`, so a prop + // spelling a live D2 conversion still rewrites on the load path is already + // canonical here and is never reported as undeclared — while a schema error + // elsewhere in the stack cannot take these findings down with it. // // Gating: every finding is an `error`, so `os validate`, `os build` and // `os lint` refuse an undeclared prop, a value the props schema rejects, a From 3301d5f2f1545c2bcc0e566254c0b0597ab85941 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 14 Oct 2026 13:54:39 +0000 Subject: [PATCH 4/4] docs(deployment): the component-props row marks the page save door Claude-Session: https://claude.ai/code/session_01D6F9axfoPzR7t8G1J7Ytzz Co-authored-by: Claude --- content/docs/deployment/validating-metadata.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/docs/deployment/validating-metadata.mdx b/content/docs/deployment/validating-metadata.mdx index a1d6f37e8e6..9d240f1aa5d 100644 --- a/content/docs/deployment/validating-metadata.mdx +++ b/content/docs/deployment/validating-metadata.mdx @@ -470,7 +470,7 @@ orthogonal to both, and no cell here can carry it; it is written out in | Chart bindings outside dashboards (#3583) | ✓ | ✓ | ✓ | — | | Navigation vs. granted access (ADR-0090 D6) | ✓ | ✓ | ✓ | — | | SDUI scoped styling (ADR-0065) | ✓ | ✓ | ✓ | — | -| 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) | ✓ | ✓ | ✓ | — | +| 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) | ✓ | ✓ | ✓ | ✓ᵖ | | JSX / React page source parses (ADR-0080/0081) | ✓ | ✓ | ✓ | — | | Approval-node approvers (ADR-0090 D3) | ✓ | ✓ | ✓ | ✓ᶠ | | Security posture (ADR-0090 — e.g. every custom object declares `sharingModel`) | ✓ | ✓ | ✓ | ✓ˢᵖᵉᵇᵒ |