@@ -11867,6 +11867,44 @@ const step18: MigrationStep = {
1186711867 + 'the field left empty is stored with that value — or the author has decided the form needs '
1186811868 + 'no pre-selection and the object field is unchanged.',
1186911869 },
11870+ // #20901 — the form-view carrier of the inline grid column. `subforms[].columns`
11871+ // was `z.array(z.any())` while the other carrier, a relationship field's
11872+ // `inlineColumns`, has been the strict `InlineGridColumnSchema` since #9227; the
11873+ // carrier now REFERENCES that schema, so both carriers are judged by one
11874+ // contract. D3 only, deliberately: the one mechanical respelling the family
11875+ // had (`field` → `name`, conversion `field-column-lists-canonicalized`) is
11876+ // already retired from the load path, and every other refused shape is a
11877+ // judgment only the author can make. A stored view whose column fails is
11878+ // refused with the column schema's own prescription, never stripped.
11879+ {
11880+ id: 'form-view-subform-columns-closed',
11881+ surface: 'view.form.subforms[].columns[] and view.formViews.<key>.subforms[].columns[] — the '
11882+ + 'form view\'s inline grid columns, which used to accept any value',
11883+ replacement: 'each entry is the strict, name-keyed inline grid column a relationship field\'s '
11884+ + '`inlineColumns` takes — `{ name, label?, type?, … }`, where `{ name }` alone hydrates the rest '
11885+ + 'from the child object\'s field. Write `name` where a column said `field` (or `fieldName`, '
11886+ + '`key`); delete `scale` from a column declaring `type: \'currency\'`; delete any key the '
11887+ + 'column schema does not declare.',
11888+ reason: 'Both carriers feed the one console grid, which reads only the keys the column schema '
11889+ + 'declares and keys a column by `name` alone. On the form view the columns were never judged, '
11890+ + 'so a mis-keyed column published clean and drew a blank grid column, and a key the other '
11891+ + 'carrier refuses — `scale` on a currency column, under the maintainer\'s ruling of 2026-09-23 '
11892+ + '(option B, `scale` retired from the currency type) and the remedy ruled on 2026-09-24 '
11893+ + '(option 乙 — a currency\'s ISO 4217 minor unit decides its display) — published green here. '
11894+ + 'The carrier now references the column schema, so every rule it holds applies here too, with '
11895+ + 'its own prescription. NOT mechanically converted: the `field` → `name` respelling this family '
11896+ + 'had is already retired from the load path, and which column an unknown key or a mixed '
11897+ + '`field`/`name` entry meant is the author\'s call — a conversion that dropped the key would '
11898+ + 'accept on every load what the parse now refuses. Population measured at the change, on '
11899+ + 'origin/main cb4c31dd52: zero authored `subforms` in the repository (the showcase derives its '
11900+ + 'master-detail grids from the data model instead), against one authored `inlineColumns` block '
11901+ + 'as the control. Deployed metadata NOT MEASURED.',
11902+ acceptanceCriteria: 'Every view in the stack parses: `objectstack validate` and a view parse report '
11903+ + 'no issue on a `subforms[].columns[]` path. Every column entry is an object carrying `name`, '
11904+ + 'no entry carries `field`, `fieldName` or `key`, and no column declaring `type: \'currency\'` '
11905+ + 'carries `scale`. Each column `name` names a field of the subform\'s `childObject`, and the '
11906+ + 'master-detail grid renders a value — not a blank cell — in each column for a row that has one.',
11907+ },
1187011908 {
1187111909 id: 'hook-register-undispatched-lifecycle-event-refused',
1187211910 surface:
@@ -12303,6 +12341,45 @@ const step18: MigrationStep = {
1230312341 + 'that one line deleted and no key added. `number` columns, and columns declaring no `type`, '
1230412342 + 'keep their `scale`; a column\'s `prefix` is still accepted on a currency column.',
1230512343 },
12344+ // #20901 — the reach of `inline-grid-column-currency-scale-refused`, extended to
12345+ // the column that declares no `type`. The column schema judges only a DECLARED
12346+ // type; an identity-only column takes its type from the child field when the
12347+ // console hydrates it, and the child field is a fact the stack holds. So
12348+ // `defineStack`'s cross-reference check re-parses such a column as the type it
12349+ // renders as and reports the column schema's own refusal — no second scale
12350+ // rule. D3 only, for the reason the declared-type entry gives: a conversion
12351+ // that dropped the key would accept it on every load, the grace window the
12352+ // ruling refused.
12353+ {
12354+ id: 'inline-grid-column-identity-only-currency-scale-refused',
12355+ surface: 'object.fields.<name>.inlineColumns[].scale and view.form.subforms[].columns[].scale '
12356+ + '(and each formViews entry) on a column that declares NO `type` and whose `name` is a '
12357+ + '`currency` field of the child object — any value, `scale: 0` included. A column declaring '
12358+ + '`type: \'number\'`, and a column over a field of any other type, keep `scale`',
12359+ replacement: 'no `scale` on the column. DELETE the key — that is the whole migration: the column '
12360+ + 'renders as a currency column, and a currency amount\'s decimal places are its currency\'s. '
12361+ + 'The currency\'s ISO 4217 minor unit decides how the cell displays the amount and the width a '
12362+ + 'computed amount is rounded to. ⛔ Nothing replaces the key: do not re-declare its value under '
12363+ + 'any other key, and do not add `type: \'number\'` to keep it on a currency amount.',
12364+ reason: 'The refusal of `scale` on a currency inline grid column (entry '
12365+ + '`inline-grid-column-currency-scale-refused`, under the maintainer\'s rulings of 2026-09-23, '
12366+ + 'option B, and 2026-09-24, option 乙) reached only a column that DECLARES `type: \'currency\'`, '
12367+ + 'because the column schema cannot see the child field. An identity-only column — the '
12368+ + 'recommended form — over a currency field renders as a currency column all the same, so it '
12369+ + 'published green carrying the refused key, and the console ignored it. `defineStack`\'s '
12370+ + 'cross-reference check, which holds the child object\'s fields, now judges such a column as the '
12371+ + 'type it renders as and refuses it with the column schema\'s own message. Reach: the child '
12372+ + 'object must be declared in the same stack; a column naming no field of it, or a subform whose '
12373+ + 'child object comes from another package, is not judged there. Population measured at the '
12374+ + 'change, on origin/main cb4c31dd52: one authored `inlineColumns` block (the showcase invoice, '
12375+ + 'seven identity-only columns, none carrying `scale`) and zero authored `subforms`. Deployed '
12376+ + 'metadata NOT MEASURED.',
12377+ acceptanceCriteria: '`objectstack validate` and `defineStack` report no cross-reference finding on '
12378+ + 'an `inlineColumns[].scale` or `subforms[].columns[].scale` path. A column that carried `scale` '
12379+ + 'over a currency child field no longer declares it, and a diff of the column shows that one line '
12380+ + 'deleted and no key added. Columns over `number` fields, and columns declaring `type: '
12381+ + '\'number\'`, keep their `scale`.',
12382+ },
1230612383 // #14478 (maintainer ruling B: a duration key carries its unit in its NAME) —
1230712384 // the D3 entry of the `job-timeout-to-timeout-ms` family (ruling B on #17152:
1230812385 // one D3 entry per retirement family, even when D2 is lossless). The job half
0 commit comments