|
2199 | 2199 | "toMajor": 18, |
2200 | 2200 | "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." |
2201 | 2201 | }, |
| 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 | + }, |
2202 | 2209 | { |
2203 | 2210 | "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", |
2204 | 2211 | "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 | 5774 | "toMajor": 18, |
5768 | 5775 | "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." |
5769 | 5776 | }, |
| 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 | + }, |
5770 | 5784 | { |
5771 | 5785 | "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", |
5772 | 5786 | "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