Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions .changeset/close-out-sweep-inert-keys.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
---
"@objectstack/spec": major
---

refactor(spec)!: the #3896 close-out sweep — fourteen inert authoring keys leave the surface

The enforce-or-remove worklist across the remaining metadata types, each key
tombstoned at its schema with the prescription (`retiredKey`) and stripped by
a protocol-17 conversion (`os migrate meta` rewrites sources):

- **action** `shortcut` / `bulkEnabled` — no keydown path ever dispatched a
shortcut; the multi-select toolbar reads the view's `bulkActions`.
- **flow** `active` / `template`, node `outputSchema`, errorHandling
`fallbackNodeId` — `active: false` never stopped a flow (`status` is the
enforced lifecycle; the default even read as disabled while the engine
treated unset as enabled); faults route via per-node fault edges.
- **view** list `responsive` / `performance`, form `defaultSort` / `aria` —
no renderer read any of them. List `aria`/`data` stay live, and **form
`data` survived the sweep**: the removal attempt broke the build —
`defineForm` writes `data.provider='schema'` onto every metadata form —
which re-verified the entry; its ledger verdict is corrected instead.
- **dashboard** `aria` / `performance`, widget `performance` (+ the orphaned
`PerformanceConfigSchema`) — no renderer applied them; virtual scrolling is
the live top-level `virtualScroll`.
- **agent** `knowledge` (+ `AIKnowledgeSchema`) — declaring sources/indexes
never scoped retrieval: `search_knowledge` takes `sourceIds` from the LLM's
tool-call arguments. The protocol-17 `topics`→`sources` rename is absorbed
into the removal pre-release.
- **skill** `triggerPhrases` — phrases were never matched; activation is
`triggerConditions` ∩ the agent's `skills[]` allowlist.

Docs-shaped annotation fields (`hook.label`/`description`, `flow.description`)
are deliberately KEPT and so noted in the ledger — they document intent for
the next reader and are exempt from enforce-or-remove. The stale report
`aria`/`performance` ledger entries (schema already clean) are deleted as
hygiene.
17 changes: 4 additions & 13 deletions content/docs/ai/agents.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -258,13 +258,12 @@ Always be professional and data-driven.`,

// Capability comes from skills — the only tool-bearing slot (ADR-0064).
// Each skill names the platform tools and `action_<name>` tools it needs.
// (There is no agent-level `knowledge` block — it was removed in protocol
// 17 (#3896 close-out): declaring sources never scoped retrieval. Restrict
// access at the knowledge-service/source level; describe intended
// grounding in `instructions`.)
skills: ['lead_qualification', 'opportunity_coaching', 'email_drafting'],

// RAG access: sources to recruit knowledge from + vector store indexes.
knowledge: {
sources: ['sales-playbook', 'leads', 'opportunities'],
indexes: ['sales_docs'],
},
});
```

Expand Down Expand Up @@ -298,10 +297,6 @@ Always be empathetic and solution-focused.`,
// Protocol). Neither needs a `defineTool` record.
skills: ['case_triage', 'knowledge_search', 'response_drafting'],

knowledge: {
sources: ['support-kb', 'cases'],
indexes: ['support_docs'],
},
});
```

Expand Down Expand Up @@ -371,10 +366,6 @@ Use the data tools to query records and aggregate metrics.`,
// names directly — see "Natural Language Queries".
skills: ['revenue_forecasting'],

knowledge: {
sources: ['opportunities', 'pipeline'],
indexes: ['sales_docs'],
},
});
```

Expand Down
4 changes: 0 additions & 4 deletions content/docs/automation/flows.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ const approvalFlow = {
type: 'record_change',
version: 1,
status: 'active',
template: false,
runAs: 'system',

variables: [
Expand Down Expand Up @@ -74,7 +73,6 @@ const approvalFlow = {
| `version` | `number` | optional | Version number (defaults to `1`) |
| `status` | `enum` | optional | `'draft'`, `'active'`, `'obsolete'`, `'invalid'` (defaults to `'draft'`) |
| `type` | `FlowType` | ✅ | Flow trigger type (see below) |
| `template` | `boolean` | optional | Is this a reusable subflow template (defaults to `false`) |
| `runAs` | `enum` | optional | `'system'` or `'user'` execution context (defaults to `'user'`) |
| `variables` | `FlowVariable[]` | optional | Input/output variables |
| `nodes` | `FlowNode[]` | ✅ | Flow nodes |
Expand Down Expand Up @@ -670,7 +668,6 @@ errorHandling: {
strategy: 'retry', // 'fail' | 'retry' | 'continue'
maxRetries: 3,
retryDelayMs: 5000,
fallbackNodeId: 'error_handler',
}
```

Expand All @@ -679,7 +676,6 @@ errorHandling: {
| `strategy` | `enum` | `'fail'` (stop), `'retry'` (retry), `'continue'` (skip) |
| `maxRetries` | `number` | Maximum retry attempts (0-10) |
| `retryDelayMs` | `number` | Delay between retries (ms) |
| `fallbackNodeId` | `string` | Node to execute on failure |

## Discovery & Registration

Expand Down
21 changes: 4 additions & 17 deletions content/docs/references/ai/agent.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -14,26 +14,13 @@ AI Model Configuration
## TypeScript Usage

```typescript
import { AIKnowledge, AIModelConfig, Agent, StructuredOutputConfig, StructuredOutputFormat, TransformPipelineStep } from '@objectstack/spec/ai';
import type { AIKnowledge, AIModelConfig, Agent, StructuredOutputConfig, StructuredOutputFormat, TransformPipelineStep } from '@objectstack/spec/ai';
import { AIModelConfig, Agent, StructuredOutputConfig, StructuredOutputFormat, TransformPipelineStep } from '@objectstack/spec/ai';
import type { AIModelConfig, Agent, StructuredOutputConfig, StructuredOutputFormat, TransformPipelineStep } from '@objectstack/spec/ai';

// Validate data
const result = AIKnowledge.parse(data);
const result = AIModelConfig.parse(data);
```

---

## AIKnowledge

### Properties

| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **sources** | `string[]` | optional | Knowledge sources/tags to recruit RAG context from. Canonical key consumed by the agent renderer (objectui AgentPreview KnowledgeSummary). |
| **topics** | `any` | optional | [REMOVED] `knowledge.topics` was removed in @objectstack/spec 17 (#3855) — use `knowledge.sources`. Rename the key; the value (a list of source tags) is unchanged. Run `os migrate meta --from 16` to rewrite it automatically. |
| **indexes** | `string[]` | ✅ | Vector Store Indexes |


---

## AIModelConfig
Expand Down Expand Up @@ -67,7 +54,7 @@ const result = AIKnowledge.parse(data);
| **surface** | `Enum<'ask' \| 'build'>` | ✅ | Product surface this agent binds ('ask' \| 'build') — ADR-0063 §1 |
| **skills** | `string[]` | optional | Skill names to attach (Agent→Skill→Tool architecture) |
| **tools** | `any` | optional | [REMOVED] `agent.tools` was removed in @objectstack/spec 17 (#3894) — use `skills`. An agent reaches exactly the tools its surface-compatible skills declare (ADR-0064), so move each reference into a skill: a platform tool by its registered name, or `action_<name>` for one of your own AI-exposed Actions. Run `os migrate meta --from 16` to rewrite it automatically. |
| **knowledge** | `{ sources?: string[]; topics?: any; indexes: string[] }` | optional | RAG access |
| **knowledge** | `any` | optional | [REMOVED] `agent.knowledge` was removed in @objectstack/spec 17.0.0 (#3896 audit close-out) — declaring knowledge sources/indexes on an agent never scoped retrieval: the `search_knowledge` tool takes `sourceIds` from the LLM's tool-call arguments, not from the agent record. Delete the block. Restrict retrieval at the knowledge-service / source level (per-source permissions), and describe intended grounding in `instructions` so the model asks for the right sources. |
| **active** | `boolean` | ✅ | |
| **access** | `string[]` | optional | Who can chat with this agent |
| **permissions** | `string[]` | optional | Required permission-set capabilities |
Expand Down
2 changes: 1 addition & 1 deletion content/docs/references/ai/skill.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ const result = Skill.parse(data);
| **surface** | `Enum<'ask' \| 'build' \| 'both'>` | ✅ | Agent surface this skill binds to ('ask' \| 'build' \| 'both') — ADR-0063 §3 |
| **instructions** | `string` | optional | LLM instructions when skill is active |
| **tools** | `string[]` | ✅ | Tool names belonging to this skill (supports trailing wildcard, e.g. `action_*`) |
| **triggerPhrases** | `string[]` | optional | Phrases that activate this skill |
| **triggerPhrases** | `any` | optional | [REMOVED] `skill.triggerPhrases` was removed in @objectstack/spec 17.0.0 (#3896 audit close-out) — phrases were never matched against the user's message; skill activation is `triggerConditions` (AND of context field/operator/value) intersected with the agent's `skills[]`, plus explicit /skill-name pinning. Delete the key. Put routing intent in `triggerConditions`; describe intent in `description`/`instructions` for the LLM. |
| **triggerConditions** | `{ field: string; operator: Enum<'eq' \| 'neq' \| 'in' \| 'not_in' \| 'contains'>; value: string \| string[] }[]` | optional | Programmatic activation conditions |
| **active** | `boolean` | ✅ | Whether the skill is enabled |
| **protection** | `{ lock: Enum<'none' \| 'no-overlay' \| 'no-delete' \| 'full'>; reason: string; docsUrl?: string }` | optional | Package author protection block — lock policy for this skill. |
Expand Down
4 changes: 2 additions & 2 deletions content/docs/references/api/automation-api.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -102,12 +102,12 @@ const result = AutomationApiErrorCode.parse(data);
| **errorMessage** | `string` | optional | Toast shown when a screen flow fails (defaults to the raw error). |
| **version** | `integer` | optional | Version number |
| **status** | `Enum<'draft' \| 'active' \| 'obsolete' \| 'invalid'>` | optional | Deployment status |
| **template** | `boolean` | optional | Is logic template (Subflow) |
| **template** | `any` | optional | [REMOVED] `flow.template` was removed in @objectstack/spec 17.0.0 (#3896 audit close-out) — no designer or engine path ever read it, so flagging a flow as a template/subflow did nothing. Delete the key. Shared logic is invoked via a subflow NODE referencing the flow by name. |
| **type** | `Enum<'autolaunched' \| 'record_change' \| 'schedule' \| 'screen' \| 'api'>` | ✅ | Flow type |
| **variables** | `{ name: string; type: string; isInput?: boolean; isOutput?: boolean }[]` | optional | Flow variables |
| **nodes** | `{ id: string; type: string; label: string; config?: Record<string, any>; … }[]` | ✅ | Flow nodes |
| **edges** | `{ id: string; source: string; target: string; condition?: string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }; … }[]` | ✅ | Flow connections |
| **active** | `boolean` | optional | Is active (Deprecated: use status) |
| **active** | `any` | optional | [REMOVED] `flow.active` was removed in @objectstack/spec 17.0.0 (#3896 audit close-out) — it never had an effect: the engine arms flows from `status`, and `active: false` did NOT stop a flow (worse, the default read as disabled while the engine treated unset as enabled). Delete the key. Use `status: 'obsolete'` (or 'invalid') to unbind and disable a flow, `status: 'active'` to arm it. |
| **runAs** | `Enum<'system' \| 'user'>` | optional | Execution identity for the run: system = elevated (bypasses RLS), user = the triggering user (RLS-respecting). A run with no trigger user has no identity to scope to, so under user its data operations are REFUSED — declare system to make the elevation explicit. This covers schedule/time-relative/api triggers AND any record-change flow fired by a write that carried no user. |
| **errorHandling** | `{ strategy?: Enum<'fail' \| 'retry' \| 'continue'>; maxRetries?: integer; retryDelayMs?: integer; backoffMultiplier?: number; … }` | optional | Flow-level error handling configuration |
| **protection** | `{ lock: Enum<'none' \| 'no-overlay' \| 'no-delete' \| 'full'>; reason: string; docsUrl?: string }` | optional | Package author protection block — lock policy for this flow. |
Expand Down
6 changes: 3 additions & 3 deletions content/docs/references/automation/flow.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,12 @@ const result = Flow.parse(data);
| **errorMessage** | `string` | optional | Toast shown when a screen flow fails (defaults to the raw error). |
| **version** | `integer` | optional | Version number |
| **status** | `Enum<'draft' \| 'active' \| 'obsolete' \| 'invalid'>` | optional | Deployment status |
| **template** | `boolean` | optional | Is logic template (Subflow) |
| **template** | `any` | optional | [REMOVED] `flow.template` was removed in @objectstack/spec 17.0.0 (#3896 audit close-out) — no designer or engine path ever read it, so flagging a flow as a template/subflow did nothing. Delete the key. Shared logic is invoked via a subflow NODE referencing the flow by name. |
| **type** | `Enum<'autolaunched' \| 'record_change' \| 'schedule' \| 'screen' \| 'api'>` | ✅ | Flow type |
| **variables** | `{ name: string; type: string; isInput?: boolean; isOutput?: boolean }[]` | optional | Flow variables |
| **nodes** | `{ id: string; type: string; label: string; config?: Record<string, any>; … }[]` | ✅ | Flow nodes |
| **edges** | `{ id: string; source: string; target: string; condition?: string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }; … }[]` | ✅ | Flow connections |
| **active** | `boolean` | optional | Is active (Deprecated: use status) |
| **active** | `any` | optional | [REMOVED] `flow.active` was removed in @objectstack/spec 17.0.0 (#3896 audit close-out) — it never had an effect: the engine arms flows from `status`, and `active: false` did NOT stop a flow (worse, the default read as disabled while the engine treated unset as enabled). Delete the key. Use `status: 'obsolete'` (or 'invalid') to unbind and disable a flow, `status: 'active'` to arm it. |
| **runAs** | `Enum<'system' \| 'user'>` | optional | Execution identity for the run: system = elevated (bypasses RLS), user = the triggering user (RLS-respecting). A run with no trigger user has no identity to scope to, so under user its data operations are REFUSED — declare system to make the elevation explicit. This covers schedule/time-relative/api triggers AND any record-change flow fired by a write that carried no user. |
| **errorHandling** | `{ strategy?: Enum<'fail' \| 'retry' \| 'continue'>; maxRetries?: integer; retryDelayMs?: integer; backoffMultiplier?: number; … }` | optional | Flow-level error handling configuration |
| **protection** | `{ lock: Enum<'none' \| 'no-overlay' \| 'no-delete' \| 'full'>; reason: string; docsUrl?: string }` | optional | Package author protection block — lock policy for this flow. |
Expand Down Expand Up @@ -103,7 +103,7 @@ const result = Flow.parse(data);
| **position** | `{ x: number; y: number }` | optional | |
| **timeoutMs** | `integer` | optional | Maximum execution time for this node in milliseconds |
| **inputSchema** | `Record<string, { type: Enum<'string' \| 'number' \| 'boolean' \| 'object' \| 'array'>; required: boolean; description?: string }>` | optional | Input parameter schema for this node |
| **outputSchema** | `Record<string, { type: Enum<'string' \| 'number' \| 'boolean' \| 'object' \| 'array'>; description?: string }>` | optional | Output schema declaration for this node |
| **outputSchema** | `any` | optional | [REMOVED] `flow.nodes[].outputSchema` was removed in @objectstack/spec 17.0.0 (#3896 audit close-out) — it was never validated: the engine does not check node outputs against it, so it documented a contract nothing enforced. Delete the key. Downstream nodes read prior outputs via expressions (`{{nodeId.field}`}) regardless of any declaration. |
| **waitEventConfig** | `{ eventType: Enum<'timer' \| 'signal' \| 'webhook' \| 'manual' \| 'condition'>; timerDuration?: string; signalName?: string; timeoutMs?: integer; … }` | optional | Configuration for wait node event resumption |
| **boundaryConfig** | `{ attachedToNodeId: string; eventType: Enum<'error' \| 'timer' \| 'signal' \| 'cancel'>; interrupting: boolean; errorCode?: string; … }` | optional | Configuration for boundary events attached to host nodes |

Expand Down
4 changes: 2 additions & 2 deletions content/docs/references/ui/action.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,8 @@ const result = Action.parse(data);
| **requiresFeature** | `Enum<'twoFactor' \| 'passkeys' \| 'magicLink' \| 'organization' \| 'multiOrgEnabled' \| 'degradedTenancy' \| 'oidcProvider' \| 'sso' \| 'ssoEnforced' \| 'deviceAuthorization' \| 'admin' \| 'phoneNumber' \| 'phoneNumberOtp'>` | optional | Public auth feature flag gating this action; lowered into `visible` at parse time. |
| **disabled** | `boolean \| string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }` | optional | Boolean or predicate (CEL) — action is disabled when TRUE. |
| **requiredPermissions** | `string[]` | optional | [ADR-0066 D4] Capabilities required to invoke this action. Enforced with 403 on the platform action route (script/flow/modal + MCP) and mirrored as a UI hide; a `type: api` action pointed at a custom endpoint must re-check it there. |
| **shortcut** | `string` | optional | Keyboard shortcut to trigger this action (e.g., "Ctrl+S") |
| **bulkEnabled** | `boolean` | optional | Whether this action can be applied to multiple selected records |
| **shortcut** | `any` | optional | [REMOVED] `action.shortcut` was removed in @objectstack/spec 17.0.0 (#3896 audit close-out) — it never triggered anything: no keydown listener feeds ActionEngine.getShortcuts(), and objectui's keyboard stack (useKeyboardShortcuts) is hand-registered and never consults action metadata. Delete the key. For a real shortcut, register the key in the Console keyboard stack and have its handler invoke the action by name. |
| **bulkEnabled** | `any` | optional | [REMOVED] `action.bulkEnabled` was removed in @objectstack/spec 17.0.0 (#3896 audit close-out) — the multi-select toolbar is driven by the LIST VIEW's `bulkActions` / `bulkActionDefs`, never by this flag, so setting it changed nothing. Delete the key and declare the action in the view's `bulkActions` instead. |
| **ai** | `{ exposed?: boolean; description?: string; category?: Enum<'data' \| 'action' \| 'flow' \| 'integration' \| 'vector_search' \| 'analytics' \| 'utility'>; paramHints?: Record<string, { description?: string; enum?: string \| number[]; examples?: any[] }>; … }` | optional | AI exposure (opt-in). Set ai.exposed=true + ai.description to make this callable by agents. |
| **recordIdParam** | `string` | optional | Body key to inject the row id into when running from a list_item context. |
| **recordIdField** | `string` | optional | Row field whose value seeds recordIdParam. Defaults to "id". |
Expand Down
4 changes: 2 additions & 2 deletions content/docs/references/ui/dashboard.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ const result = Dashboard.parse(data);
| **refreshInterval** | `number` | optional | Auto-refresh interval in seconds |
| **dateRange** | `{ field?: string; defaultRange: Enum<'today' \| 'yesterday' \| 'this_week' \| 'last_week' \| 'this_month' \| 'last_month' \| 'this_quarter' \| 'last_quarter' \| 'this_year' \| 'last_year' \| 'last_7_days' \| 'last_30_days' \| 'last_90_days' \| 'custom'>; allowCustomRange: boolean }` | optional | Global dashboard date range filter configuration |
| **globalFilters** | `{ name?: string; field: string; label?: string; type?: Enum<'text' \| 'select' \| 'date' \| 'number' \| 'lookup'>; … }[]` | optional | Global filters that apply to all widgets in the dashboard |
| **aria** | `{ ariaLabel?: string; ariaDescribedBy?: string; role?: string }` | optional | ARIA accessibility attributes |
| **performance** | `{ lazyLoad?: boolean; virtualScroll?: object; cacheStrategy?: Enum<'none' \| 'cache-first' \| 'network-first' \| 'stale-while-revalidate'>; prefetch?: boolean; … }` | optional | Performance optimization settings |
| **aria** | `any` | optional | [REMOVED] `dashboard.aria` was removed in @objectstack/spec 17.0.0 (#3896 audit close-out) — no dashboard renderer ever applied it, so declared ARIA attributes silently did not reach the DOM. Delete the key. |
| **performance** | `any` | optional | [REMOVED] `dashboard.performance` was removed in @objectstack/spec 17.0.0 (#3896 audit close-out) — no renderer or runtime read it; dashboard performance tuning was never implemented. Delete the key. |
| **protection** | `{ lock: Enum<'none' \| 'no-overlay' \| 'no-delete' \| 'full'>; reason: string; docsUrl?: string }` | optional | Package author protection block — lock policy for this dashboard. |
| **_lock** | `Enum<'none' \| 'no-overlay' \| 'no-delete' \| 'full'>` | optional | Item-level lock — controls overlay & delete (ADR-0010). |
| **_lockReason** | `string` | optional | Human-readable reason shown when a write is refused by _lock. |
Expand Down
Loading
Loading