Skip to content

lint: flow-double-brace-interp / flow-bare-dollar-ref never reach an http node's request payload — the same stripRegions union that blinded the flow-template rules #16405

Description

@baozhoutao

Found while fixing card 16111 (the flow-template rules' warning half). Not fixed there: it changes the reach of two DIFFERENT rule ids, so it earns its own changeset line and its own review rather than riding along.

The defect

packages/lint/src/lint-flow-patterns.ts:1562 runs a recursive template-string scan over a region-stripped view of each node's config:

const strings: string[] = [];
collectTemplateStrings(stripRegions(node.config), undefined, strings);

stripRegions is called with ONE argument, so it strips the flat union of every config key that holds a region on any node type — body, try, catch, branches. But body is loop's region slot and the canonical request-payload key on an http node (packages/spec/src/automation/io-node-config.zod.ts says so in as many words: "body IS canonical on an http node"). So config.body is deleted from every node's view before this scan reads it, and the whole of an http node's request payload is invisible to:

  • flow-double-brace-interp — a {{ ... }} token in a payload. Flow node values use SINGLE braces; the double-brace form is the formula dialect and is never interpolated, so it ships to the endpoint as the literal text {{ ... }}.
  • flow-bare-dollar-ref — a bare $ref.field written as a literal in a payload, likewise never interpolated.

http-nodes.ts interpolates the raw config wholesale (interpolate(raw, variables, context)), so a payload is exactly where an uninterpolated token reaches a real outbound request.

The same union also hides a non-loop node's try / catch / branches keys, if any node type ever uses those names as ordinary config.

Why it is not the card-16111 fix

Card 16111 changed WalkedFlowNode.localConfig to strip only the slots the node's own type declares, and deliberately left stripRegions' DEFAULT argument as the flat union so this caller's behaviour did not change under it. That was scope control, not a verdict that the union is right here — it is the identical defect, one call site over.

The fix

node.type is already in scope at that call site (the line above builds nodeWhere from it):

collectTemplateStrings(stripRegions(node.config, ownRegionKeys(node.type)), undefined, strings);

ownRegionKeys is exported from ./flow-walk.js as of card 16111. Once this call site passes its own slots, the flat-union default has no callers left and should be removed, so the next caller cannot inherit the trap: make regionKeys a required parameter.

Expected blast radius

New findings only where a {{ }} or bare $ref.field sits in a previously-hidden key. Both rules' severities are unchanged. Worth measuring against the example apps the way card 16111 did — examples/app-showcase has http nodes with body payloads inside a parallel branch and a try_catch try.

Verification this needs

  • Pins on an http node payload carrying each of the two token shapes, at top level and nested in a region.
  • The double-count negative control: a token in a loop body must still be reported ONCE, on the node that carries it, not also against the container. That is what the strip exists for, and it is the direction that breaks if the fix over-corrects to stripping nothing.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions