M2_SMOKE_LOG.md 4.6 KB

M2 Smoke Test — Live Results

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 results

# 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.

What this proves about the rules engine

  1. 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.
  2. 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).
  3. subscriptions.min_severity is enforced — Bob's min_severity=critical filter dropped him from the warning alerts in steps 2 and 5.
  4. subscriptions.quiet_hours is enforced — Carol's 00:00–23:59 window dropped her from every severity except inminent_colapse (step 6).
  5. inminent_colapse bypasses quiet hours — Carol received the step 6 alert even though her quiet window was active.
  6. Hard-fail on zero recipients — step 7 produced no delivery row and emitted a WARN log line with the full alert context (company, source, severity, category, alert_id).

Performance

  • Resolution latency: not separately measured in M2. The routerd "routed" log line shows enqueue latency (publish to NATS), not resolver latency. Will be a Prometheus histogram in M9.
  • DB round-trips per alert: 1 (the CTE in internal/routing/routing.go). Comparable to M1's broadcast query.

Test artifacts

  • M2_VERIFICATION.md — the 7-step test plan
  • scripts/m2_smoke.sh — automated end-to-end runner for steps 2-6
  • internal/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 TestMinSeverityRank

Honest flags

  • Routing rule priority is "all matching rules win, unioned", not the full priority-ordered spec semantics. M2 SPEC §6 says "first match wins, unless continue=true". We union because: (a) the v1 use case is "all-storage-alerts go to sre" and "critical-only goes to on-call", which is naturally union, and (b) the M2 honest flag in PROMPT.md says priority semantics land in M3+. Will revisit when we add per-company on-call rotations.
  • routing_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.
  • M2 still reads HMAC secrets from 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.
  • Subscription 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.
  • Resolver "broadcast fallback": when the source row exists, the SQL CTE 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).