Skip to content

Three pre-existing flow-lint fixtures bind the loop item with itemVar, a key LoopConfigSchema rejects — the nested-region tests describe a loop no author can write #5700

Description

@os-zhuang

Found while implementing #5633. Filed unassigned, observation-class (finding, no pm:queue): no shipped behaviour is wrong — this is test fixtures describing a container the schema rejects, so the coverage they advertise is weaker than it reads.

What

packages/lint/src/lint-flow-patterns.test.ts binds the loop item with itemVar in three pre-existing fixtures:

LoopConfigSchema (packages/spec/src/automation/control-flow.zod.ts) is a strictObject, and the declared key is iteratorVariable. itemVar is neither declared nor aliased, so it is reported as an unrecognized_key rather than dropped (#4001):

REJECTED  itemVar             unrecognized_keys=[["itemVar"]]     (the only issue)
PARSES    iteratorVariable    unrecognized_keys=[]
REJECTED  itemVariable        unrecognized_keys=[["itemVariable"]]  (declared alias -> guidance)

(itemVariable is deliberately registered as an alias so the edit-distance suggester cannot send an author to indexVariable — see the comment above LoopConfigSchema's aliases. itemVar is not in that set.)

Why the tests do not notice

Region collection reads config.body. collectFlowGraphs / regionSlotsOf never look at the item-binding key, so a fixture spelling itemVar descends and gets scanned exactly like a correct one — every assertion in those cases passes for the right reason about the rule, while the fixture describes a loop an author cannot write. Nothing in the suite parses these fixtures, so nothing goes red.

That is the same shape this very file already documents for a trigger descriptor — the #4966 note above the time-relative case, which added expect(TimeRelativeTriggerSchema.safeParse(...).success).toBe(true) precisely because "a fixture the runtime would refuse teaches the wrong shape to every reader of this file". This is that finding one container down.

Scope, and what is already fixed

Fixtures added by #5633 (PR #5695) are already correct: re-spelled to iteratorVariable, given the label that FlowNodeSchema requires on the body nodes, and pinned against LoopConfigSchema in both directions (fixture parses green; the near-miss spelling really is rejected, so the pin has teeth). That PR deliberately did not touch the three pre-existing fixtures — they are not exposed by its change, and re-spelling someone else's fixtures is scope creep on a build-gating PR.

So what remains is exactly those three call sites.

Fix

Mechanical: itemVar to iteratorVariable at the three sites, plus the label the body nodes need if the fixture is to parse fully, plus a LoopConfigSchema pin on the shared loopBodyFlow helper so the whole family cannot rot back. Worth checking in the same pass whether other container fixtures in the flow-lint tests spell undeclared keys on parallel branches or try_catch regions — the audit is one safeParse per fixture and is the durable half of the fix.

Note the flow-level label requirement is a separate and much broader convention in this file: essentially none of its fixtures are full-FlowSchema-green (FlowSchema requires label on the flow and on every node), because the suite passes hand-written raw literals rather than parsed stacks. Bringing the whole file to full-flow-parse-green is a different, larger decision and is not proposed here — the narrow claim is that a key a fixture writes should be a key the schema accepts.

Related

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions