Part 3 of 3 for #27: team member sources and mute scope. Parts 1 (quiet hours and digests) and 2 (reminders and escalation) are separate PRs.
Team sources
New public SPI AlertAudienceResolver (resolve(sourceType, sourceValue) -> Set<userId>), registered with AlertsQBitConfig.withAudienceResolver(...). The qbit doesn't depend on the role or Authentik qbits; the host supplies the resolver.
The router merges the resolved users with the MANUAL members, de-duplicates them and caps the list at maxAudienceSize. Over the cap it logs and stops adding, and doesn't fail routing for everyone else. Each user still goes through the visibility check.
With no resolver, or a resolver that throws, ROLE and IdP Group teams resolve to nobody. The router logs a warning, and the team screen help says "ROLE and IdP Group members need a host resolver; none is configured" (worked out at runtime).
Mutes
The per-alert Mute action now mutes only that alert, using a new ALERT scope keyed on type plus dedup key. Mute this alert type is a separate action.
RECORD-scope mutes store scopeRecordId, and the matcher now compares it. Before, it was always passed null, so RECORD mutes never matched.
Changesets 21 (alerts_mute.scope_record_id) and 22 (widen scope_value to 520) are guarded. If part 1 or 2 merges first and takes those numbers, I'll renumber on the merge.
Tests: mvnw -o -B verify green (112 tests, 93.9% instruction coverage, checkstyle, guardrails). New tests cover resolver expansion (ROLE, IdP Group, de-duplication with MANUAL, no resolver), the per-alert mute, RECORD-scope matching and the type mute.
Part 3 of 3 for #27: team member sources and mute scope. Parts 1 (quiet hours and digests) and 2 (reminders and escalation) are separate PRs.
**Team sources**
- New public SPI `AlertAudienceResolver` (`resolve(sourceType, sourceValue) -> Set<userId>`), registered with `AlertsQBitConfig.withAudienceResolver(...)`. The qbit doesn't depend on the role or Authentik qbits; the host supplies the resolver.
- The router merges the resolved users with the MANUAL members, de-duplicates them and caps the list at `maxAudienceSize`. Over the cap it logs and stops adding, and doesn't fail routing for everyone else. Each user still goes through the visibility check.
- With no resolver, or a resolver that throws, ROLE and IdP Group teams resolve to nobody. The router logs a warning, and the team screen help says "ROLE and IdP Group members need a host resolver; none is configured" (worked out at runtime).
**Mutes**
- The per-alert **Mute** action now mutes only that alert, using a new `ALERT` scope keyed on type plus dedup key. **Mute this alert type** is a separate action.
- RECORD-scope mutes store `scopeRecordId`, and the matcher now compares it. Before, it was always passed null, so RECORD mutes never matched.
- Changesets **21** (`alerts_mute.scope_record_id`) and **22** (widen `scope_value` to 520) are guarded. If part 1 or 2 merges first and takes those numbers, I'll renumber on the merge.
Tests: `mvnw -o -B verify` green (112 tests, 93.9% instruction coverage, checkstyle, guardrails). New tests cover resolver expansion (ROLE, IdP Group, de-duplication with MANUAL, no resolver), the per-alert mute, RECORD-scope matching and the type mute.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
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.
Part 3 of 3 for #27: team member sources and mute scope. Parts 1 (quiet hours and digests) and 2 (reminders and escalation) are separate PRs.
Team sources
AlertAudienceResolver(resolve(sourceType, sourceValue) -> Set<userId>), registered withAlertsQBitConfig.withAudienceResolver(...). The qbit doesn't depend on the role or Authentik qbits; the host supplies the resolver.maxAudienceSize. Over the cap it logs and stops adding, and doesn't fail routing for everyone else. Each user still goes through the visibility check.Mutes
ALERTscope keyed on type plus dedup key. Mute this alert type is a separate action.scopeRecordId, and the matcher now compares it. Before, it was always passed null, so RECORD mutes never matched.alerts_mute.scope_record_id) and 22 (widenscope_valueto 520) are guarded. If part 1 or 2 merges first and takes those numbers, I'll renumber on the merge.Tests:
mvnw -o -B verifygreen (112 tests, 93.9% instruction coverage, checkstyle, guardrails). New tests cover resolver expansion (ROLE, IdP Group, de-duplication with MANUAL, no resolver), the per-alert mute, RECORD-scope matching and the type mute.🤖 Generated with Claude Code