Skip to content

Commit b8a3e2a

Browse files
committed
chore(spec): regenerate spec-changes.json and the upgrade guide after merging main at 3ca71b6 (step 18: 64 conversions, 327 semantic entries)
main added one step-18 semantic entry since bf492c8: flow-text-slot-single-brace-refused (#22315). At protocol 18 both generators project every step-18 entry, so both documents gain it, and the guide's 17 -> 18 intro paragraph carries its summary. The conversion ids are unchanged. Claude-Session: https://claude.ai/code/session_01VZqqwTj2wsihZEbfT6yyYN Co-authored-by: Claude <noreply@anthropic.com>
1 parent a46840e commit b8a3e2a

2 files changed

Lines changed: 18 additions & 1 deletion

File tree

‎docs/protocol-upgrade-guide.md‎

Lines changed: 4 additions & 1 deletion
Large diffs are not rendered by default.

‎packages/spec/spec-changes.json‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2199,6 +2199,13 @@
21992199
"toMajor": 18,
22002200
"rationale": "The `script` and `subflow` executors (`service-automation` `builtin/screen-nodes.ts`, `builtin/subflow-node.ts`) parse the node's `config` against a strict contract (`ScriptConfigSchema`, `SubflowConfigSchema`) before they act, and refuse the node on an undeclared key. No door before the run judged one: `registerFlow`'s undeclared-key check derives the declared set from the node type descriptor's `configSchema`, and these two descriptors publish none (the schemaless class, `SCHEMALESS_NODE_CONFIG_SCHEMAS`), while the build doors' executor-contract arm judged required keys and present values but held key membership back on the premise that registration judges it. So a `script` node carrying `bogusKey` passed `FlowSchema.parse`, `objectstack validate` and `objectstack compile` (which copied the key into the artifact), registered, and then failed every run that reached the node: the config is metadata, and no rerun could succeed. The one judge `FlowSchema.parse`, `AutomationEngine.registerFlow` (which parses first) and `objectstack validate` share (`flowNodeConfigRefusals`) now refuses such a key on these two types as `node-config-refused-by-contract`, anchored at the key, one refusal per key, in the contract's own words — the code the value half and the approval contract already use. Every other builtin keeps its undeclared keys where they were judged: at registration, against its descriptor, with that check's own prescriptions. `decision` is schemaless too, but its executor parses no contract, so an undeclared key there fails no run and stays unjudged. A retired `script` key keeps its tombstone path. ⚠️ A spelling the ADR-0087 D2 conversion `flow-node-script-config-aliases` or `flow-node-subflow-flow-alias` still rewrites at load (`functionName`, `input` on a `script`; `flow` on a `subflow`) is converted before the judge at every door that converts first; met by a direct `FlowSchema.parse` or `defineFlow()` it is refused like any other undeclared key, as the missing canonical key already was. ⚠️ No D2 conversion: the platform cannot know what an undeclared key was meant to be. ⚠️ Where such a node already sits the whole flow is refused: registered from the metadata registry or `sys_metadata` at boot it is skipped with a `warn` naming it, its trigger not armed, while the flows beside it register; a `defineStack({ flows })` source throws `StackSchemaInvalidError` for the whole stack; an artifact file is refused whole at load. ADR-0087, ADR-0031."
22012201
},
2202+
{
2203+
"surface": "flows[].nodes[].config of a notify node (title, message), a screen node (title, description) and an end node (message) — a string, or the source of a template envelope, carrying a single-brace template token",
2204+
"replacement": "a double-brace template hole, rendered by the formula template engine over the flow's variables: a variable path with an optional formatter, {{ record.name }}, {{ $error.message }}, {{ rows.0.subject }}, {{ record.amount | currency }}. A token no hole can spell is computed into a variable first, with an assignment node — arithmetic and functions as a CEL value envelope, the date macros and the run-user paths as the value-slot spelling that still reads them — and written as {{ variable }}",
2205+
"migrationId": "flow-text-slot-single-brace-refused",
2206+
"toMajor": 18,
2207+
"rationale": "ADR-0032 Decision 3 fixes one template delimiter, double braces, and deletes the single brace: it collides with CEL map literals, and an author who meets both dialects in one flow mixes them. The 17.x interpolator and the template engine render the same text for a path holding a string, a number, a boolean, null, an absent key or variable, an ISO date string, an object or an array, but not for every value — a Date rendered JSON-quoted under the interpolator and as its ISO text under the engine, and a screen title, screen description or end message that was one token holding an object, an array or a Date rendered String(value) — so no conversion is lossless (ADR-0087 D2) and none is applied. Arithmetic, function calls, the date macros and the run-user paths have no hole spelling: a hole is a path with a formatter, never logic. A flow carrying a single-brace token in a text slot is refused at registration, by objectstack validate and by the node contract; a stored flow carrying one is skipped at boot with a warn naming it."
2208+
},
22022209
{
22032210
"surface": "the record and previous roots a record-change flow receives — a password or secret field, and an internal field, of the triggering record, on every object",
22042211
"replacement": "read a credential through a privileged binder — the flow credential channel for an http node's signing secret, or a privileged server-side read such as the engine's resolveSecretField — never off `record` or `previous`; on those roots a set credential-class field now reads as the mask `SECRET_MASK`, an unset one as null, and an `internal: true` field is absent",
@@ -5767,6 +5774,13 @@
57675774
"toMajor": 18,
57685775
"rationale": "The `script` and `subflow` executors (`service-automation` `builtin/screen-nodes.ts`, `builtin/subflow-node.ts`) parse the node's `config` against a strict contract (`ScriptConfigSchema`, `SubflowConfigSchema`) before they act, and refuse the node on an undeclared key. No door before the run judged one: `registerFlow`'s undeclared-key check derives the declared set from the node type descriptor's `configSchema`, and these two descriptors publish none (the schemaless class, `SCHEMALESS_NODE_CONFIG_SCHEMAS`), while the build doors' executor-contract arm judged required keys and present values but held key membership back on the premise that registration judges it. So a `script` node carrying `bogusKey` passed `FlowSchema.parse`, `objectstack validate` and `objectstack compile` (which copied the key into the artifact), registered, and then failed every run that reached the node: the config is metadata, and no rerun could succeed. The one judge `FlowSchema.parse`, `AutomationEngine.registerFlow` (which parses first) and `objectstack validate` share (`flowNodeConfigRefusals`) now refuses such a key on these two types as `node-config-refused-by-contract`, anchored at the key, one refusal per key, in the contract's own words — the code the value half and the approval contract already use. Every other builtin keeps its undeclared keys where they were judged: at registration, against its descriptor, with that check's own prescriptions. `decision` is schemaless too, but its executor parses no contract, so an undeclared key there fails no run and stays unjudged. A retired `script` key keeps its tombstone path. ⚠️ A spelling the ADR-0087 D2 conversion `flow-node-script-config-aliases` or `flow-node-subflow-flow-alias` still rewrites at load (`functionName`, `input` on a `script`; `flow` on a `subflow`) is converted before the judge at every door that converts first; met by a direct `FlowSchema.parse` or `defineFlow()` it is refused like any other undeclared key, as the missing canonical key already was. ⚠️ No D2 conversion: the platform cannot know what an undeclared key was meant to be. ⚠️ Where such a node already sits the whole flow is refused: registered from the metadata registry or `sys_metadata` at boot it is skipped with a `warn` naming it, its trigger not armed, while the flows beside it register; a `defineStack({ flows })` source throws `StackSchemaInvalidError` for the whole stack; an artifact file is refused whole at load. ADR-0087, ADR-0031."
57695776
},
5777+
{
5778+
"surface": "flows[].nodes[].config of a notify node (title, message), a screen node (title, description) and an end node (message) — a string, or the source of a template envelope, carrying a single-brace template token",
5779+
"replacement": "a double-brace template hole, rendered by the formula template engine over the flow's variables: a variable path with an optional formatter, {{ record.name }}, {{ $error.message }}, {{ rows.0.subject }}, {{ record.amount | currency }}. A token no hole can spell is computed into a variable first, with an assignment node — arithmetic and functions as a CEL value envelope, the date macros and the run-user paths as the value-slot spelling that still reads them — and written as {{ variable }}",
5780+
"migrationId": "flow-text-slot-single-brace-refused",
5781+
"toMajor": 18,
5782+
"rationale": "ADR-0032 Decision 3 fixes one template delimiter, double braces, and deletes the single brace: it collides with CEL map literals, and an author who meets both dialects in one flow mixes them. The 17.x interpolator and the template engine render the same text for a path holding a string, a number, a boolean, null, an absent key or variable, an ISO date string, an object or an array, but not for every value — a Date rendered JSON-quoted under the interpolator and as its ISO text under the engine, and a screen title, screen description or end message that was one token holding an object, an array or a Date rendered String(value) — so no conversion is lossless (ADR-0087 D2) and none is applied. Arithmetic, function calls, the date macros and the run-user paths have no hole spelling: a hole is a path with a formatter, never logic. A flow carrying a single-brace token in a text slot is refused at registration, by objectstack validate and by the node contract; a stored flow carrying one is skipped at boot with a warn naming it."
5783+
},
57705784
{
57715785
"surface": "the record and previous roots a record-change flow receives — a password or secret field, and an internal field, of the triggering record, on every object",
57725786
"replacement": "read a credential through a privileged binder — the flow credential channel for an http node's signing secret, or a privileged server-side read such as the engine's resolveSecretField — never off `record` or `previous`; on those roots a set credential-class field now reads as the mask `SECRET_MASK`, an unset one as null, and an `internal: true` field is absent",

0 commit comments

Comments
 (0)