diff --git a/docs/protocol-upgrade-guide.md b/docs/protocol-upgrade-guide.md index 04c39114b3..eda395db9d 100644 --- a/docs/protocol-upgrade-guide.md +++ b/docs/protocol-upgrade-guide.md @@ -3,7 +3,7 @@ # Metadata protocol upgrade guide -Current protocol: **15.0.0** · chain support floor: **protocol 10** · generated from the ADR-0087 registries (`@objectstack/spec` `conversions/` + `migrations/`). +Current protocol: **16.0.0** · chain support floor: **protocol 10** · generated from the ADR-0087 registries (`@objectstack/spec` `conversions/` + `migrations/`). ## How to upgrade — from any past major @@ -104,6 +104,16 @@ Protocol 15 unified the conditional-visibility predicate under `visibleWhen` (AD - Why not automatic: The `.strict()` flip (ADR-0089 D3a) turns a previously silently-stripped unknown key into a parse error. There is no mapping target for an arbitrary unknown key — auto-deleting it would be exactly the silent data loss ADR-0078 bans — so each occurrence needs the author to decide: fix the typo, move it to the right layer, or delete dead metadata. - Done when: `objectstack validate` passes with no unknown-key parse errors on form fields, form sections, or page components. +## Protocol 15 → 16 + +Protocol 16 flipped `DashboardWidgetSchema` to `.strict()` (framework#3251, ADR-0021 endpoint): an undeclared top-level widget key is now a loud parse error instead of a silent strip (ADR-0049 enforce-or-remove, ADR-0078 no-silently-inert). The inline analytics shape it most often catches (`object`+`categoryField`+`valueField`+`aggregate`, pivot `rowField`/`columnField`) was already removed at protocol 9, so no mechanical rewrite applies; the residue is the strictness itself, delegated to the author because an arbitrary unknown key has no lossless canonical target. + +### Semantic (delegated to you, with acceptance criteria) + +- **`dashboard-widget-strict-unknown-keys`** — `dashboard widgets (undeclared top-level keys — legacy inline analytics, objectui-internal `component`/`data`, or typos)` → declared keys only (`dataset` + `dimensions` + `values` for analytics; `options` for renderer-specific extras) + - Why not automatic: The `.strict()` flip turns a previously silently-stripped unknown key into a parse error. There is no mapping target for an arbitrary unknown key — auto-deleting it would be exactly the silent data loss ADR-0078 bans — so each occurrence needs the author to decide: bind a `dataset` and select `dimensions`/`values`, move a renderer setting under `options`, or delete the dead key. + - Done when: `objectstack validate` passes with no unknown-key parse errors on dashboard widgets. + --- *Machine-readable equivalents: `spec-changes.json` (shipped in `@objectstack/spec` and attached to each GitHub Release) and the structured output of `objectstack migrate meta --json`.* diff --git a/packages/spec/spec-changes.json b/packages/spec/spec-changes.json index ee99fca03b..75838db21a 100644 --- a/packages/spec/spec-changes.json +++ b/packages/spec/spec-changes.json @@ -1,11 +1,11 @@ { "$comment": "GENERATED (ADR-0087 D4) — do not edit. Regenerate with: pnpm --filter @objectstack/spec gen:spec-changes. A projection of the D2 conversion table + D3 migration chain; the upgrade guide and the MCP spec_changes tool derive from this same data.", - "protocolVersion": "15.0.0", + "protocolVersion": "16.0.0", "supportFloor": 10, "migrateCommand": "objectstack migrate meta --from (N >= 10)", "aggregate": { "from": 10, - "to": 15, + "to": 16, "added": [], "converted": [ { @@ -132,6 +132,13 @@ "migrationId": "ui-schemas-strict-unknown-keys", "toMajor": 15, "rationale": "The `.strict()` flip (ADR-0089 D3a) turns a previously silently-stripped unknown key into a parse error. There is no mapping target for an arbitrary unknown key — auto-deleting it would be exactly the silent data loss ADR-0078 bans — so each occurrence needs the author to decide: fix the typo, move it to the right layer, or delete dead metadata." + }, + { + "surface": "dashboard widgets (undeclared top-level keys — legacy inline analytics, objectui-internal `component`/`data`, or typos)", + "replacement": "declared keys only (`dataset` + `dimensions` + `values` for analytics; `options` for renderer-specific extras)", + "migrationId": "dashboard-widget-strict-unknown-keys", + "toMajor": 16, + "rationale": "The `.strict()` flip turns a previously silently-stripped unknown key into a parse error. There is no mapping target for an arbitrary unknown key — auto-deleting it would be exactly the silent data loss ADR-0078 bans — so each occurrence needs the author to decide: bind a `dataset` and select `dimensions`/`values`, move a renderer setting under `options`, or delete the dead key." } ], "removed": [] @@ -307,6 +314,22 @@ } ], "removed": [] + }, + { + "from": 15, + "to": 16, + "added": [], + "converted": [], + "migrated": [ + { + "surface": "dashboard widgets (undeclared top-level keys — legacy inline analytics, objectui-internal `component`/`data`, or typos)", + "replacement": "declared keys only (`dataset` + `dimensions` + `values` for analytics; `options` for renderer-specific extras)", + "migrationId": "dashboard-widget-strict-unknown-keys", + "toMajor": 16, + "rationale": "The `.strict()` flip turns a previously silently-stripped unknown key into a parse error. There is no mapping target for an arbitrary unknown key — auto-deleting it would be exactly the silent data loss ADR-0078 bans — so each occurrence needs the author to decide: bind a `dataset` and select `dimensions`/`values`, move a renderer setting under `options`, or delete the dead key." + } + ], + "removed": [] } ] }