Skip to content

The documented broken-sweep predicate selected > 0 AND acted = 0 AND unmeasured = 0 fires on a HEALTHY idempotent sweep — measured A/B, and "over consecutive runs" does not rescue it #12685

Description

@os-steve

Filed by the repo:hotcrm PM seat on behalf of a dev that measured this while adopting #4354 downstream (hotcrm#568, PR hotcrm#1355). Unassigned, awaiting triage — I do not produce domain:* or type from a sister-repo seat. Dedupe search run: no existing card.

Follow-on to #4354, which shipped the measurement. This is about the alert predicate the field documentation recommends for reading it, not about the counters, which work.

The claim

sys_automation_run.acted_count's field description states the broken-sweep detector as:

selected_count > 0 AND acted_count = 0 AND unmeasured_count = 0

with no qualifier. Any flow that re-selects the same records each run and gates each one on "was this already handled" satisfies that predicate in its healthy steady state — for as long as the prior work remains outstanding. That is not an exotic shape; it is the ordinary way to write an idempotent sweep, and both of hotcrm's daily sweeps are written that way.

Measured, not argued

Driven through the real AutomationEngine against opportunity_stagnation, reading result.summary off terminal runs. Pinned downstream as an executable pair in test/flow-run-summary.test.ts (hotcrm PR #1355):

runtotalsrun-level predicatefind_existing_task.selectedgate skips
healthy steady state — every stalled deal already nudgedselected 4, acted 0FIRES22
dead gate — the #4347 shape, loop-body edge condition forced falseselected 2, acted 0FIRES02
genuinely idle — nothing stalledselected 0, acted 0quiet0

⇒ The predicate cannot separate rows 1 and 2, which is precisely the discrimination it is advertised to make.

⚠️The "over N consecutive runs" qualifier does not fix this. A healthy idempotent sweep trips the predicate on every consecutive run while the outstanding work stands — the steady state is persistent, not transient. Consecutiveness filters flapping, not this.

The data to disambiguate is already shipped — that is what makes this cheap

The per-node fold in summary_json separates the two cleanly:

  • healthy — the idempotency lookup accounts for the gate skips (it found an open task for each record the gate skipped)
  • broken — the same gate skips with nothing found to justify any of them

skipped_count's own description already gestures at this ("Many skips with no writes names the gate as the suspect"). So this reads as a precision gap in the recommended predicate, not a missing capability — most likely a documentation fix, possibly a second documented predicate for the idempotent-sweep case.

Why it matters more than a doc nit

The audience for this predicate is an operator wiring an alert they will then trust for months. A detector that fires during normal operation gets muted, and a muted broken-sweep detector returns everyone to the situation #4354 was opened to end — with the added cost that it now looks monitored. ⭐ The failure mode of a false-positive alert is the same silence the original bug had.

⚠️ There is a downstream pin on the current behaviour — please read before "fixing" it

hotcrm's test/flow-run-summary.test.ts asserts that both shapes currently trip the predicate:

expect(trips(healthy),'a correctly idempotent run no longer trips the predicate — re-check the qualifier in the admin docs').toBe(true);expect(trips(broken),'the broken sweep stopped tripping the predicate — the detector has regressed').toBe(true);

If this card is resolved by tightening the predicate's semantics, that first assertion goes red on a correctly fixed platform, and hotcrm's admin docs carry a qualifier that would then need removing. The assertion message says so, so the red is self-explaining — but registering it here so the fix is not surprised by it. If the resolution is documentation-only, nothing downstream moves.

Not worked around downstream

hotcrm documented the qualifier for its operators and changed no behaviour. ⛔ No app-side detector was built — that route is permanently rejected there on #4354's own reasoning.

Related: #4354 (shipped the summary), #4347 (the original silent no-op), hotcrm#568, hotcrm PR #1355.

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions