Skip to content

Flow conditions over vars.* / get_record outputs have the same abort exposure #633 fixed for record.* — unmeasured #643

Description

@os-zhuang

Found while implementing #633, deliberately left out of its scope.

What #633 covered, and what it did not

#633 made every record.x / previous.x read in a record-change flow condition total, and pinned it with test/flow-condition-totality.test.ts. That sweep is explicitly filtered to the trigger record:

.filter((c) => /\b(record|previous)\./.test(c.source));

Conditions over flow-local variables were left alone, because they are a different shape with a different failure mode and measuring them properly is its own job. They are:

flow condition scope
opportunity_approval (+ _on_create) oppRecord.amount > 500000 (node check_high_value, edges e5/e6) get_record output
quote_generation oppRecord.stage == "prospecting" || … (node check_stage, edges e4a/e4b) get_record output
campaign_enrollment vars.campaignRecord.status == "planning" || … (node check_campaign_open, edge e4) get_record output
lead_conversion vars.matchedAccount != null, vars.matchedContact != null, vars.createOpportunity == true (+ edges e5/e6/e11/e12/e16/e17) flow variable
demo_bootstrap vars.firstUser != null (+ edges e2/e_nouser) flow variable

Why they may be exposed

The mechanism #633 measured applies unchanged — same AutomationEngine.evaluateCondition, same strict CEL, same consequence (evaluateCondition throws, execute records the run failed, the automation does not happen). Only the source of the sparse shape differs, and there are two distinct ones:

  1. get_record outputs are driver rows. A get_record node resolves through data.findOne, which on driver-memory / driver-mongodb returns the stored document with absent columns genuinely missing — exactly the shape that broke case_escalation. oppRecord.amount and oppRecord.stage are required on crm_opportunity today so they are always written, but campaign_enrollment's vars.campaignRecord.status has not been checked.

  2. Unset flow variables are a different failure. vars.matchedAccount != null reads a variable that a preceding node may never have assigned. Measured on the engine's evaluator, an unset key aborts the same way an absent record column does (No such key: matchedAccount) — but the remedy is not a has() guard, it is declaring the variable with a default so it is always bound. Guarding here would paper over a flow-graph defect rather than fix it.

What to establish

  1. For each get_record-backed condition: is every field it reads required or defaulted on the target object? If not, it has Flow start conditions carry no has() guards — measure whether the abort-and-skip class of #630 reaches them #633's exposure and wants Flow start conditions carry no has() guards — measure whether the abort-and-skip class of #630 reaches them #633's guards.
  2. For each vars.* condition: can the referenced variable be unbound on any path through the graph? Note lead_conversion and campaign_enrollment are screen flows and demo_bootstrap is a schedule flow, so none is reachable through the record-change trigger — the shapes reaching them are different and must be measured, not assumed.
  3. Decide whether test/flow-condition-totality.test.ts's sweep should widen to these scopes, or whether unset variables want a separate structural check ("every variable a condition reads is declared in flow.variables").

Do not inherit #633's conclusion here — that is the mistake #633 itself was opened to prevent. See the house-rule block at the top of test/flow-condition-totality.test.ts for the measured mechanism table, and test/sharing-seeding.test.ts for the surface where has() is actively wrong.

Filed unassigned.

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingmetadataDeclarative metadata — schema, security posture, UI surfacespm:dispatchedDispatched to a dev agent by /pm-dispatchpm:queueReady for the PM dispatch loopprio:p1Next in line once P0s clear

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions