Found by the standing correlation pass on the 2026-08-25 evening unattended run. Static review only — not yet observed in production data.
The defect
_call_fits_incident (app/internal/incident_correlator.py:1601) still computes idle with the signed_incident_idle_minutes. Commit 33a247d moved every correlation gate onto _idle_gate_minutes (:478), which is distance-based and cannot go negative. _call_fits_incident was missed.
Why the sign matters
recorrelation_sweep.py:104 anchors now to the call's own started_at, not wall clock. A back-dated call therefore sees idle_min < 0, and every comparison of the form idle_min >= N is silently false:
content-divergence veto at :1620 — skipped
content-divergence veto at :1632 (idle_min >= 15) — skipped
tactical link at :1719 (idle_min < 20.0) — always true
Failure scenario
An orphaned call from 02:46 is swept against an incident last updated 02:50. Unit overlap fires. With no location proxy and both divergence vetoes bypassed, the call links unconditionally — one incident absorbing unrelated traffic on the same talkgroup. This is the over-merge mechanism, reachable on precisely the one code path that can produce a negative idle.
Closed#22 added the fit check itself. This is a sign bug inside that check, introduced later by the 33a247d gate refactor, and it defeats the check on the sweep path only.
Test gap
test_idle_gate_uses_distance_not_sign covers the gates. _call_fits_incident has no equivalent coverage. A regression test should sweep a back-dated call against a newer incident and assert the divergence vetoes still fire.
Note on shipping this
incident_correlator.py is one of the four files board minutes #62 decision 9 froze for autonomous push to main until the deploy rollback (#65) lands. Fix on a branch, or wait for #65.
Found by the standing correlation pass on the 2026-08-25 evening unattended run. Static review only — not yet observed in production data.
## The defect
`_call_fits_incident` (`app/internal/incident_correlator.py:1601`) still computes idle with the **signed** `_incident_idle_minutes`. Commit `33a247d` moved every correlation *gate* onto `_idle_gate_minutes` (`:478`), which is distance-based and cannot go negative. `_call_fits_incident` was missed.
## Why the sign matters
`recorrelation_sweep.py:104` anchors `now` to the **call's own `started_at`**, not wall clock. A back-dated call therefore sees `idle_min < 0`, and every comparison of the form `idle_min >= N` is silently false:
- content-divergence veto at `:1620` — skipped
- content-divergence veto at `:1632` (`idle_min >= 15`) — skipped
- tactical link at `:1719` (`idle_min < 20.0`) — **always true**
## Failure scenario
An orphaned call from 02:46 is swept against an incident last updated 02:50. Unit overlap fires. With no location proxy and both divergence vetoes bypassed, the call links unconditionally — one incident absorbing unrelated traffic on the same talkgroup. This is the over-merge mechanism, reachable on precisely the one code path that can produce a negative idle.
## Why this is not #22
Closed #22 added the fit check itself. This is a sign bug *inside* that check, introduced later by the `33a247d` gate refactor, and it defeats the check on the sweep path only.
## Test gap
`test_idle_gate_uses_distance_not_sign` covers the **gates**. `_call_fits_incident` has no equivalent coverage. A regression test should sweep a back-dated call against a newer incident and assert the divergence vetoes still fire.
## Note on shipping this
`incident_correlator.py` is one of the four files board minutes #62 decision 9 froze for autonomous push to `main` until the deploy rollback (#65) lands. Fix on a branch, or wait for #65.
Refs #22, #27, #65, #62.
Still open — confirmed half-fixed by the standing correlation pass, unattended run 2026-08-27. Do not close.
_call_fits_incident (incident_correlator.py:1601) still reads the signed_incident_idle_minutes rather than the _idle_gate_minutes helper written for exactly this case (:478-493).
Every gate inside that function consumes the signed value:
the two idle_min >= 15 content-divergence vetoes (:1622, :1633)
idle_min < 20.0 -> tactical_default (:1720)
On the re-correlation sweep, now is anchored to the call's own started_at, so idle_min goes negative. Both divergence vetoes are therefore skipped, unit_overlap returns True unconditionally, and tactical channels link by default.
Direction: this pushes over-merging, and it is unintended. It is the sweep path specifically — ingest is unaffected because now is real there.
Not fixed this run; the run's engineering item was the AI-flag enforcement pair (#75/#76) and this is a separate change to the merge gates, which is not something to land untested alongside unrelated work.
**Still open — confirmed half-fixed by the standing correlation pass, unattended run 2026-08-27. Do not close.**
`_call_fits_incident` (`incident_correlator.py:1601`) still reads the **signed** `_incident_idle_minutes` rather than the `_idle_gate_minutes` helper written for exactly this case (`:478-493`).
Every gate inside that function consumes the signed value:
- the two `idle_min >= 15` content-divergence vetoes (`:1622`, `:1633`)
- `idle_min < 20.0 -> tactical_default` (`:1720`)
On the re-correlation sweep, `now` is anchored to the call's own `started_at`, so `idle_min` goes **negative**. Both divergence vetoes are therefore skipped, `unit_overlap` returns True unconditionally, and tactical channels link by default.
**Direction: this pushes over-merging, and it is unintended.** It is the sweep path specifically — ingest is unaffected because `now` is real there.
Not fixed this run; the run's engineering item was the AI-flag enforcement pair (#75/#76) and this is a separate change to the merge gates, which is not something to land untested alongside unrelated work.
_call_fits_incident now measures incident idle with the unsigned gate helper, matching every other recency gate in the file. Commit e30d594.
Confirmed still-broken at HEAD by the standing correlation pass before the fix, and the failure mode was worse than the title suggests: on the sweep path now is the call's own started_at, so the signed value went negative, idle_min >= 15 was false, and the content-divergence veto never ran at all — unit overlap was accepted unconditionally. On a shared dispatch backbone that veto is the only thing bounding the feedback loop that lets one incident absorb a whole talkgroup. The same negative value made idle_min < 20.0 true at any back-dating, so a tactical channel returned tactical_default for every swept orphan out to the 90-minute bound.
One variable feeds all four gates in the function, so the correction is a single line at the source rather than four separate comparisons.
Two tests added beside the existing idle-gate cases, both confirmed failing on the old line and passing on the new one: a back-dated call no longer bypasses the divergence veto, and a back-dated call on a tactical channel no longer gets the default. Suite: 266 pass, 0 fail.
Direction: toward more splitting, on the sweep path only. That is the intent — the bug was suppressing an over-merge veto — but it is worth naming, because it compounds the under-linking in #27: an orphan whose incident moved on 15-90 minutes after the call ended will now be back-linked less often. #27's retry budget (wall-clock rather than 3 attempts) is the natural follow-on to this, not a separate concern.
Post-deploy watch: 12 minutes of c2-core logs after the deploy, zero exceptions, zero errors. No revert.
**Fixed, verified and live at `e30d594`. Closing.**
`_call_fits_incident` now measures incident idle with the unsigned gate helper, matching every other recency gate in the file. Commit `e30d594`.
Confirmed still-broken at HEAD by the standing correlation pass before the fix, and the failure mode was worse than the title suggests: on the sweep path `now` is the call's own `started_at`, so the signed value went negative, `idle_min >= 15` was false, and **the content-divergence veto never ran at all** — unit overlap was accepted unconditionally. On a shared dispatch backbone that veto is the only thing bounding the feedback loop that lets one incident absorb a whole talkgroup. The same negative value made `idle_min < 20.0` true at any back-dating, so a tactical channel returned `tactical_default` for every swept orphan out to the 90-minute bound.
One variable feeds all four gates in the function, so the correction is a single line at the source rather than four separate comparisons.
Two tests added beside the existing idle-gate cases, both confirmed failing on the old line and passing on the new one: a back-dated call no longer bypasses the divergence veto, and a back-dated call on a tactical channel no longer gets the default. Suite: **266 pass, 0 fail**.
**Direction: toward more splitting, on the sweep path only.** That is the intent — the bug was suppressing an over-merge veto — but it is worth naming, because it compounds the under-linking in **#27**: an orphan whose incident moved on 15-90 minutes after the call ended will now be back-linked less often. #27's retry budget (wall-clock rather than 3 attempts) is the natural follow-on to this, not a separate concern.
Post-deploy watch: 12 minutes of `c2-core` logs after the deploy, zero exceptions, zero errors. No revert.
Refs #5, #27, #80.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Found by the standing correlation pass on the 2026-08-25 evening unattended run. Static review only — not yet observed in production data.
The defect
_call_fits_incident(app/internal/incident_correlator.py:1601) still computes idle with the signed_incident_idle_minutes. Commit33a247dmoved every correlation gate onto_idle_gate_minutes(:478), which is distance-based and cannot go negative._call_fits_incidentwas missed.Why the sign matters
recorrelation_sweep.py:104anchorsnowto the call's ownstarted_at, not wall clock. A back-dated call therefore seesidle_min < 0, and every comparison of the formidle_min >= Nis silently false::1620— skipped:1632(idle_min >= 15) — skipped:1719(idle_min < 20.0) — always trueFailure scenario
An orphaned call from 02:46 is swept against an incident last updated 02:50. Unit overlap fires. With no location proxy and both divergence vetoes bypassed, the call links unconditionally — one incident absorbing unrelated traffic on the same talkgroup. This is the over-merge mechanism, reachable on precisely the one code path that can produce a negative idle.
Why this is not #22
Closed #22 added the fit check itself. This is a sign bug inside that check, introduced later by the
33a247dgate refactor, and it defeats the check on the sweep path only.Test gap
test_idle_gate_uses_distance_not_signcovers the gates._call_fits_incidenthas no equivalent coverage. A regression test should sweep a back-dated call against a newer incident and assert the divergence vetoes still fire.Note on shipping this
incident_correlator.pyis one of the four files board minutes #62 decision 9 froze for autonomous push tomainuntil the deploy rollback (#65) lands. Fix on a branch, or wait for #65.Refs #22, #27, #65, #62.
Still open — confirmed half-fixed by the standing correlation pass, unattended run 2026-08-27. Do not close.
_call_fits_incident(incident_correlator.py:1601) still reads the signed_incident_idle_minutesrather than the_idle_gate_minuteshelper written for exactly this case (:478-493).Every gate inside that function consumes the signed value:
idle_min >= 15content-divergence vetoes (:1622,:1633)idle_min < 20.0 -> tactical_default(:1720)On the re-correlation sweep,
nowis anchored to the call's ownstarted_at, soidle_mingoes negative. Both divergence vetoes are therefore skipped,unit_overlapreturns True unconditionally, and tactical channels link by default.Direction: this pushes over-merging, and it is unintended. It is the sweep path specifically — ingest is unaffected because
nowis real there.Not fixed this run; the run's engineering item was the AI-flag enforcement pair (#75/#76) and this is a separate change to the merge gates, which is not something to land untested alongside unrelated work.
Fixed, verified and live at
e30d594. Closing._call_fits_incidentnow measures incident idle with the unsigned gate helper, matching every other recency gate in the file. Commite30d594.Confirmed still-broken at HEAD by the standing correlation pass before the fix, and the failure mode was worse than the title suggests: on the sweep path
nowis the call's ownstarted_at, so the signed value went negative,idle_min >= 15was false, and the content-divergence veto never ran at all — unit overlap was accepted unconditionally. On a shared dispatch backbone that veto is the only thing bounding the feedback loop that lets one incident absorb a whole talkgroup. The same negative value madeidle_min < 20.0true at any back-dating, so a tactical channel returnedtactical_defaultfor every swept orphan out to the 90-minute bound.One variable feeds all four gates in the function, so the correction is a single line at the source rather than four separate comparisons.
Two tests added beside the existing idle-gate cases, both confirmed failing on the old line and passing on the new one: a back-dated call no longer bypasses the divergence veto, and a back-dated call on a tactical channel no longer gets the default. Suite: 266 pass, 0 fail.
Direction: toward more splitting, on the sweep path only. That is the intent — the bug was suppressing an over-merge veto — but it is worth naming, because it compounds the under-linking in #27: an orphan whose incident moved on 15-90 minutes after the call ended will now be back-linked less often. #27's retry budget (wall-clock rather than 3 attempts) is the natural follow-on to this, not a separate concern.
Post-deploy watch: 12 minutes of
c2-corelogs after the deploy, zero exceptions, zero errors. No revert.Refs #5, #27, #80.