@@ -10528,6 +10528,43 @@ const step18: MigrationStep = {
1052810528 + 'and must be verified as such: nothing ever parsed or read these shapes, so removing '
1052910529 + 'them removes no behaviour.',
1053010530 },
10531+ {
10532+ id: 'inline-grid-column-currency-scale-refused',
10533+ surface: 'object.fields.<name>.inlineColumns[].scale on an inline grid column that declares '
10534+ + '`type: \'currency\'` — any declared value, `scale: 0` included, computed or not. `scale` on a '
10535+ + '`number` column, and on a column that declares no `type`, is untouched',
10536+ replacement: 'no `scale` on a currency inline grid column. DELETE the key — that is the whole '
10537+ + 'migration: a currency amount\'s decimal places are its currency\'s, not a column setting. The '
10538+ + 'currency\'s ISO 4217 minor unit decides how the cell displays the amount and the width a '
10539+ + 'computed amount is rounded to. ⛔ Nothing replaces the key: do not re-declare its value under '
10540+ + 'any other key.',
10541+ reason:
10542+ 'Maintainer ruling 5791803339 (batch #215 item 1, letter B) retired `scale` from the '
10543+ + '`currency` field type, and ruling 5805782503 (batch #218 item 2, letter 乙 — a currency\'s '
10544+ + 'ISO 4217 minor unit decides its display) worded the remedy. Neither reached the inline grid '
10545+ + 'column, the strict mirror of the console grid\'s column, which still offered per-column '
10546+ + 'decimals on a `currency` column; triage read the column as inherited from both rulings, so '
10547+ + '`InlineGridColumnSchema` now refuses the key on a column declaring `type: \'currency\'` at '
10548+ + 'parse, with the field refusal\'s first sentence and remedy. ⛔ No alias and no grace window, '
10549+ + 'per ruling B. NOT mechanically converted, deliberately, for the reason the field entry '
10550+ + '`field-currency-scale-refused` gives: a conversion that dropped the key would accept it on '
10551+ + 'every load, which is the grace window the ruling refused; the refusal names the key and its '
10552+ + 'one-line fix instead. The same change rewords the column\'s `prefix` description: it replaces '
10553+ + 'the resolved currency\'s symbol and has no default (the grid no longer falls back to a fixed '
10554+ + 'yen sign). Reach: only a DECLARED column `type` is judged — a column that declares none takes '
10555+ + 'its type from the child field when the console hydrates it, which the column schema cannot '
10556+ + 'see. Population measured at the change, on origin/main 1c8b320a89: one authored '
10557+ + '`inlineColumns` block in the tree (the showcase invoice, seven identity-only columns, none '
10558+ + 'declaring `type` or `scale`), no platform object, skill, documentation example or JSON fixture '
10559+ + 'declaring an inline grid column at all, and one test fixture carrying `scale: 2` on a currency '
10560+ + 'column, re-judged in the same change. Deployed metadata NOT MEASURED.',
10561+ acceptanceCriteria:
10562+ 'Every field in the stack parses: an `ObjectSchema` parse and `objectstack validate` report no '
10563+ + 'issue on an `inlineColumns[].scale` path of a column declaring `type: \'currency\'`. A '
10564+ + 'currency column that carried `scale` no longer declares it, and a diff of the column shows '
10565+ + 'that one line deleted and no key added. `number` columns, and columns declaring no `type`, '
10566+ + 'keep their `scale`; a column\'s `prefix` is still accepted on a currency column.',
10567+ },
1053110568 // #14478 (maintainer ruling B: a duration key carries its unit in its NAME) —
1053210569 // the D3 entry of the `job-timeout-to-timeout-ms` family (ruling B on #17152:
1053310570 // one D3 entry per retirement family, even when D2 is lossless). The job half
@@ -15568,6 +15605,48 @@ const step18: MigrationStep = {
1556815605 + 'author has confirmed that everything it shows may be listed to all readers of its object, '
1556915606 + 'or has deleted it. The view switcher lists the same views as before the upgrade.',
1557015607 },
15608+ // #20186, route C-prime (seat answers 5855433719 / 5855548706). A write-time
15609+ // re-routing of the flattened view overlays: each member judges only the
15610+ // `viewKind` it names. The stored rows it newly refuses are read and served
15611+ // exactly as before and fail only on their next save, which is why this is a
15612+ // semantic entry and not a conversion — which value a refused key meant is a
15613+ // fact only its author holds.
15614+ {
15615+ id: 'view-overlay-judged-by-viewkind-arm',
15616+ surface:
15617+ 'A flattened `view` overlay saved through the metadata write door (`PUT /api/v1/meta/view/:name`, the '
15618+ + 'Studio / MCP save) whose `viewKind` names one family while the body was judged by the other: a '
15619+ + 'column-less `viewKind: "list"` body, which only the form overlay member used to accept (its list keys '
15620+ + '`sort`, `searchableFields`, `timeline`, `sharing` and the rest stripped unread), and a `viewKind: "form"` '
15621+ + 'body carrying list `columns`, which only the list overlay member used to accept.',
15622+ replacement:
15623+ 'Each overlay is judged by the member its `viewKind` names. A column-less list overlay is a patch on the '
15624+ + 'view it shadows and carries list keys the list view schema accepts: a `sort` array of `{ field, order }` '
15625+ + '(the bare string clause was retired in 17.5.0), no `timeline.metaFields` (the timeline block has no such '
15626+ + 'key), an array `searchableFields`, and the list `sharing` block (`{ type, lockedBy }`), not the form '
15627+ + 'public-link block. A column-less list overlay names no `type`; one that does is a full inline config and '
15628+ + 'lists its `columns`. A form overlay\'s `columns` is its body-column count (an integer); a field list '
15629+ + 'means the body is a list view (`viewKind: "list"`) or belongs in `sections: [{ fields }]`.',
15630+ reason:
15631+ 'Both overlay members shared one `viewKind: list | form` enum. The list member required `columns`, so it '
15632+ + 'refused the column-less list patch the console writes on every toolbar save (the ruled patch-only '
15633+ + 'storage shape, maintainer ruling: 「`persistViewPatch` 只存 patch,不存 merged base」); the '
15634+ + 'union then tried the form member, which requires no list key and strips every one, and accepted it — '
15635+ + 'so a retired `sort` string or a `timeline.metaFields` the list schema refuses by name was saved with '
15636+ + '`success: true` and stored as sent. Measured on `origin/main` @ `4df101c3` and again at `ce70876e` '
15637+ + '(after the `options`-bag door landed) through the real save. Ruled route C-prime: each member admits one `viewKind`, the list '
15638+ + 'member judges a column-less patch (`columns` optional there only; the authoring list view keeps it '
15639+ + 'required), and a column-less body that names a `type` stays refused at `columns`. Not convertible: '
15640+ + 'whether a refused value was a typo or a stale capability is the author\'s call.',
15641+ acceptanceCriteria:
15642+ 'Every stored flattened `view` overlay saves again unchanged. A row that does not is refused '
15643+ + '`422 INVALID_METADATA` on its next save, with the issue located at the refused key (`sort`, `timeline`, '
15644+ + '`searchableFields`, `sharing`, `columns`) and carrying that key\'s own message — for a column-less list '
15645+ + 'overlay naming a `type`, the prescription at `columns`; for a field list on a form overlay, the '
15646+ + 'body-column-count prescription at `columns`. Nothing is rewritten on read and nothing is refused on '
15647+ + 'read: a row that fails is served exactly as stored until it is saved. Verify by re-saving each stored '
15648+ + 'flattened overlay (a GET then a PUT of the same body) and reading a `200`.',
15649+ },
1557115650 // The door half of ruling A on objectui#10380 (#20051). A write-time narrowing
1557215651 // of the flattened view overlays: the stored rows it newly refuses are read and
1557315652 // served exactly as before and fail only on their next save, which is why this
0 commit comments