Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .changeset/23789-page-props-save-door-sentences.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
'@objectstack/spec': patch
---

docs(spec): the step-18 upgrade text and the `exportOptions` TSDoc say a stored page's next save meets the component-props gate at the metadata save door

Clause-②: no

Text only; no accept set moves. Since the component-props gate (`validateComponentProps` / `validateComponentTypes`) runs on a `page` write at the metadata save door, three step-18 semantic entries that `os migrate meta` and the protocol upgrade guide print — `ui-page-action-row-operation-declared`, `ui-object-block-grouping-config-typed` and `element-text-variant-heading-subheading-retired` — and the `object-grid` `exportOptions` TSDoc in the `.d.ts` still said a page component's `properties` is not parsed on the save path. They now say what runs: a stored page still loads, because `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 (422 `INVALID_METADATA` on a finding), beside `os validate`, `os build` and `os lint`.
2 changes: 1 addition & 1 deletion content/docs/deployment/cli.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -1491,7 +1491,7 @@ can produce, because both supply a live one.
| Rows that still fail the current schema after conversion | That is a genuine contract violation, not chain-owned history. The write path's rejection is correct; fix the row in Studio |
| A flow whose rename the conflict guard refused | The old node-type token is a live name something else owns here. Rewriting would clobber that owner, so the row fails loudly naming the token — never a silent skip |
| A site the conversion chain leaves as stored because no lossless rewrite exists — above all a page filter carrying `$and` / `$or` / `$not` | Flattening a combinator changes which rows the page selects, so it is never done. Each site is printed as a `TODO` line under its row — path, block, and what blocks the rewrite — whatever the row's outcome; a row with nothing but TODOs is reported `skipped`. It does not fail the run, since no run of this pass can clear it: rewrite each site by hand |
| A stored shape protocol 18 refuses and no conversion rewrites — a `controlled_by_parent` object without exactly one required `master_detail` (two masters, none, a required `lookup` only, an explicit `required: false`), a view bound to the retired view-level API data source or a page component whose `data` prop binds it (`object-grid`, `object-map`, `object-gantt`, `object-tree`), a flow value slot carrying a `{…}` token (a `get_record` / `update_record` / `delete_record` filter operand among them) | Each would need a choice only its owner can make — which master, which page, what an absent value should do — so it is never rewritten. Each site is printed as a `TODO` line under its row, naming the step-18 entry that refuses it, the object, view, page component or flow node, and the owner's choices; a binding's request headers are withheld. Until it is rewritten by hand, the row is refused: an object registers but derives no master, a view registers under `[metadata_spec_invalid]`, a page still loads and saves while `os validate`, `os build` and `os lint` refuse the binding in source, and a flow is not registered |
| A stored shape protocol 18 refuses and no conversion rewrites — a `controlled_by_parent` object without exactly one required `master_detail` (two masters, none, a required `lookup` only, an explicit `required: false`), a view bound to the retired view-level API data source or a page component whose `data` prop binds it (`object-grid`, `object-map`, `object-gantt`, `object-tree`), a flow value slot carrying a `{…}` token (a `get_record` / `update_record` / `delete_record` filter operand among them) | Each would need a choice only its owner can make — which master, which page, what an absent value should do — so it is never rewritten. Each site is printed as a `TODO` line under its row, naming the step-18 entry that refuses it, the object, view, page component or flow node, and the owner's choices; a binding's request headers are withheld. Until it is rewritten by hand, the row is refused: an object registers but derives no master, a view registers under `[metadata_spec_invalid]`, a page still loads, its next save is refused at the metadata save door (422), and `os validate`, `os build` and `os lint` refuse the binding in source, and a flow is not registered |
| Any other site `registerFlow` refuses in a stored flow the parse accepts — a text slot's template hole, a decision's `mode`, a malformed predicate, a plugin node's undeclared config key, an `api` flow with no secret | The flow's own definition is wrong, and no conversion rewrites it. Each site is printed as a `TODO registerFlow` line under its row, in the words `registerFlow` refuses it with; a site already listed under a step-18 entry is not repeated. Until it is fixed, the flow is not registered at boot and never runs. A flow the parse itself refuses is reported among the rows that could not be rewritten, in the parse's words |

**One thing it lists and never writes: decision nodes that changed meaning at
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,11 @@ export const entry: SemanticMigration = {
+ 'and `subheading` in a medium-weight small heading style, and `h2` / `h3` draw their own, larger '
+ 'styles. Whether the page wanted that level is the author\'s call — a heading placed for its '
+ 'size rather than its place in the outline may want a deeper level. Nothing is dropped at rest: '
+ 'a stored page replays the rewrite at rehydration; a page component\'s `properties` is not '
+ 'parsed on the save path, and the component-props gate reports an old spelling as a '
+ 'a stored page replays the rewrite at rehydration and 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 gate reports an old spelling as a '
+ '`component-props-invalid` error, carrying the prescription, on `os validate`, `os build` and '
+ '`os lint`. ADR-0087',
+ '`os lint` and at that save door. ADR-0087',
acceptanceCriteria:
'No `element:text` page component carries `variant` `heading` or `subheading`; `os validate` '
+ 'reports no `component-props-invalid` finding under `properties.variant` for these blocks. For '
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ import type { SemanticMigration } from '../../types.js';
// key, and #21704's fork 5 A refused the member's `properties.params`, so a
// 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 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
// is not parsed on the metadata load path, so a stored page is never refused at
// load (its next save meets the component-props gate at the metadata save
// door); 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 = {
id: 'ui-action-group-menu-member-params-array-only',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@ 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 at load; and the authored census
// load path, so a stored page is never refused at load (its next save meets the
// component-props gate at the metadata save door); 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.
// of the very reads the ruling refuses (a member `className`,
// `outcomeMessages`, `properties.params`) and of the host's `autoTrigger` flag.
export const entry: SemanticMigration = {
id: 'ui-action-group-menu-members-typed',
surface: 'page `action:group` and `action:menu` components — each member of `properties.actions` (whose keys '
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,10 @@ export const entry: SemanticMigration = {
+ 'unpadded name silently equivalent, which is the consumer tolerance the contract refuses, '
+ 'and a bare string, a number or an empty list has no mapping that says what grouping was '
+ 'meant. Metadata AT REST is left exactly as stored — `properties` on a page component is '
+ 'not parsed on the save path, so a stored page keeps loading and renders as it does today; '
+ 'the component-props gate reports such a value as a `component-props-invalid` '
+ 'error at the offending path on `os validate`, `os build` and `os lint`, a padded name '
+ 'not parsed on the metadata load path, so a stored page keeps loading and renders as it does '
+ 'today, and the next save of the page meets the component-props gate at the metadata save '
+ 'door. The gate reports such a value as a `component-props-invalid` error at the offending '
+ 'path on `os validate`, `os build` and `os lint` and at that save door, a padded name '
+ 'with the received value and the unpadded name to write. ADR-0049 / ADR-0087.',
acceptanceCriteria:
'Every `object-grid` and `object-kanban` node in your pages either omits `grouping` or '
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ 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 at load;
// and the authored census, with every inline option list evaluated, found no
// parsed on the metadata load path, so a stored page is never refused at load
// (its next save meets the component-props gate at the metadata save door); 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
// nothing. The shipped object-manager dialog's options (`{ label: 'Box', value:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@ import type { SemanticMigration } from '../../types.js';
// while the form drew a `{ name }` field entry its own page-builder guide
// taught. objectstack-ai/objectui#11550 retired that entry from every authoring
// face (the form still draws a STORED one, by its name, as tolerance), so both
// rows now take field names — objectui's own declaration of the member — and
// an object entry is refused with what to write instead. 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 authored
// value to respell — the refused values are fixtures probing the stored read,
// the console warning and objectui's own refusal.
// rows now take field names — objectui's own declaration of the member — and an
// object entry is refused with what to write instead. D3 only: page-component
// `properties` is not parsed on the metadata load path, so a stored page is
// never refused at load (its next save meets the component-props gate at the
// metadata save door); and the authored census found no authored value to
// respell — the refused values are fixtures probing the stored read, the
// console warning and objectui's own refusal.
export const entry: SemanticMigration = {
id: 'ui-object-form-fields-names-typed',
surface: 'page `object-form` and `object-master-detail-form` components — `properties.fields` (whose '
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ import type { SemanticMigration } from '../../types.js';
// `z.unknown()` (the form draws a `{ name }` field entry and an inline runtime
// 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 at load; an off-shape value has no
// D3 only: page-component `properties` is not parsed on the metadata load path,
// so a stored page is never refused at load (its next save meets the
// component-props gate at the metadata save door); 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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ import type { SemanticMigration } from '../../types.js';
// 3, letter B) a page-block section shape of its own — the form view's section
// 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 at load; and the authored census, with every inline option list evaluated,
// not parsed on the metadata load path, so a stored page is never refused at
// load (its next save meets the component-props gate at the metadata save
// door); 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`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,18 @@

import type { SemanticMigration } from '../../types.js';

// #21464 — the `object-gantt` page block's `markers` was `z.array(z.unknown())`:
// its element contract lived only in objectui, so a marker with no `date`, a
// numeric `date` or a misspelled member passed the component-props gate, and
// the chart drew no line, or drew it unlabelled and in the default colour. The
// 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 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.
// #21464 — the `object-gantt` page block's `markers` was
// `z.array(z.unknown())`: its element contract lived only in objectui, so a
// marker with no `date`, a numeric `date` or a misspelled member passed the
// component-props gate, and the chart drew no line, or drew it unlabelled and
// in the default colour. The 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 load path, so a stored page is never refused at load (its next save
// meets the component-props gate at the metadata save door); 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.
export const entry: SemanticMigration = {
id: 'ui-object-gantt-markers-typed',
surface: 'page `object-gantt` components — `properties.markers` (whose entries used to accept any value)',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,18 @@ import type { SemanticMigration } from '../../types.js';
// #21464 — the `object-grid` page block's `columns` was `z.array(z.unknown())`
// although the grid reads it with one shape, so a column keyed `accessorKey` /
// `header` / `name`, a column with no `field`, a mixed list or a key the grid
// never reads off a column passed the component-props gate, and the grid drew no
// column or ignored the key, in silence. Stage 2 held it because the grid's group
// headers drew a column's `options`, which the list view's column entry does not
// declare; objectui retired that read (the group-header labels come from the
// 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 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.
// never reads off a column passed the component-props gate, and the grid drew
// no column or ignored the key, in silence. Stage 2 held it because the grid's
// group headers drew a column's `options`, which the list view's column entry
// does not declare; objectui retired that read (the group-header labels come
// from the 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 load path, so a
// stored page is never refused at load (its next save meets the component-props
// gate at the metadata save door); 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.
export const entry: SemanticMigration = {
id: 'ui-object-grid-columns-typed',
surface: 'page `object-grid` components — `properties.columns` (whose entries used to accept any value)',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,17 @@

import type { SemanticMigration } from '../../types.js';

// #21229 — an `object-grid` page block's `exportOptions` was `z.unknown()`, so a
// bare format array (the list view's legacy spelling, which the list view lifts
// to `{ formats }`) was accepted on the grid, whose renderer reads
// #21229 — an `object-grid` page block's `exportOptions` was `z.unknown()`, so
// a bare format array (the list view's legacy spelling, which the list view
// lifts to `{ formats }`) was accepted on the grid, whose renderer reads
// `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 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.
// only: page-component `properties` is not parsed on the metadata load path, so
// a stored page is never refused at load (its next save meets the
// component-props gate at the metadata save door) 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.
export const entry: SemanticMigration = {
id: 'ui-object-grid-export-options-closed',
surface: 'page `object-grid` components — `properties.exportOptions` (which used to accept any value)',
Expand Down
Loading
Loading