Skip to content

Commit 2a01821

Browse files
authored
Merge pull request #1864 from objectstack-ai/spec-field-liveness-audit
docs(audit): metadata property liveness & necessity — all types
2 parents 71578f2 + d612500 commit 2a01821

18 files changed

Lines changed: 550 additions & 0 deletions
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Audit: ActionSchema property liveness & necessity
2+
3+
**Date**: 2026-06-15 · **Scope**: `packages/spec/src/ui/action.zod.ts`. **Consumers**: objectui action renderers (`components/.../action/*`, `RowActionMenu`, `record-quick-actions`), runtime dispatcher (`useConsoleActionRuntime`, `ActionRunner`), server (`runtime/http-dispatcher``engine.executeAction``body-runner`), AI bridge (`service-ai/tools/action-tools.ts`).
4+
5+
## 🔴 `disabled``enabled` naming drift (CEL form silently ignored)
6+
Spec's canonical field is `disabled` (bool | CEL), but the primary renderers (`action-button.tsx:56,116`, action-menu, action-group) read a **non-spec `schema.enabled`** and invert it. Only detail/quick-action toolbars read spec `disabled`, and only its **boolean** form. **The CEL-predicate form of `disabled` has zero consumers** — authoring `disabled: "<expr>"` is silently ignored.
7+
8+
## 🔴 Aspirational / half-built
9+
- **`type:'form'`** — in the enum, marks `target` required, documents a `/console/forms/:name` route, but **no renderer or runtime consumes it** (AI bridge classifies it unsupported). Dead action type.
10+
- **`shortcut`**`ActionEngine` registers it + exposes `handleShortcut`, but **no keydown listener** pumps events → never fires.
11+
- **`bulkEnabled`** — engine has `getBulkActions`/`executeBulk`, but no spec-driven view path calls `executeBulk`.
12+
- **`timeout`** (action-level) — DEAD; server uses `body.timeoutMs`, no UI consumer.
13+
- **`mode`** — consumed only by the AI HITL heuristic, never by UI. **`aria`** — honored by a few renderers but not the core action buttons/menus.
14+
15+
## LIVE & well-wired
16+
`name`, `label`, `objectName`, `icon`, `type`, `target` (+`${param}`/`${ctx}` interpolation), `body` (server script only), `params` (+ field/objectOverride/defaultFromRow/options/placeholder/helpText/defaultValue/required/name), `variant`, `component`, `locations`, `confirmText`, `successMessage`, `refreshAfter`, `resultDialog` (one-shot reveal), `visible` (CEL, fail-closed), `recordIdParam`/`recordIdField`, `bodyShape{wrap}`, `bodyExtra`, `method`, `opensInNewTab`/`newTabUrl`, the full `ai.*` bridge. **Action types**: api/script/flow fully wired; url thinner; **modal PARTIAL** (console maps modal→serverActionHandler, not a real modal opener); form DEAD.
17+
18+
## 🟠 Two parallel prop-readers with different coverage
19+
The **AI bridge** (`action-tools.ts`) reads almost every advanced prop; the **visual button** (`action-button.tsx`) forwards only a fixed subset and relies on the grid path (`ObjectGrid.tsx:1333 ...rest`) to carry the full def into handlers. Divergent coverage = maintenance risk.
20+
21+
## Recommendation
22+
Fix `disabled``enabled` and wire the CEL form (correctness + matches `visible`). Decide `type:'form'`/`shortcut`/`bulkEnabled`/`timeout`/`mode`/`aria` — wire or remove. Make the modal action a real modal opener or drop it. Unify the two prop-readers.
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Audit: AgentSchema property liveness & necessity
2+
3+
**Date**: 2026-06-15 · **Scope**: `packages/spec/src/ai/agent.zod.ts`. **Consumers**: framework `service-ai/agent-runtime.ts` (+ agent/assistant routes, eval-runner); objectui `AgentPreview` (display) + chatbot (picker).
4+
5+
## 🔴 Model provider drift — `model.provider` is DEAD
6+
Runtime applies only `model.{model,temperature,maxTokens}` (`agent-runtime.ts:264-266`). **`model.provider` and `model.topP` are never applied** — the provider/model comes from the configured AI adapter (`plugin.ts`), so an agent setting `provider:'anthropic'` has **zero effect**. (`AgentPreview.tsx:124` displays provider, reinforcing the false impression.)
7+
8+
## 🔴 The entire "autonomy" surface is aspirational except one knob
9+
**DEAD** (no runtime reader; only authored + displayed in AgentPreview): `memory.*` (shortTerm/longTerm/reflectionInterval), `guardrails.*` (maxTokensPerInvocation/maxExecutionTimeSec/blockedTopics — real limits enforced by an unrelated quota service), `structuredOutput.*`, `lifecycle` (StateMachine), `planning.strategy`, `planning.allowReplan`. **The only LIVE planning sub-field is `planning.maxIterations`** (3 call sites).
10+
11+
## 🔴 Access control declared but UNENFORCED (security gap)
12+
`access`, `visibility`, `tenantId` — none read at runtime. `permissions` is display-only; the chat route **hardcodes `['ai:chat','ai:agents']`** (`agent-routes.ts:109`) regardless of the agent's declared `permissions`. So **"who can chat with this agent" is currently a no-op** — a latent access-control gap. (`visibility` defaults `organization` but gates nothing.)
13+
14+
## Drift
15+
**`knowledge`** — spec defines `{topics,indexes}` but the only consumer (`AgentPreview.tsx:213`) reads `knowledge.sources`/`indexes`, and **no runtime reads it at all** — RAG is wired via `service-knowledge`, not `agent.knowledge`. Chatbot picker `description` is derived from `role` (not an AgentSchema field).
16+
17+
## LIVE & necessary
18+
`name`, `label`, `role`, `avatar`, `instructions` (core), `model.{model,temperature,maxTokens}`, `skills` (→ tools+prompt), `tools` (legacy fallback), `active` (gates listing + 403 on chat), `planning.maxIterations`, `protection` (loader).
19+
20+
## Recommendation
21+
Either route `model.provider`/`topP` into the LLM call or remove them (currently misleading). **Enforce `permissions`/`visibility`/`access`** at the chat route, or stop accepting them (security). Prune the aspirational autonomy surface (memory/guardrails/structuredOutput/lifecycle/planning.{strategy,allowReplan}) or mark `experimental`. Fix the `knowledge` shape drift.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Audit: AppSchema property liveness & necessity
2+
3+
**Date**: 2026-06-15 · **Scope**: `packages/spec/src/ui/app.zod.ts`. **Renderers**: `objectui` `layout/NavigationRenderer.tsx`, `app-shell` `{AppSidebar,ConsoleLayout,AppContent}`. **Method**: consumer cross-reference.
4+
5+
## 🔴 Spec↔renderer drift — renderer reads fields the spec doesn't declare (would be stripped by `AppSchema.parse()`)
6+
- `branding.accentColor` — consumed `AppShell.tsx:160-165`, **absent from `AppBrandingSchema`**
7+
- nav-item `badgeVariant` — consumed `NavigationRenderer.tsx:850/890`, absent from `BaseNavItemSchema`
8+
- nav-item `type:'separator'` — rendered `NavigationRenderer.tsx:574/774`, **not a union member** in app.zod.ts
9+
10+
These are the highest-priority fixes (authoring them per spec fails; they only work because parse is bypassed somewhere).
11+
12+
## DEAD — aspirational, no consumer in either repo
13+
`version`, `aria`, `objects[]`, `apis[]`, `App.sharing`, `App.embed`, `mobileNavigation`/`bottomNavItems` (fully unimplemented — even `packages/mobile` ignores it), `branding.logo` (passed at `ConsoleLayout.tsx:117` but never read in `AppShell`).
14+
- **Misleading**: `App.sharing`/`App.embed` attach Sharing/Embed config to apps, but the only live sharing/embed path is `FormView.sharing` (`framework/.../rest-server.ts:3282`) — no public-app or iframe route reads the app-level versions.
15+
- The spec itself labels `objects[]`/`apis[]` "config convenience"; the chatbot's object list comes from nav items (`AppHeader.tsx:500 collectNavObjects`), not `App.objects`.
16+
17+
## LIVE & necessary (the sidebar core — all camelCase, no snake drift)
18+
`name`, `label`, `description`, `icon`, `active`, `isDefault`, `hidden`, `navigation` (whole tree), `areas` (precedence over navigation), `contextSelectors` (all sub-fields), `homePageId`, `defaultAgent` (dual consumer: framework `agent-runtime.ts:341` + objectui chatbot), `branding.{primaryColor,favicon}`, `protection`. Nav-item union fully live: `id/label/icon/order/badge/visible(CEL)/requiredPermissions/requiresObject/requiresService` + per-type payloads (`objectName/viewName/recordId/recordMode/dashboardName/pageName/url/target/reportName/componentRef/params/children/expanded`). NavigationContribution (ADR-0029) live via `objectql/engine.ts:912`.
19+
- PARTIAL: `App.requiredPermissions` (app-entry gate not observed; only nav-item perms enforced), nav `type:'action'` `actionDef` (fires `onAction(item)`; `actionDef.{actionName,params}` shape read loosely in the action runtime, 0 direct grep in shell).
20+
21+
## Recommendation
22+
Add the 3 drift fields to the spec (`accentColor`, `badgeVariant`, `separator`) **or** stop the renderer reading them. Prune the aspirational block; `App.sharing`/`App.embed`/`apiEnabled`-style props create a false security/feature impression.
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Audit: DashboardSchema / DashboardWidgetSchema property liveness & necessity
2+
3+
**Date**: 2026-06-15 · **Scope**: `packages/spec/src/ui/dashboard.zod.ts`. **Live path**: `objectui` `DashboardView``DashboardRenderer``DatasetWidget` (ADR-0021) / legacy inline `getComponentSchema()`. The `spec-bridge/bridges/dashboard.ts` path is **orphaned** (no `src` caller; its node types have no renderer).
4+
5+
## 🔴 The ADR-0021 cutover is half-done and the spec contradicts the code
6+
`dashboard.zod.ts:153-156` declares `dataset`+`values` **required** and says the legacy `object/categoryField/valueField/aggregate` query "was removed." But `DashboardRenderer.getComponentSchema()` (`:423-723`) is built almost entirely on that **legacy inline shape**, and Studio's `WidgetConfigPanel.tsx:161-285` still authors `object/categoryField/valueField/aggregate` with **no dataset/dimensions/values controls**. → Studio emits widgets the current spec **rejects**; the dataset-required rule cannot hold. (Same debt that left the HotCRM dashboard seeds invalid.)
7+
8+
## DEAD — only the orphaned spec-bridge references them
9+
Dashboard: `globalFilters`, `dateRange`, `aria`, `performance`. Widget: `responsive`, `aria`, `actionUrl`/`actionType`/`actionIcon`, `requiresService` (nav uses it; widgets don't). `chartConfig` is read by **CLI lint only** (`validate-widget-bindings.ts:239`), no runtime renderer. They appear in `dashboard.form.ts` so Studio shows editable fields that render nothing.
10+
11+
## Drift
12+
- **`title` vs `label`**: renderer reads `schema.title` (`DashboardRenderer.tsx:856`, `DashboardView.tsx:358`), but `DashboardSchema` has only `label`. `header.showTitle/showDescription` gate on a non-spec field.
13+
- **`refreshInterval` effectively dead**: interval logic exists (`:332`) but only fires if `onRefresh` is passed; `DashboardView` never passes it.
14+
- **Renderer depends on undeclared props**: `component`, `data`, `rowField`, `columnField`, `searchable`, `pagination`, `categoryGranularity` are read but **not in `DashboardWidgetSchema`** (inverse of dead-spec-prop).
15+
16+
## LIVE & well-wired
17+
`name`, `label`, `description`, `widgets`, `columns`, `gap`, `header.actions`, widget `id/title/description/type/colorVariant/filter→runtimeFilter/compareTo/layout.{w,h}/options/requiresObject`, and the canonical `dataset`/`dimensions`/`values` (DatasetWidget + framework `build-probes.ts` runtime probe + CLI lint). Plus the legacy `object/valueField/categoryField/aggregate` (LIVE-drift; spec says removed but renderer+Studio still depend on them).
18+
19+
## Recommendation
20+
Finish ADR-0021: migrate `DashboardRenderer` + `WidgetConfigPanel` to dataset/dimensions/values, then remove the legacy inline shape (or un-deprecate it in the spec). Fix `title``label`. Prune the orphaned-bridge-only props. Declare the undeclared-but-read props.
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Audit: DatasetSchema property liveness & necessity
2+
3+
**Date**: 2026-06-15 · **Scope**: `packages/spec/src/ui/dataset.zod.ts`. **Consumers**: framework `service-analytics` (`dataset-compiler`/`dataset-executor`/`analytics-service`), objectui `DatasetWidget`/`DatasetReportRenderer`/`DatasetDefaultInspector`.
4+
5+
## LIVE & well-wired (the analytics query path)
6+
Dataset: `name`, `label`, `object` (FROM), `include` (join allowlist), `filter`, `dimensions[]`, `measures[]`. Dimension: `name`, `field`, `type`, `dateGranularity`. Measure: `name`, `aggregate`, `field`, `filter`, `format`, `label`, `derived{op,of}`. Evidence: `dataset-compiler.ts:137-191`, `dataset-executor.ts:184-278`, `analytics-service.ts:464-472`. Report/widget renderers bind by **name only** + a `{dimensions,measures}` selection → all sub-prop resolution happens server-side in the compiler/executor.
7+
8+
## DEAD
9+
- **`description`** (dataset) — only a Studio form field; no runtime reader.
10+
- **`certified`** (measure) — has a Studio checkbox (`DatasetDefaultInspector.tsx:160`) but **no runtime gate** (aspirational ADR-0021 governance checkpoint with zero enforcement).
11+
12+
## PARTIAL / drift
13+
- **Dimension `label`** is compiled but surfaces only via `getMeta` discovery titles; charts render the raw server-resolved dimension *value*, never the declared `label`. (Measure `label` fully flows to the renderer.)
14+
- **Dual vocabulary**: spec says `measures`/`dimensions`; presentations select via `values` (measures) and `rows`/`columns` (dimensions); the compile renames `DatasetMeasure→Metric`, `aggregate→type`. Two parallel naming layers (ADR-0021 Phase-1).
15+
16+
## 🟠 Studio under-covers the live schema (authoring gap)
17+
`DatasetDefaultInspector` edits only name/label/description/object/include + dim(name/field/type) + measure(name/aggregate/field/certified). It exposes **no editor** for the LIVE, behavior-changing props: dataset `filter`, measure `filter`, measure `format`, measure `derived`, dimension `dateGranularity`, dim/measure `label`. These must be hand-authored in `.dataset.ts`.
18+
19+
## Recommendation
20+
Remove `description`/`certified` or wire them. Surface the live-but-uneditable props in the Studio dataset designer. Reconcile the measures/dimensions ↔ values/rows/columns vocabulary in one documented place.

0 commit comments

Comments
 (0)