Skip to content

lint: flow-decision-unconditional-branch skips the decision that gates on NOTHING — if (gated.length === 0) continue makes the fully-inert gateway the one case the rule cannot see #16093

Description

@os-steve

Measured on the pinned @objectstack/lint@17.3.0 artifact as installed by objectstack-ai/hotcrm, ⛔ not on the platform source tree. Filed unassigned by the hotcrm step-3 rule survey (hotcrm#1613); the local assertion that currently covers this stays in place there.

The gap

lintFlowPatterns reports flow-decision-unconditional-branch only when a decision has BOTH gated and ungated out-edges. The scan is guarded by:

const gated = outs.filter((e) => e.condition || e.isDefault === true);
if (gated.length === 0) continue;

So the strictly worse shape — a decision whose out-edges carry no condition and no isDefault, and whose node declares no config.conditions[] — falls out of the loop before any finding is pushed. That decision branches on nothing: every successor runs on every pass, and the gateway is decoration.

The mixed case (some edges guarded, some not) is at least partially routed. The fully-inert case is not routed at all, and it is the one a reader is least likely to notice, because the node still says type: 'decision'.

Measurement, with a working control

One committed tree (hotcrm at a0362a37), one injection at a time, restored by blob hash after each.

injected defect at opportunity_stagnation.check_not_nudged pnpm lint result
add config.condition to the decision node flow-inert-node-condition warning, exit 0
add an unconditional out-edge beside the guarded one flow-decision-unconditional-branch warning, exit 0
add two isDefault: true out-edges flow-multiple-default-edges warning, exit 0
mark the guarded edge isDefault: true as well flow-default-edge-with-condition error, exit 1
remove the condition from the only out-edge (decision now gates on nothing) nothing fires — 0 errors, 17 warnings, exit 0, byte-identical to the clean baseline

The first four rows are the control: the same command, the same flow, the same run design, all four naming their rule id. The fifth is therefore a reading about the rule, not about the harness.

The clean-tree baseline for that repo is 0 error(s), 17 warning(s), 12 suggestion(s), exit 0.

Site

objectstack-ai/hotcrmsrc/flows/opportunity-stagnation.flow.ts, decision check_not_nudged inside loop_opps. Region descent is NOT the problem here: flow-inert-node-condition fired on that same nested node, so the walk reaches it (the #5383 class is fixed on this pin).

Suggested direction, not a prescription

Either drop the gated.length === 0 short-circuit and report an all-unconditional decision under the same rule id, or give it its own id. A decision with exactly one unconditional out-edge and no conditions[] is indistinguishable from a noop node in behaviour, so a distinct message may read better than folding it into the mixed-branch wording.

Related, and why this is not a duplicate

Refs: hotcrm#1613 · hotcrm#1582 (the family card whose deletion half depends on this rule)

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

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions