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
{{ message }}
Repository navigation
Commit f6981bd
Browse filesBrowse the repository at this point in the historyBrowse files
docs(spec): the migration text offers a {token} only where http interpolates before its parse
The changeset's FROM -> TO rows and the D3 entry's replacement told an
author to write a {token} template in limit or maxIterations, but
get_record and loop parse their config as authored, so such a value
passes the build doors and fails every run. A number or boolean slot
outside http now takes a literal only, http's slots keep the sole-token
form, and the "still accepted" token bullet says the hold-back is no
promise the value runs. The step-18 fragment says the same; the
registry region is regenerated.
Claude-Session: https://claude.ai/code/session_01T9u38rswFp5Rw8DswRUReJ
Co-authored-by: Claude <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: .changeset/21898-flow-builtin-node-config-values-refused.md
+7-6Lines changed: 7 additions & 6 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -14,10 +14,10 @@ Clause-②: yes (narrowing)
14
14
15
15
**What is refused.** A node of any builtin type (`get_record`, `create_record`, `update_record`, `delete_record`, `notify`, `http`, `screen`, `script`, `subflow`, `map`, `loop`, `parallel`, `try_catch`), at any depth, whose present config value its executor contract refuses — a wrong type, a value outside the declared set or range, an empty `function` / `flowName`, or a rule finding on present keys (a `notify` `template` beside an inline `title`). The refusal is the existing closed-set code `node-config-refused-by-contract`, `params: { nodeType, key }`, anchored at the key (`nodes.N.config.outputVariable`, `nodes.N.config.fields.0.min`), from the one judge `flowNodeConfigRefusals` that `FlowSchema.parse`, `AutomationEngine.registerFlow` (which parses first) and `objectstack validate` share. The issue's `code` is `custom`. That covers `FlowSchema`, `defineFlow()`, `defineStack` (`STACK_SCHEMA_INVALID`, 422, at `flows.N.nodes.M.config.<key>`), `os validate`, `os compile`, an artifact's parse, `registerFlow` and the metadata save door (`422 INVALID_METADATA`).
16
16
17
-
**What stays accepted, byte for byte.** Every value its contract accepts, and the values the build cannot know the run will parse:
17
+
**What the build doors still accept, byte for byte.** Every value its contract accepts, and the values this arm holds back:
18
18
19
-
- a value carrying a `{token}` (also spelled with double braces or a leading `$`) — never refused for its pre-interpolation type;
20
-
- on `http`, which parses after interpolating its whole config, any value with a token inside it, and `signingSecret` (the credential channel may supply it);
19
+
- a value carrying a `{token}` (also spelled with double braces or a leading `$`) — never refused at the build doors for its pre-interpolation type. That is not a promise it runs: only `http` interpolates its config before it parses, so only an `http` slot sees the token's resolved value. Every other builtin parses its config as authored, so a token in one of its number or boolean slots (`limit: '{n}'`, `maxIterations: '{cap}'`, a screen field `min: '{m}'`, `multi: '{bulk}'`) still fails at its first run, exactly as before — write a literal there;
20
+
- on `http`, any value with a token inside it, and `signingSecret` (the credential channel may supply it);
21
21
- a `loop` with no `body` (its executor does not parse it), and the region slots of `loop`, `parallel` and `try_catch`;
22
22
- an undeclared or retired key, a screen field's `visibleWhen` and a CRUD `fields` value — each keeps the judge it had.
23
23
@@ -27,14 +27,15 @@ Clause-②: yes (narrowing)
27
27
|:--|:--|
28
28
|`outputVariable: 42`|`outputVariable: 'taskId'` — the variable's name |
29
29
| a screen field `min: '1'`, `max: '10'`|`min: 1`, `max: 10`|
30
-
|`limit: '10'`, `timeoutMs: '5000'`, `maxIterations: '5'`|`limit: 10`, `timeoutMs: 5000`, `maxIterations: 5` — or a `{token}` template computed per run |
31
-
|`multi: 'true'`, `durable: 'yes'`, a screen field `required: 'yes'`|`multi: true`, `durable: true`, `required: true`|
30
+
|`limit: '10'`, `maxIterations: '5'` (any number slot outside `http`) |`limit: 10`, `maxIterations: 5` — a literal number only: these executors parse the config as authored, so a `{token}` here passes the build and fails every run |
31
+
|`multi: 'true'`, a screen field `required: 'yes'` (any boolean slot outside `http`) |`multi: true`, `required: true` — a literal boolean only, for the same reason |
32
+
|`http``timeoutMs: '5000'`, `durable: 'yes'`|`timeoutMs: 5000`, `durable: true` — or, on `http` alone, a sole-token template such as `timeoutMs: '{timeout}'`: `http` interpolates before it parses, so the token resolves to its value's type first |
32
33
|`severity: 'loud'`, `mode: 'view'`| one of the declared values (`'info'` / `'warning'` / `'critical'`; `'create'` / `'edit'`) |
33
34
| a `notify` with both `template` and `title`| one content path, as the refusal's sentence says |
34
35
35
36
**The one-line fix: write the value the contract declares at the key the refusal names.** The runtime never ran such a node, so the fix changes nothing a working flow does.
36
37
37
-
**Who is affected, measured.** At `833d57c9cf`, every builtin node `config` authored in this repository's examples, docs, skills and `packages/qa` fixtures (96 nodes), and every one in hotcrm at `4054ec2680` (138 nodes), parses under this arm. The one real writer found to store a refused value is the Studio flow designer, which saved a screen field's Min / Max as strings until objectui `5ba255538a`. Deployed metadata, and other repositories, were not measured. Where such a node already sits in a stored flow, the whole flow is refused at registration: at boot it is skipped with a warn naming it, its trigger not armed, while the flows beside it register.
38
+
**Who is affected, measured.** At `833d57c9cf`, every builtin node `config` authored in this repository's examples, docs, skills and `packages/qa` fixtures (96 nodes), and every one in hotcrm at `4054ec2680` (138 nodes), parses under this arm. A second census at `d1c7d8d392` that also reads helper calls, same-file constants and assignments into a node config (1065 configs in this repository, 138 in hotcrm) found no other real writer; 64 configs here take a value from an import, a call or a spread that no static reading evaluates, and are not counted either way. The one real writer found to store a refused value is the Studio flow designer, which saved a screen field's Min / Max as strings until objectui `5ba255538a`. Deployed metadata, and other repositories, were not measured. Where such a node already sits in a stored flow, the whole flow is refused at registration: at boot it is skipped with a warn naming it, its trigger not armed, while the flows beside it register.
0 commit comments