Run on 2026-06-13, host interserver2. All 7 steps from
M2_VERIFICATION.md (plus a hard-fail scenario) passed against
the running docker-compose stack.
| # | step | expected recipients | actual | result |
|---|---|---|---|---|
| 2 | warning, db-prod-04 | Alice only | ind-acme-001 | ✅ |
| 3 | critical, db-prod-04 | Alice + Bob | ind-acme-001,ind-acme-002 | ✅ |
| 4 | critical, db-prod-03 (rule match) | Alice + Bob | ind-acme-001,ind-acme-002 | ✅ |
| 5 | warning, db-prod-03 (rule match but min filter) | Alice only (Bob filtered by min_severity) | ind-acme-001 | ✅ |
| 6 | inminent_colapse, rack-b | Alice + Bob + Carol (Carol's quiet hours bypassed) | ind-acme-001,ind-acme-002,ind-acme-003 | ✅ |
| 7 | info, with subscriptions paused | drop + log | WARN msg="zero recipients, dropping" |
✅ |
Total: 9 deliveries across 5 unique alert_ids, 0 failures.
source.allowed_targets is honored — the resolver only
considered individuals in {sre group, ind-acme-003}. Adding a
4th individual to the company with no subscription produced no
deliveries.routing_rules.match_expr works — the rule for
category=storage + data.host=db-prod-03 → Bob was evaluated
in step 4 (Bob would have been there anyway) and step 5 (Bob
was added by the rule but then dropped by his min_severity
filter).subscriptions.min_severity is enforced — Bob's
min_severity=critical filter dropped him from the warning
alerts in steps 2 and 5.subscriptions.quiet_hours is enforced — Carol's
00:00–23:59 window dropped her from every severity except
inminent_colapse (step 6).inminent_colapse bypasses quiet hours — Carol received
the step 6 alert even though her quiet window was active.WARN log line with the full
alert context (company, source, severity, category, alert_id).internal/routing/routing.go). Comparable to M1's broadcast
query.M2_VERIFICATION.md — the 7-step test planscripts/m2_smoke.sh — automated end-to-end runner for
steps 2-6internal/routing/routing_test.go — 10 subtests for the
inQuietHours helper (same-day, wrap-around, always-quiet,
edge cases)internal/alert/alert_test.go — added TestSeverityRank and
TestMinSeverityRankrouting_rules.match_expr is intentionally simple:
category, severity, data.<key>=<value>, all. Full
JSONPath/expression language is M6 or later. The SQL uses
EXISTS (SELECT 1 FROM jsonb_each(...)) so adding more
data.* keys is free.BA_INGESTD_SOURCES env,
not the sources table. The sources table here holds
addressing metadata (allowed_targets, match_expr); the
auth path is unchanged from M0/M1. The M2 honest flag in
PROMPT.md plans to move auth to the DB in M5 when mTLS and
API-key paths come in.channel_mask accepts telegram but the M2
resolver drops it (Q3 answer (a): emit the row, drop it).
Since our seed only has ["fcm"] in channel_mask, no rows
are dropped in M2 verification. Adding ["telegram"] to a
subscription would result in a "no worker" situation; the
SQL WHERE f.channel = 'fcm' filter is what enforces this.allowed_individual_ids includes all
active individuals in the company. This is the M2 "fall
back to everyone" behavior, gated by whether the source
row exists. Hard-fail (Q2 answer) kicks in only when the
resolver returns zero targets AFTER this expansion — which
requires the SQL filters (min_severity, quiet hours,
channel_mask) to drop everyone, or no subscriptions to
exist (step 7).