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
25 changes: 22 additions & 3 deletions packages/spec/src/automation/flow-text-slot-template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,32 @@
* dialect: a single-brace `{…}` token is refused, and the refusal names the
* `{{ }}` spelling of each token or, for a token no hole can spell, where to
* compute it. The contracts compose it (`NotifyConfigSchema`,
* `ScreenConfigSchema`, `EndConfigSchema`), and `AutomationEngine.registerFlow`
* and `objectstack validate` call it on every slot
* `HttpConfigSchema`, `ScreenConfigSchema`, `EndConfigSchema`), and
* `AutomationEngine.registerFlow` and the `validateStackExpressions` rule
* (`objectstack validate`, `objectstack compile`, the runtime publish gate of
* an `active` metadata save) call it on every slot
* {@link flowNodeTextSlotSources} finds — ⛔ never a second reading of the
* dialect anywhere. Those two doors then compile each slot's `{{ }}` holes
* dialect anywhere. Those doors then compile each slot's `{{ }}` holes
* (`validateExpression('template', …)`), which the spec cannot do itself: the
* template engine is a runtime.
*
* ## Not at `FlowSchema.parse`, but for an `end` node (#23320)
*
* The flow parse runs this judge on one slot: a top-level `end` node's
* `message`, because `end` has no executor, so its contract, `EndConfigSchema`,
* has no door but the parse (`parseEndNodeConfig` in `flow.zod.ts`). On every
* other text slot the parse leaves the token to the doors above, as it leaves
* a value-slot token to the value-slot judge's: `flowNodeConfigRefusals`'
* value arm stands aside at the paths this judge refuses
* (`slotJudgeRefusedPaths`, #23260), and the parse calls no slot judge of its
* own. The reasons are measured once, for both judges, in
* `flow-value-slot-template.ts` ("The doors that run it"): a refusal at the
* parse would replace `registerFlow`'s located line and `objectstack
* validate`'s `expression-invalid` finding with the parse's issue list, and
* would fail the row in the stored pass (`os migrate meta --stored`), which
* lists a text-slot refusal made after the parse as a TODO under
* `registerFlow` and leaves its verdict unmoved.
*
* ## Refused, not converted (ADR-0087 D2)
*
* D2 lets a conversion rewrite only what maps LOSSLESSLY. Every token spelling
Expand Down
44 changes: 44 additions & 0 deletions packages/spec/src/automation/flow-value-slot-template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,50 @@
* `AutomationEngine.registerFlow`, `objectstack validate` and the executors
* call it — ⛔ never a second reading of the dialect anywhere.
*
* ## The doors that run it — and why `FlowSchema.parse` is not one (#23320)
*
* The judge runs at every door that reports a flow's refusals per slot:
* `AutomationEngine.registerFlow`, after its parse; the `validateStackExpressions`
* rule (`expression-invalid` at `error`), which `objectstack validate`,
* `objectstack compile` and the runtime publish gate of an `active` metadata
* save run; and every run, through the node contracts that compose
* `FlowValueSlotSchema` and the `assignment` executor's own call. The flow
* parse does NOT run it: `FlowSchema.parse`, and so `defineStack`,
* `ObjectStackDefinitionSchema`, the `flow` metadata type's schema and an
* artifact's parse, accept a value-slot token. The parse
* parses the node contracts too (`flowNodeConfigRefusals`' value arm), and
* that arm stands aside at exactly the paths this judge refuses
* (`slotJudgeRefusedPaths`, #23260), so the parse leaves the token to the
* doors above, on purpose. Two measured reasons, taken by running this judge
* and the text-slot judge in `FlowSchema`'s refinement and reverting it:
*
* - **The located refusal.** `registerFlow` parses first, and a parse
* refusal reaches its caller as the parse's own issue list. A token
* refused at the parse would replace `registerFlow`'s located line — the
* node's id and type, the slot's label, the config path, the remedy and the
* `source:` — at registration and in the boot's log, and `objectstack
* validate` and `objectstack compile`, which parse the stack before their
* rules run, would stop at the parse before the `expression-invalid`
* finding. Those doors' pins of the located refusal went red.
* - **The stored pass.** The parse is also the stored-flow canonicalization
* (`AutomationEngine.canonicalizeStoredFlow`) that `os migrate meta
* --stored` runs on each flow row, and a parse refusal reports the row
* `failed`, which flips `storedMigrationClean`: a non-zero exit that no run
* of the command can clear, because no conversion rewrites the token (the
* next section). A refusal after the parse is listed instead, as a TODO —
* a value-slot site under the step-18 D3 entry
* `flow-value-slot-template-dialect-refused` (`conversions/refused-shapes.ts`),
* a text-slot site under `registerFlow` — and the verdict does not move.
* The pins that a braced slot parses and is listed went red.
*
* What this leaves: a stack built by `defineStack` and handed to none of those
* doors — `os dev` and `os serve` load it and boot — reaches `registerFlow` at
* boot, which refuses the flow with its located line and does not register it.
* The judge can join the parse once `registerFlow` reports a parse refusal as
* located as its own and the stored pass lists a slot the parse refuses
* rather than failing its row; the contract arm's exemption then keeps each
* token reported once.
*
* ## Refused, not converted (ADR-0087 D2)
*
* D2 lets a conversion rewrite only what maps LOSSLESSLY. Every token spelling
Expand Down
5 changes: 4 additions & 1 deletion packages/spec/src/automation/flow.zod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1503,7 +1503,10 @@ export const FlowSchema = lazySchema(() => strictObject(
// value carrying a `{token}` included since #23260 (`get_record`
// `limit: '{x}'`), never a region slot, a ledger predicate or value slot,
// a value or text slot whose own judge refuses its token, or `http`'s
// run-resolved `signingSecret`. A KEY its
// run-resolved `signingSecret`. (#23320) This parse runs neither slot
// judge itself, but for an `end` node's `message` through its contract:
// the doors that do, and the measured reasons the parse is not one, are
// in `flow-value-slot-template.ts` ("The doors that run it"). A KEY its
// contract does not declare is refused too (#21982), for every builtin
// `builtinNodeConfigKeysJudged` names (every one, `try_catch` included
// since #22343), a body-less legacy `loop` included. The one plugin node contract the spec declares, `approval`
Expand Down
Loading