You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(spec): one house sentence for every os-migrate-meta prescription (#6856 route D) (#7031)
* fix(spec): one house sentence for every os-migrate-meta prescription (#6856 route D)
All 41 tombstone prescriptions whose sentence spelled the migrate step as
'to rewrite it automatically.' / 'to remove it.' / 'to remove it
automatically.' / 'rewrites it for you.' converge on the ruled house form:
Run `os migrate meta --from 16` to rewrite existing sources automatically.
The ambiguity was in the verb's OBJECT: "rewrite it" has two antecedents
(the key vs your sources), and over the 44 strip-backed sites the wrong
reading promises a value conversion that never happens. The key's fate
stays in the body prose ("Delete the key…", "Rename the key to…").
- MIXED conversions keep the two-clause form naming which part converts:
dashboard compareTo.offset (unchanged, the model) and script
config.actionType (now "rewrite the shorthand case into `config.function`
automatically; the stub and marker values are removed", matching
removeScriptBranchKeys).
- agent.tools (the one strip site whose body never said "delete") now
states the migration DELETES the key — not a rename — per the
agent-tools-to-skills registry docblock.
- House rule recorded in shared/retired-key.ts's docblock (+ the @example
now carries the sentence), pinned class-wide by
retired-key-migrate-sentence.test.ts: source scan, house-or-MIXED by
shape, no per-site exemptions; RED on planted "rewrite it", on an
emptied sentence, and on a buried (non-final) sentence.
- The #6854 aria pin (toMatch(/to remove it/)) re-swept to the house
sentence — the disclosed, accepted cost of the ruling.
- content/docs/references/** regenerated (13 pages). authorable-surface,
json-schema manifest, api-surface, upgrade guide: no diff (acceptance is
byte-identical; the guide projects the untouched migrations ledger).
Fixes#6856
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018ffcE95NaMJcL9XJ9VDYgk
* docs(spec): align the connector module-blurb migrate sentence with the house verb (PM-directed)
PM-directed amendment on PR #7031: integration/connector.zod.ts's module
docblock said "`os migrate meta --from 16` rewrites it." over
field-mapping-transform-removed — a pure STRIP — the same misleading-verb
defect the #6856 ruling kills, spelled as comment prose. Now: "rewrites
existing sources automatically — the key itself is removed."
ui/action.zod.ts:422 ("rewrites it for you") deliberately unchanged: it
sits over action-execute-to-target, a genuine rename, so the prose is
accurate. The criterion is "misleading over a strip", not comment
uniformity.
Measured against the PM's zero-regen expectation: the module docblock IS a
render input (build-docs lifts the first JSDoc as the reference page's
blurb), so check:docs went stale and connector.mdx re-rendered — one page.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018ffcE95NaMJcL9XJ9VDYgk
---------
Co-authored-by: Claude <noreply@anthropic.com>
One house sentence for every `os migrate meta` prescription (#6856, maintainer-ruled route D). The 41 tombstone prescriptions that closed with `to rewrite it automatically.` / `to remove it.` / `to remove it automatically.` / `rewrites it for you.` now all close with exactly `Run 'os migrate meta --from 16' to rewrite existing sources automatically.` — the sentence states a property of the tool (it rewrites your source files), never the fate of the key, whose disposition stays in the body prose ("Delete the key…", "Rename the key to…"). The two partial conversions keep the precise two-clause form naming which part of the value converts (`dashboard.widgets[].compareTo.offset`, script `config.actionType`). The `agent.tools` prescription additionally states that the migration DELETES the key — not a rename — and each listed tool must be re-declared in a skill by hand. The rule is recorded in `shared/retired-key.ts`'s docblock and pinned class-wide by `retired-key-migrate-sentence.test.ts`. Acceptance is byte-identical everywhere: `retiredKey()` rejects with the guidance string regardless of its wording.
|**skills**|`string[]`| optional | Skill names to attach (Agent→Skill→Tool architecture) |
56
-
|**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. |
56
+
|**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. This is NOT a rename — there is no key the value moves to: the migration DELETES the key and emits a notice naming each tool that was listed, and you re-declare each one in a skill by hand. Run `os migrate meta --from 16` to rewrite existing sources automatically. |
57
57
|**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. |
58
58
|**active**|`boolean`| ✅ ||
59
59
|**access**|`string[]`| optional | Who can chat with this agent |
Copy file name to clipboardExpand all lines: content/docs/references/automation/control-flow.mdx
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -149,7 +149,7 @@ const result = FlowRegionSchema.parse(data);
149
149
|**backoffMultiplier**|`number`| ✅ | Exponential backoff multiplier; 1 (the default) keeps the delay flat |
150
150
|**maxRetryDelayMs**|`integer`| ✅ | Ceiling for a single backoff delay (ms) |
151
151
|**jitter**|`boolean`| ✅ | Randomize each delay within [50%, 100%] of its computed value — spreads a thundering herd of simultaneous retries |
152
-
|**retryDelayMs**|`never`| optional |[REMOVED]`retryDelayMs` was removed in @objectstack/spec 17.0.0 (#4661, #4964) — the retry policy now has ONE spelling for its base delay across every surface that carries it: `job.retryPolicy`, a `try_catch` node's `retry` and `flow.errorHandling`. Rename the key to `backoffMs`; the value (milliseconds before the first retry) is unchanged. `os migrate meta --from 16`rewrites it for you. |
152
+
|**retryDelayMs**|`never`| optional |[REMOVED]`retryDelayMs` was removed in @objectstack/spec 17.0.0 (#4661, #4964) — the retry policy now has ONE spelling for its base delay across every surface that carries it: `job.retryPolicy`, a `try_catch` node's `retry` and `flow.errorHandling`. Rename the key to `backoffMs`; the value (milliseconds before the first retry) is unchanged. Run `os migrate meta --from 16`to rewrite existing sources automatically. |
Copy file name to clipboardExpand all lines: content/docs/references/automation/schemaless-node-config.mdx
+5-5Lines changed: 5 additions & 5 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -150,11 +150,11 @@ const result = DecisionConditionSchema.parse(data);
150
150
|**function**|`string`| ✅ | Registered function to call (defineStack(`{ functions }`)). Contractually pure — it returns a value a later declarative node persists |
151
151
|**inputs**|`Record<string, any>`| optional | Inputs passed to the function (values interpolate `{token}` templates) |
152
152
|**outputVariable**|`string`| optional | Flow variable the function's return value is bound to |
153
-
|**actionType**|`never`| optional |[REMOVED]`script.config.actionType` was removed in @objectstack/spec 17 (#4343) — none of its values did what it said. The two built-ins were logger-backed stubs that recorded the intent and delivered nothing under any configuration, and every other value was a second spelling of `config.function`. Replace it per branch: for `email` use a `notify` node (it delivers through the messaging service — the in-app inbox by default, real email once `@objectstack/plugin-email` is installed); for `slack` use a `connector_action` node with the Slack connector, or an `http` node posting to a webhook; for anything else, move the name into `config.function`. Run `os migrate meta --from 16` to rewrite it automatically. |
154
-
|**template**|`never`| optional |[REMOVED]`script.config.template` was removed in @objectstack/spec 17 (#4343) — it fed only the logger-backed `email`/`slack` stubs, which never rendered or sent a message, so no template id was ever resolved. Delete the key. A `notify` node carries its own `title`/`message`, and stored templates live in the messaging service (`sys_notification_template`), not on the node. Run `os migrate meta --from 16` to rewrite it automatically. |
155
-
|**recipients**|`never`| optional |[REMOVED]`script.config.recipients` was removed in @objectstack/spec 17 (#4343) — the addresses were logged, never messaged: the `email`/`slack` branches it fed delivered nothing. Use a `notify` node, whose `recipients` (user ids, field refs or addresses) reach the messaging service for real. Run `os migrate meta --from 16` to rewrite it automatically. |
156
-
|**variables**|`never`| optional |[REMOVED]`script.config.variables` was removed in @objectstack/spec 17 (#4343) — it injected values into a template no side effect ever rendered. Delete the key. A `notify` node carries structured data in `payload`; a registered function takes it in `config.inputs`. Run `os migrate meta --from 16` to rewrite it automatically. |
157
-
|**script**|`never`| optional |[REMOVED]`script.config.script` was removed in @objectstack/spec 17 (#4343) — the built-in runtime has no server-side JS sandbox, so an inline body was recognized and never executed: the node warned and completed as a no-op. Move the logic into a registered function (`defineStack({ functions })`) and name it in `config.function`. Run `os migrate meta --from 16` to rewrite it automatically. |
153
+
|**actionType**|`never`| optional |[REMOVED]`script.config.actionType` was removed in @objectstack/spec 17 (#4343) — none of its values did what it said. The two built-ins were logger-backed stubs that recorded the intent and delivered nothing under any configuration, and every other value was a second spelling of `config.function`. Replace it per branch: for `email` use a `notify` node (it delivers through the messaging service — the in-app inbox by default, real email once `@objectstack/plugin-email` is installed); for `slack` use a `connector_action` node with the Slack connector, or an `http` node posting to a webhook; for anything else, move the name into `config.function`. Run `os migrate meta --from 16` to rewrite the shorthand case into `config.function`automatically; the stub and marker values are removed. |
154
+
|**template**|`never`| optional |[REMOVED]`script.config.template` was removed in @objectstack/spec 17 (#4343) — it fed only the logger-backed `email`/`slack` stubs, which never rendered or sent a message, so no template id was ever resolved. Delete the key. A `notify` node carries its own `title`/`message`, and stored templates live in the messaging service (`sys_notification_template`), not on the node. Run `os migrate meta --from 16` to rewrite existing sources automatically. |
155
+
|**recipients**|`never`| optional |[REMOVED]`script.config.recipients` was removed in @objectstack/spec 17 (#4343) — the addresses were logged, never messaged: the `email`/`slack` branches it fed delivered nothing. Use a `notify` node, whose `recipients` (user ids, field refs or addresses) reach the messaging service for real. Run `os migrate meta --from 16` to rewrite existing sources automatically. |
156
+
|**variables**|`never`| optional |[REMOVED]`script.config.variables` was removed in @objectstack/spec 17 (#4343) — it injected values into a template no side effect ever rendered. Delete the key. A `notify` node carries structured data in `payload`; a registered function takes it in `config.inputs`. Run `os migrate meta --from 16` to rewrite existing sources automatically. |
157
+
|**script**|`never`| optional |[REMOVED]`script.config.script` was removed in @objectstack/spec 17 (#4343) — the built-in runtime has no server-side JS sandbox, so an inline body was recognized and never executed: the node warned and completed as a no-op. Move the logic into a registered function (`defineStack({ functions })`) and name it in `config.function`. Run `os migrate meta --from 16` to rewrite existing sources automatically. |
Copy file name to clipboardExpand all lines: content/docs/references/data/external-lookup.mdx
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -67,7 +67,7 @@ const result = ExternalDataSourceSchema.parse(data);
67
67
| :--- | :--- | :--- | :--- |
68
68
|**source**|`string`| ✅ | Source field name |
69
69
|**target**|`string`| ✅ | Target field name |
70
-
|**transform**|`never`| optional |[REMOVED]`FieldMapping.transform` — authored as `connector.fieldMappings[].transform` and `externalLookup.fieldMappings[].transform` — was removed in @objectstack/spec 17.0.0 (#5552, ADR-0049), and the whole `FieldMappingTransform` union went with it (`constant` / `cast` / `lookup` / `javascript` / `map`) — no runtime ever executed any of the five, and the `javascript` member advertised `dialect: "js"`, a dialect retired in #3278. Delete the key. The transform pipeline that IS enforced is the import mapping's: `mapping.fieldMapping[].transform` (a string enum — `none`/`constant`/`map`/`split`/`join`/`lookup` — with its settings in `params`), applied by the REST import path, which rejects `javascript` with a 400 rather than pretending to run it. Run `os migrate meta --from 16` to rewrite it automatically. |
70
+
|**transform**|`never`| optional |[REMOVED]`FieldMapping.transform` — authored as `connector.fieldMappings[].transform` and `externalLookup.fieldMappings[].transform` — was removed in @objectstack/spec 17.0.0 (#5552, ADR-0049), and the whole `FieldMappingTransform` union went with it (`constant` / `cast` / `lookup` / `javascript` / `map`) — no runtime ever executed any of the five, and the `javascript` member advertised `dialect: "js"`, a dialect retired in #3278. Delete the key. The transform pipeline that IS enforced is the import mapping's: `mapping.fieldMapping[].transform` (a string enum — `none`/`constant`/`map`/`split`/`join`/`lookup` — with its settings in `params`), applied by the REST import path, which rejects `javascript` with a 400 rather than pretending to run it. Run `os migrate meta --from 16` to rewrite existing sources automatically. |
71
71
|**defaultValue**|`any`| optional | Default if source is null/undefined |
Copy file name to clipboardExpand all lines: content/docs/references/data/field.mdx
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -119,7 +119,7 @@ const result = AddressSchema.parse(data);
119
119
|**visibleWhen**|`string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }`| optional | Predicate (CEL) — field is shown only when TRUE (else hidden). e.g. P`record.type == 'invoice'`|
120
120
|**readonlyWhen**|`string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }`| optional | Predicate (CEL) — field is read-only when TRUE. e.g. P`record.status == 'paid'`|
121
121
|**requiredWhen**|`string \| { dialect: Enum<'cel' \| 'cron' \| 'template'>; source?: string; ast?: any; meta?: object }`| optional | Predicate (CEL) — field is required when TRUE. The only slot; the `conditionalRequired` alias was removed in protocol 17 (#3855). |
122
-
|**conditionalRequired**|`never`| optional |[REMOVED]`conditionalRequired` was removed in @objectstack/spec 17 (#3855) — use `requiredWhen`. Rename the key; the value (a CEL predicate) is unchanged. Run `os migrate meta --from 16` to rewrite it automatically. |
122
+
|**conditionalRequired**|`never`| optional |[REMOVED]`conditionalRequired` was removed in @objectstack/spec 17 (#3855) — use `requiredWhen`. Rename the key; the value (a CEL predicate) is unchanged. Run `os migrate meta --from 16` to rewrite existing sources automatically. |
123
123
|**widget**|`string`| optional | Form widget override — names a registered field component (resolved as `field:<widget>`) to render this field instead of the `type` default. Degrades to the `type` renderer when unregistered. e.g. "object-ref", "filter-condition", "recipient-picker". |
124
124
|**hidden**|`boolean`| optional | Hidden from default UI |
125
125
|**readonly**|`boolean`| optional | Read-only — never editable in forms, AND server-enforced on BOTH write paths: a non-system write to this field is silently dropped from the payload on UPDATE (#2948/#3003) and on INSERT (#3043; a create can no longer directly seed e.g. `approval_status: "approved"`), symmetric with `readonlyWhen`. A stripped INSERT field still falls back to its `defaultValue`. Exempt from the strip on BOTH paths: `isSystem` writes (seed replay, migration). Exempt on the UPDATE path ONLY: an opt-in "historical" import (`preserveAudit`, #3493) — which admits a whitelist (the audit/timestamp family plus author-declared business `readonly` fields). On INSERT the exemption does NOT apply (#6640): a non-system create that requests `preserveAudit` still has its readonly fields stripped, and is warned loudly that the exemption is UPDATE-only — replaying archival readonly facts on create requires a system context. A normal (non-system) import is NOT system-context and still strips. |
Copy file name to clipboardExpand all lines: content/docs/references/data/object.mdx
+2-2Lines changed: 2 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -68,8 +68,8 @@ const result = ApiMethod.parse(data);
68
68
|**name**|`string`| optional | Index name (auto-generated if not provided) |
69
69
|**fields**|`string[]`| ✅ | Fields included in the index |
70
70
|**unique**|`boolean \| 'global' \| 'organization'`| ✅ | Whether the index enforces uniqueness, and at which scope (ADR-0120). 'global' = materialized over exactly `fields`, no organization column injected — one holder across the whole installation; 'organization' = the driver prepends the NULL-safe organization key part (COALESCE(organization_id, '__global__')) at registration — one holder per organization; bare true = deprecated positional spelling of 'global' (warned in 17.x by lint unique/unscoped-declared-index, rejected at protocol 18, #5082) — state the scope. 'tenant'/'org' are rejected — the word is 'organization' |
71
-
|**type**|`never`| optional |[REMOVED]`indexes[].type` was removed in @objectstack/spec 17.0.0 (#5248, ADR-0049) — no driver ever read it. `SqlDriver.syncDeclaredIndexes` creates every declared index through knex's `table.index()` / `table.unique()`, which cannot express an access method, so the value changed no DDL; its `.default('btree')` merely made an inert knob show up in every parse output. Delete the key. The index method is the driver/dialect's decision (Postgres defaults to B-tree; `gin`/`gist`/`fulltext` are dialect-specific and are chosen by a database-layer migration when a workload actually needs one). Run `os migrate meta --from 16` to rewrite it automatically. |
72
-
|**partial**|`never`| optional |[REMOVED]`indexes[].partial` was removed in @objectstack/spec 17.0.0 (#5248, #4943, ADR-0049) — no driver ever emitted the `WHERE` clause, so a declared partial index was materialized as a FULL index and the predicate silently did nothing. Delete the key. Partial indexes are built at the database layer, not the declaration surface: issue `CREATE [UNIQUE] INDEX … WHERE <predicate>` from a runtime migration (this is what `metadata-protocol`'s `ensureOverlayIndex` already does for `sys_metadata`). Drift detection is unaffected — it reads partiality back from the database's own DDL, never from this key. Run `os migrate meta --from 16` to rewrite it automatically. |
71
+
|**type**|`never`| optional |[REMOVED]`indexes[].type` was removed in @objectstack/spec 17.0.0 (#5248, ADR-0049) — no driver ever read it. `SqlDriver.syncDeclaredIndexes` creates every declared index through knex's `table.index()` / `table.unique()`, which cannot express an access method, so the value changed no DDL; its `.default('btree')` merely made an inert knob show up in every parse output. Delete the key. The index method is the driver/dialect's decision (Postgres defaults to B-tree; `gin`/`gist`/`fulltext` are dialect-specific and are chosen by a database-layer migration when a workload actually needs one). Run `os migrate meta --from 16` to rewrite existing sources automatically. |
72
+
|**partial**|`never`| optional |[REMOVED]`indexes[].partial` was removed in @objectstack/spec 17.0.0 (#5248, #4943, ADR-0049) — no driver ever emitted the `WHERE` clause, so a declared partial index was materialized as a FULL index and the predicate silently did nothing. Delete the key. Partial indexes are built at the database layer, not the declaration surface: issue `CREATE [UNIQUE] INDEX … WHERE <predicate>` from a runtime migration (this is what `metadata-protocol`'s `ensureOverlayIndex` already does for `sys_metadata`). Drift detection is unaffected — it reads partiality back from the database's own DDL, never from this key. Run `os migrate meta --from 16` to rewrite existing sources automatically. |
0 commit comments