Let a silent team's findings out of the escalation queue (#190) - #200
Merged
icebergai-review-bot[bot] merged 1 commit intoAug 19, 2026
Merged
Conversation
Escalation could stall behind findings that had nobody to escalate to.
`_escalation_targets` returns no targets in three cases: an owning team with no
channel ("silent by choice", and allowed), a team pointing at a disabled
channel, and an unowned finding no enabled channel's filter selects. None of
them writes an outbox row — so the SQL exclusion, which asks "has this been
escalated for this deadline?", kept answering no and kept selecting them.
The beat is bounded and ordered by deadline, oldest first. Once `limit` silent
findings accumulated they occupied the whole page on every beat, and nothing
behind them was ever escalated. That is the stall the exclusion was written to
prevent, arriving by a different route: a bounded page that never advances.
So "has this finding anywhere to go?" is now answered by the query that selects
the page rather than in Python after it. `_has_escalation_target` is the SQL
twin of `_escalation_targets` and mirrors its three cases; `_wanted_by` is
`channel_wants` as a predicate, built from the same parsed `EventFilter`, so
there is one filter vocabulary and two evaluation sites rather than two
vocabularies. A test holds the two readings against each other on the whole
severity range and on a source filter, so they cannot quietly diverge.
An outbox row recording the silence would have been the more evidence-preserving
fix and is what the issue proposed, but `notification_delivery.channel_id` is
non-nullable and two of the three cases have no channel at all. Making it
nullable would weaken the row's meaning — one announcement, to one channel — and
break the partial unique index that stops an escalation re-inserting every beat,
since NULLs do not collide. Not worth it for a bookkeeping row.
Owner groups are now loaded once for the predicate, which also retires the
`db.get(OwnerGroup, ...)` per finding in the loop below it.
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012sohE85sRDt6t2w3936rGJ
There was a problem hiding this comment.
Verdict
APPROVE
Completed bounded review across 1 immutable scope(s). No independently actionable regressions found in the supplied change.
Scope health
Convergence: healthy. Review mode: initial.
Recommended action: CONTINUE_INCREMENTAL.
- No escalation signals.
Prior findings
| Finding | Status |
|---|---|
| — | No prior finding state |
New findings
No new findings.
Fix-induced regressions
- None evidenced.
Uncertainty
- No material uncertainty recorded.
Validation
- Reviewed the supplied immutable diff and its added regression coverage.
- Exact-head CI is reported as passed.
Residual risks
- None identified.
icebergai-review-bot
Bot
deleted the
claude/codebase-review-cleanup-ovlli4
branch
August 19, 2026 23:15
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #190.
Overdue escalation could stall behind findings that had nobody to escalate to.
The defect
_escalation_targetslegitimately returns no targets in three cases: an owning team with no channel ("silent by choice", and allowed), a team pointing at a disabled channel, and an unowned finding that no enabled channel's filter selects. None of them writes an outbox row — so the SQL exclusion, which asks "has this been escalated for this deadline?", kept answering no and kept selecting them.The beat is bounded (200) and ordered by deadline, oldest first. Once that many silent findings accumulated, they occupied the whole page on every beat and nothing behind them was ever escalated. That is precisely the stall the exclusion was written to prevent, arriving by a different route: a bounded page that never advances.
Reproduced against merged
main— three silent findings, one targeted finding with a later deadline, five beats atlimit=2:Zero escalations, indefinitely.
The fix
"Has this finding anywhere to go?" is now answered by the query that selects the page, rather than in Python after it — the same reason the existing exclusion is in SQL: it has to happen before the limit.
_has_escalation_targetis the SQL twin of_escalation_targets, mirroring its three cases._wanted_byischannel_wantsas a predicate, built from the same parsedEventFilter— one filter vocabulary, two evaluation sites, rather than two vocabularies drifting apart. Severities are enumerated from the existing rank rather than compared in SQL, since the column stores the label and the ordering is the filter's policy.tests/test_suppression_consistency.pyapplies to the API/engine suppression pair.The design I did not take
The issue proposed recording a terminal "skipped/silent" delivery row, which preserves more evidence, and I'd have preferred it. It isn't available:
notification_delivery.channel_idis non-nullable, and two of the three cases have no channel at all. Making it nullable would weaken what the row means — one announcement, to one channel, about one finding — and break the partial unique index that stops an escalation re-inserting on every beat, because NULLs don't collide in a unique index. That is a lot of structural damage for a bookkeeping row, so the exclusion goes in the query instead. The console's overdue queue remains the record for silent findings, exactly as the docs already said.Validation
channel_wantswith_wanted_by. (These fail pre-fix only because the helper is new — they guard the future, they aren't evidence about this bug.)ruff/ruff format/mypy/make docs-checkclean.Incidental
Owner groups are now loaded once to build the predicate, which also retires the
db.get(OwnerGroup, ...)issued per finding in the loop below — one of the N+1s noted in #197.Operator note
No action required, and no migration. The next beat picks up whatever had been starved.
🤖 Generated with Claude Code
https://claude.ai/code/session_012sohE85sRDt6t2w3936rGJ
Generated by Claude Code