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
40 changes: 40 additions & 0 deletions .changeset/docs-gen-retired-key-never.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
---
"@objectstack/spec": patch
---

fix(spec): the reference generator prints a `retiredKey()` tombstone as `never`, not `any` (#5606)

`retiredKey()` is `z.never()`, which `z.toJSONSchema` emits as `{ "not": {} }` —
a node with no `type`, no `$ref` and no `enum`. `formatType()` had no branch for
it, so every one of the ~28 tombstones in the spec fell through to the
`prop.type || 'any'` tail and the generated reference pages typed a **removed**
key as **`any`**.

That is the worst available rendering for a retirement. These pages are the
primary input for an upgrading author — very often an AI one (ADR-0033) — and
`heading?: any` does not read "this key was deleted", it reads "this slot exists
and nothing validates it": strictly *more* inviting than the `heading?: string`
it replaced. The author writes it, the parse rejects it with the `[REMOVED]`
prescription, and the prescription arrives only after a wrong metadata file
already exists.

Two changes, both in `scripts/lib/format-type.ts`:

- **`{ not: {} }` now renders as `never`.** Accurate TypeScript — the key's
`z.input` type *is* `never` — and, unlike `any`, self-evident with no prose
to lean on.
- **Tombstones are dropped from an inline shape summary before
`INLINE_KEY_LIMIT` counts.** A summary cell prints `k?: type` for the first
four declared keys and has no description column, so a nested tombstone had
nowhere to put its prescription at all: `ui/theme.mdx` advertised
`{ base?: string; heading?: any; mono?: any }` with both prescriptions
appearing NOWHERE on the page. Retired keys are no longer authorable surface,
so they no longer spend one of the four slots — nor push a key the author
must write behind the `…`. The known workaround of moving a tombstone to the
bottom of the shape cannot cover this: #5248 retired `IndexSchema` down to
three live keys, and with a limit of four the first tombstone is then
*mathematically* guaranteed into the summary.

Per-key table rows are unaffected and keep carrying the full `[REMOVED]`
prescription in their description column; their type cell simply now says
`never` instead of `any`.
4 changes: 2 additions & 2 deletions content/docs/references/ai/agent.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,8 @@ const result = AIModelConfigSchema.parse(data);
| **lifecycle** | `{ id: string; description?: string; contextSchema?: Record<string, any>; initial: string; … }` | optional | [EXPERIMENTAL — not enforced] State machine defining the agent conversation flow and constraints. Parsed but no runtime consumer yet (liveness #1878/#1893). |
| **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** | `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. |
| **tools** | `never` | 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** | `never` | 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 = SkillSchema.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** | `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. |
| **triggerPhrases** | `never` | 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/analytics.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,8 @@ const result = AnalyticsEndpoint.parse(data);
| **limit** | `number` | optional | |
| **offset** | `number` | optional | |
| **timezone** | `string` | optional | |
| **query** | `any` | optional | [REMOVED] `query` was removed from AnalyticsQueryRequest in @objectstack/spec 17.0.0 (#3878). The `{ cube, query: {...} }` envelope was the dialect of the retired degraded analytics shim (#3891) — the real engine never understood it. Move the query.* fields to the body top level: `{ cube, measures, dimensions?, where?, timeDimensions?, order?, limit?, offset?, timezone? }`. |
| **format** | `any` | optional | [REMOVED] `format` was removed from AnalyticsQueryRequest in @objectstack/spec 17.0.0 (#3878). It was never implemented — every response is the JSON envelope. Delete the key; for CSV/XLSX use the export surface instead. |
| **query** | `never` | optional | [REMOVED] `query` was removed from AnalyticsQueryRequest in @objectstack/spec 17.0.0 (#3878). The `{ cube, query: {...} }` envelope was the dialect of the retired degraded analytics shim (#3891) — the real engine never understood it. Move the query.* fields to the body top level: `{ cube, measures, dimensions?, where?, timeDimensions?, order?, limit?, offset?, timezone? }`. |
| **format** | `never` | optional | [REMOVED] `format` was removed from AnalyticsQueryRequest in @objectstack/spec 17.0.0 (#3878). It was never implemented — every response is the JSON envelope. Delete the key; for CSV/XLSX use the export surface instead. |


---
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** | `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. |
| **template** | `never` | 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** | `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. |
| **active** | `never` | 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; backoffMs?: 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
10 changes: 5 additions & 5 deletions content/docs/references/api/batch.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ const result = BatchConfigSchema.parse(data);
| :--- | :--- | :--- | :--- |
| **enabled** | `boolean` | ✅ | Enable batch operations |
| **maxRecordsPerBatch** | `integer` | ✅ | Maximum records per batch |
| **defaultOptions** | `{ atomic: boolean; returnRecords: boolean; continueOnError: boolean; validateOnly?: any }` | optional | Default batch options |
| **defaultOptions** | `{ atomic: boolean; returnRecords: boolean; continueOnError: boolean }` | optional | Default batch options |


---
Expand Down Expand Up @@ -89,7 +89,7 @@ const result = BatchConfigSchema.parse(data);
| **atomic** | `boolean` | ✅ | Opt-in all-or-nothing. When explicitly true the whole batch runs inside ONE engine transaction: the first failure rolls back every prior write, and the response reports zero successes — each row carries `errors[0].code` ROLLED_BACK (written, then undone), the causal row its own error, and rows never reached NOT_ATTEMPTED. A runtime that cannot roll back REFUSES the request (501 NOT_IMPLEMENTED) rather than silently degrading to best-effort — probe `capabilities.transactionalBatch` on /discovery first. Takes precedence over continueOnError. Default false: sequential best-effort. |
| **returnRecords** | `boolean` | ✅ | If true, return full record data in response |
| **continueOnError** | `boolean` | ✅ | If true (and atomic=false), continue processing remaining records after errors |
| **validateOnly** | `any` | optional | [REMOVED] `options.validateOnly` was removed from BatchOptions in @objectstack/spec (#4052). It was never implemented: the batch surfaces persisted regardless, so a "dry-run" would have silently executed. There is no dry-run today — drop the key. If you need to preview a batch without writing, open an issue so it can be designed (no-commit cascade / constraint semantics) and reintroduced as a flag that actually holds. |
| **validateOnly** | `never` | optional | [REMOVED] `options.validateOnly` was removed from BatchOptions in @objectstack/spec (#4052). It was never implemented: the batch surfaces persisted regardless, so a "dry-run" would have silently executed. There is no dry-run today — drop the key. If you need to preview a batch without writing, open an issue so it can be designed (no-commit cascade / constraint semantics) and reintroduced as a flag that actually holds. |


---
Expand All @@ -115,7 +115,7 @@ const result = BatchConfigSchema.parse(data);
| :--- | :--- | :--- | :--- |
| **operation** | `Enum<'create' \| 'update' \| 'upsert' \| 'delete'>` | ✅ | Type of batch operation |
| **records** | `{ id?: string; data?: Record<string, any>; externalId?: string }[]` | ✅ | Array of records to process (server caps the count — see batch.maxBatchSize) |
| **options** | `{ atomic: boolean; returnRecords: boolean; continueOnError: boolean; validateOnly?: any }` | optional | Batch operation options |
| **options** | `{ atomic: boolean; returnRecords: boolean; continueOnError: boolean }` | optional | Batch operation options |


---
Expand Down Expand Up @@ -199,7 +199,7 @@ A cross-object batch strip event: dropped fields plus the operation index
| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **ids** | `string[]` | ✅ | Array of record IDs to delete (server caps the count — see batch.maxBatchSize) |
| **options** | `{ atomic: boolean; returnRecords: boolean; continueOnError: boolean; validateOnly?: any }` | optional | Delete options |
| **options** | `{ atomic: boolean; returnRecords: boolean; continueOnError: boolean }` | optional | Delete options |


---
Expand All @@ -223,7 +223,7 @@ A cross-object batch strip event: dropped fields plus the operation index
| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **records** | `{ id: string; data: Record<string, any> }[]` | ✅ | Array of records to update (server caps the count — see batch.maxBatchSize) |
| **options** | `{ atomic: boolean; returnRecords: boolean; continueOnError: boolean; validateOnly?: any }` | optional | Update options |
| **options** | `{ atomic: boolean; returnRecords: boolean; continueOnError: boolean }` | optional | Update options |


---
Expand Down
Loading
Loading