diff --git a/.changeset/govern-report-dashboard-liveness.md b/.changeset/govern-report-dashboard-liveness.md new file mode 100644 index 0000000000..0567802748 --- /dev/null +++ b/.changeset/govern-report-dashboard-liveness.md @@ -0,0 +1,27 @@ +--- +"@objectstack/spec": patch +--- + +chore(spec): enroll `report` and `dashboard` in the liveness GOVERNED set (#3462) + +Closes the systemic anti-drift gap for two more authorable UI types (umbrella +#1878). Both were registered/round-trippable but ungoverned, so their property +liveness wasn't CI-checked — the reason drifts like dashboard `title`↔`label` +and stale report `chart` config survived until an audit caught them. + +- Added `packages/spec/liveness/report.json` (20 live / 2 dead) and + `dashboard.json` (18 live / 2 dead), each property classified with an + objectui consumer reference. +- Re-verification corrected several stale 2026-06 audit findings against current + code: report `chart` is **live** (DatasetReportChart plots `chart.xAxis`/ + `yAxis` via `useDatasetRows`, #1890/#3441); dashboard `globalFilters`/ + `dateRange` are **live** (framework#2501); `title`↔`label` fixed (objectui#2806); + the ADR-0021 widget migration shipped (#3251). Only `aria`/`performance` remain + dead on each (perf `authorWarn`'d). +- Added both to `GOVERNED` in `check-liveness.mts`; the gate is green. Future + drift on these types is now a CI failure, not an audit finding. + +`webhook` (the third type in #3462) is deferred — it isn't a registered +metadata type; its enrollment rides with the disconnect decision in #3461. + +No spec shape/behavior change (ledger + gate config only). diff --git a/packages/spec/liveness/README.md b/packages/spec/liveness/README.md index 812d136fee..203bf3f067 100644 --- a/packages/spec/liveness/README.md +++ b/packages/spec/liveness/README.md @@ -203,7 +203,10 @@ EOF | page | 16 | – | – | 1 | fully live + one planned | | view | 70 | 0 | 5 | – | list/form drilled via `children` (#2998 Track B); dead = list.{responsive,performance} + form.{data,defaultSort,aria}, all but aria authorWarn'd; form.{buttons,defaults} now live — objectui ObjectForm folds them onto its flat props (framework#1894 / #2998); audit-era DEAD lines superseded by re-verification (submitBehavior, sharing.lockedBy, list ViewData providers, and the ADR-0021 chart shape — all live now); level-2 dead residue (userActions.buttons, addRecord.mode/formView, tabs[].order) noted on parents — one drill level only | +| report | 20 | 0 | 2 | – | dataset-bound (ADR-0021); dead = aria + performance (perf authorWarn'd); audit-era `chart` DEAD superseded — DatasetReportChart plots `chart.xAxis`/`yAxis` via useDatasetRows (framework#1890 / #3441), `groupBy` stays experimental (describe marker) | +| dashboard | 18 | 0 | 2 | – | ADR-0021 dataset widgets (WidgetConfigPanel + DashboardRenderer migrated #3251; DashboardWidgetSchema `.strict()`); dead = aria + performance (perf authorWarn'd); audit-era `globalFilters`/`dateRange` DEAD superseded — LIVE via framework#2501; `title`↔`label` fixed (objectui#2806) | + The `dead` set across types is the enforce-or-remove worklist (ADR-0049); every misleading entry carries `authorWarn` so authors hear about it at compile time. -Not yet governed (rollout): dashboard, app, report, job, datasource, +Not yet governed (rollout): app, job, datasource, translation, email_template, doc, book, validation, seed. diff --git a/packages/spec/liveness/dashboard.json b/packages/spec/liveness/dashboard.json new file mode 100644 index 0000000000..48c78b003a --- /dev/null +++ b/packages/spec/liveness/dashboard.json @@ -0,0 +1,56 @@ +{ + "type": "dashboard", + "_note": "DashboardSchema (UI, ADR-0021 dataset-bound). Live path: objectui DashboardView → DashboardRenderer → DatasetWidget. Seeded from docs/audits/2026-06-dashboardschema-property-liveness.md and re-verified against objectui HEAD — several audit-era findings are superseded: the ADR-0021 widget migration shipped (Studio WidgetConfigPanel + DashboardRenderer on dataset/dimensions/values, framework#3251; DashboardWidgetSchema is now `.strict()`); `globalFilters`/`dateRange` are LIVE (dashboard-level filters, framework#2501); the `title`↔`label` drift is fixed (renderer falls back to `label`, objectui#2806); the undeclared widget props were reconciled (#1894). objectui paths cited as prose in `note` (not `evidence`). Framework provenance/lock fields auto-classify live (ADR-0010). Widget-level props are classified in the DashboardWidgetSchema subtree, not drilled here.", + "props": { + "name": { + "status": "live", + "note": "dashboard identity — metadata key + objectui DashboardView/DashboardRenderer." + }, + "label": { + "status": "live", + "note": "objectui: DashboardRenderer header title (falls back from the legacy `title`, objectui#2806) + spec-bridge dashboard.ts." + }, + "description": { + "status": "live", + "note": "objectui: DashboardRenderer header (`:787`), gated by header.showDescription." + }, + "header": { + "status": "live", + "note": "objectui: DashboardRenderer.tsx:778-796 — showTitle/showDescription gates + header.actions row." + }, + "widgets": { + "status": "live", + "note": "objectui: the widget grid — DashboardRenderer maps each to DatasetWidget/metric/etc. Per-widget props live in the DashboardWidgetSchema subtree (strict, ADR-0021)." + }, + "columns": { + "status": "live", + "note": "objectui: DashboardRenderer.tsx:250 — grid column count (inferred when unset)." + }, + "gap": { + "status": "live", + "note": "objectui: DashboardRenderer.tsx:262 — grid gap." + }, + "refreshInterval": { + "status": "live", + "note": "objectui: DashboardRenderer.tsx:385-386 — sets a setInterval(onRefresh) every N seconds. Caveat: only fires when the host passes an `onRefresh` handler; the renderer is the consumer." + }, + "globalFilters": { + "status": "live", + "note": "objectui: DashboardRenderer.tsx:279,988 — dashboard-level filter values hosted and merged into every widget's query (framework#2501). Supersedes the 2026-06 audit's DEAD finding." + }, + "dateRange": { + "status": "live", + "note": "objectui: DashboardRenderer.tsx:279,988 — dashboard-level date-range filter (framework#2501). Supersedes the 2026-06 audit's DEAD finding." + }, + "aria": { + "status": "dead", + "note": "ARIA attributes — authorable (dashboard.form.ts) but no DashboardRenderer path applies them." + }, + "performance": { + "status": "dead", + "authorWarn": true, + "authorHint": "Dashboard performance tuning is not implemented — this config has no runtime consumer.", + "note": "Perf-optimization config — authorable but no renderer/runtime reads it (audit; re-verified — no consumer in plugin-dashboard)." + } + } +} diff --git a/packages/spec/liveness/report.json b/packages/spec/liveness/report.json new file mode 100644 index 0000000000..437b2a1144 --- /dev/null +++ b/packages/spec/liveness/report.json @@ -0,0 +1,64 @@ +{ + "type": "report", + "_note": "ReportSchema (analytics report, ADR-0021 single-form). Live path: objectui ReportRenderer → DatasetReportRenderer (dataset-bound). Seeded from docs/audits/2026-06-reportschema-property-liveness.md and re-verified against objectui HEAD — the audit's `chart` = DEAD finding is superseded: DatasetReportRenderer's DatasetReportChart now plots `chart.xAxis`/`yAxis` (dataset dimension/measure) via useDatasetRows (framework#1890/#3441), so `chart` is LIVE (only its `groupBy` sub-key stays experimental, per its own describe marker). objectui paths cited as prose in `note` (not `evidence`, which resolves framework file:line). Framework provenance/lock fields (protection/_lock*) auto-classify live (ADR-0010).", + "props": { + "name": { + "status": "live", + "note": "report identity — metadata key + objectui ReportRenderer." + }, + "label": { + "status": "live", + "note": "objectui: DatasetReportRenderer header + Studio ReportDefaultInspector." + }, + "description": { + "status": "live", + "note": "display subtitle — objectui report header." + }, + "type": { + "status": "live", + "note": "objectui: DatasetReportRenderer routes tabular/summary/matrix (cross-tab + server totals) / joined." + }, + "dataset": { + "status": "live", + "note": "objectui: DatasetReportRenderer binds the semantic-layer dataset (ADR-0021); superRefine requires dataset+values for non-joined reports." + }, + "rows": { + "status": "live", + "note": "objectui: dataset dimension names grouped down — DatasetReportRenderer." + }, + "columns": { + "status": "live", + "note": "objectui: matrix across-dimensions (rows × columns pivot) — DatasetReportRenderer." + }, + "values": { + "status": "live", + "note": "objectui: dataset measure names — DatasetReportRenderer; required (superRefine)." + }, + "runtimeFilter": { + "status": "live", + "note": "objectui: render-time scope filter ANDed at query time (`?? filter`, mergeFilters) — DatasetReportRenderer." + }, + "drilldown": { + "status": "live", + "note": "objectui: DatasetReportRenderer.tsx:697 — click-through to underlying records (default on; set false to disable)." + }, + "chart": { + "status": "live", + "note": "objectui: DatasetReportRenderer.tsx:776 + DatasetReportChart plots `chart.xAxis` (dataset dimension) × `chart.yAxis` (measure) via useDatasetRows, honoring `type`/`title` (framework#1890/#3441). Supersedes the 2026-06 audit's DEAD finding. Sub-key `groupBy` stays experimental (its own describe marker) — the dataset renderer plots a single series and never reads it." + }, + "blocks": { + "status": "live", + "note": "objectui: joined-report sub-reports, each dataset-bound (dataset/rows/columns/values/runtimeFilter/chart) — DatasetReportRenderer joined branch." + }, + "aria": { + "status": "dead", + "note": "ARIA attributes — authorable (report.form.ts) but no report renderer applies them; the dataset renderer ignores `report.aria`." + }, + "performance": { + "status": "dead", + "authorWarn": true, + "authorHint": "Report performance tuning is not implemented — this config has no runtime consumer.", + "note": "Perf-optimization config — authorable but no renderer/runtime reads it (audit L; re-verified — no consumer in plugin-report)." + } + } +} diff --git a/packages/spec/scripts/liveness/check-liveness.mts b/packages/spec/scripts/liveness/check-liveness.mts index e32c45652d..00e7a49d23 100644 --- a/packages/spec/scripts/liveness/check-liveness.mts +++ b/packages/spec/scripts/liveness/check-liveness.mts @@ -53,7 +53,7 @@ const repoRoot = resolve(specRoot, '../..'); const ledgerRoot = join(specRoot, 'liveness'); // Governed metadata types, rolled out highest-frequency / highest-risk first. -const GOVERNED = ['object', 'field', 'flow', 'action', 'hook', 'permission', 'position', 'agent', 'tool', 'skill', 'dataset', 'page', 'view']; +const GOVERNED = ['object', 'field', 'flow', 'action', 'hook', 'permission', 'position', 'agent', 'tool', 'skill', 'dataset', 'page', 'view', 'report', 'dashboard']; // ADR-0010 provenance/lock overlay fields — system-stamped, on every type; auto-live. const FRAMEWORK_FIELDS = new Set([