Thin-call auto-attach has no fit check, merging unrelated events into one incident #22

Closed
opened 2026-08-20 03:17:18 -04:00 by logan · 1 comment
Owner

is_thin_call attaches a call to the open incident without testing whether it belongs there — tags, location and severity are all ignored. 58 of 133 calls in the 2026-08-19 dump took this path, and 6 of those were real dispatches, not chatter.

Worst case observed: incident f5190670 — 68 calls spanning 4h09m, 44 units, 12 tags, and at least 13 distinct events, all fused into a single "incident". That is not an incident, it is a shift.

This is the direct cause of the junk-chain problem getting worse, not better: 4 of 6 sampled incidents are junk chains (67%), against 2 of 5 (40%) in the previous rules-only dump, and the bad ones are far bigger. It is the single most damaging defect against the product's premise that the unit of value is the incident.

Note the failure mode has fully inverted. There is no over-splitting in this dump at all — every incident has exactly one new call. Everything now over-merges. server-26#5 (scene over-splitting) is the opposite complaint and needs re-checking against fresh data before more work goes into it.

Fix direction: a thin call should still have to pass a minimum fit test before attaching — at least a location or unit overlap, or an explicit "no signal, attach to most recent" that is bounded much more tightly in time than 4 hours. Consider a hard cap on incident duration and call count that forces a new incident regardless of path.

Full analysis: CORRELATION_REVIEW_0820.md (project root, not in git).

`is_thin_call` attaches a call to the open incident without testing whether it belongs there — tags, location and severity are all ignored. 58 of 133 calls in the 2026-08-19 dump took this path, and 6 of those were real dispatches, not chatter. **Worst case observed:** incident `f5190670` — **68 calls spanning 4h09m, 44 units, 12 tags, and at least 13 distinct events**, all fused into a single "incident". That is not an incident, it is a shift. This is the direct cause of the junk-chain problem getting *worse*, not better: 4 of 6 sampled incidents are junk chains (67%), against 2 of 5 (40%) in the previous rules-only dump, and the bad ones are far bigger. It is the single most damaging defect against the product's premise that the unit of value is the incident. Note the failure mode has fully **inverted**. There is no over-splitting in this dump at all — every incident has exactly one `new` call. Everything now over-merges. server-26#5 (scene over-splitting) is the opposite complaint and needs re-checking against fresh data before more work goes into it. Fix direction: a thin call should still have to pass a minimum fit test before attaching — at least a location or unit overlap, or an explicit "no signal, attach to most recent" that is bounded much more tightly in time than 4 hours. Consider a hard cap on incident duration and call count that forces a new incident regardless of path. Full analysis: `CORRELATION_REVIEW_0820.md` (project root, not in git).
logan closed this issue 2026-08-20 03:34:56 -04:00
Author
Owner

Fixed in 33a247d; deployed (Gitea Actions 732 build + 733 deploy, both success, health check passed).

Three changes:

  1. _is_thin_call now rejects any call carrying tags, a location string, a severity above routine, or the reassignment flag - so a real dispatch can no longer take the one path with no fit check. That also closes a self-defeating guard: upload.py blanks units on a reassignment to stop unit-overlap chaining, and blanking units was what made the call thin.
  2. The 30s tier-1 / single-candidate tier-2 thin tiering now applies to every channel instead of dispatch only. Non-dispatch previously fell through to the full 90-minute window with no single-candidate requirement; it now gets TG_THIN_IDLE_MINUTES=15, inside the 20-minute tactical-default already used in _call_fits_incident.
  3. Hard caps INCIDENT_MAX_DURATION_MINUTES=120 and INCIDENT_MAX_CALLS=40 remove an over-grown incident from the candidate pool before any path can choose it, including the LLM tier. 120 clears the only genuine incident in the dump (63 min) with headroom, sits well under the four junk chains (3h41m-4h09m), and equals correlation_window_hours, which the location and slow paths already enforce and the fast path was exempt from.

Also: recency gates now compare |idle|. The sweep anchors now to the call's own started_at, so idle goes negative (9d376ffe: -4.1) and every idle <= window test read True - the gates had stopped bounding anything for exactly the calls the sweep re-examines.

31 new cases in tests/test_correlator_merge_caps.py, including a replay of the f5190670 night. Without the caps that traffic still builds a 125-call / 244-minute incident; with the old thinness test on top, 153 calls over 247 minutes in 3 incidents. With the fix: 13 incidents, largest 40 calls over 80 minutes. Suite 138 passed, was 107.

Deliberately out of scope, filed separately: #26 (title re-derived from the newest call) and #27 (sweep retry budget expiring before the incident exists - an under-linking bug, so every fix pushes toward more merging). Unit-overlap positive feedback on dispatch backbones is bounded by the caps rather than fixed at its root; noted in DEFERRED.md, because narrowing unit overlap is the easiest way to re-introduce the over-splitting the 2026-08-20 data no longer shows.

Fixed in `33a247d`; deployed (Gitea Actions 732 build + 733 deploy, both success, health check passed). Three changes: 1. `_is_thin_call` now rejects any call carrying tags, a location string, a severity above routine, or the reassignment flag - so a real dispatch can no longer take the one path with no fit check. That also closes a self-defeating guard: `upload.py` blanks `units` on a reassignment to stop unit-overlap chaining, and blanking units was what made the call thin. 2. The 30s tier-1 / single-candidate tier-2 thin tiering now applies to every channel instead of dispatch only. Non-dispatch previously fell through to the full 90-minute window with no single-candidate requirement; it now gets `TG_THIN_IDLE_MINUTES=15`, inside the 20-minute tactical-default already used in `_call_fits_incident`. 3. Hard caps `INCIDENT_MAX_DURATION_MINUTES=120` and `INCIDENT_MAX_CALLS=40` remove an over-grown incident from the candidate pool before any path can choose it, including the LLM tier. 120 clears the only genuine incident in the dump (63 min) with headroom, sits well under the four junk chains (3h41m-4h09m), and equals `correlation_window_hours`, which the location and slow paths already enforce and the fast path was exempt from. Also: recency gates now compare |idle|. The sweep anchors `now` to the call's own `started_at`, so idle goes negative (`9d376ffe`: -4.1) and every `idle <= window` test read True - the gates had stopped bounding anything for exactly the calls the sweep re-examines. 31 new cases in `tests/test_correlator_merge_caps.py`, including a replay of the `f5190670` night. Without the caps that traffic still builds a 125-call / 244-minute incident; with the old thinness test on top, 153 calls over 247 minutes in 3 incidents. With the fix: 13 incidents, largest 40 calls over 80 minutes. Suite 138 passed, was 107. Deliberately out of scope, filed separately: #26 (title re-derived from the newest call) and #27 (sweep retry budget expiring before the incident exists - an under-linking bug, so every fix pushes toward more merging). Unit-overlap positive feedback on dispatch backbones is bounded by the caps rather than fixed at its root; noted in DEFERRED.md, because narrowing unit overlap is the easiest way to re-introduce the over-splitting the 2026-08-20 data no longer shows.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: logan/server-26#22