Skip to content

Fix four drill-exposed detection gaps: verify-deploy blind window, dormant carve-out, change correlation, error grouping#307

Merged
Szer merged 6 commits into
mainfrom
fix/drill2-detection-gaps
Jul 27, 2026
Merged

Fix four drill-exposed detection gaps: verify-deploy blind window, dormant carve-out, change correlation, error grouping#307
Szer merged 6 commits into
mainfrom
fix/drill2-detection-gaps

Conversation

@Szer

@Szer Szer commented Jul 27, 2026

Copy link
Copy Markdown
Owner

Summary

Fixes the four detection defects exposed by today's (2026-07-26) deliberate production drill,
where a genuinely broken AlitaBot deploy (background loop throwing every 15s + a throwing
Telegram update handler, bot completely non-functional) was shipped GREEN by
verify-deploy.sh (run 30259908859) and then reported CLEAN by the monitor (run 30260458048).

  1. verify-deploy.sh blind window (scripts/verify-deploy.sh) — Phase 3 now samples Loki
    only after a settle delay past confirmed readiness, and scopes the query to the NEW pod's
    name(s) resolved via the ArgoCD resource-tree endpoint (falls back to the broad selector,
    logged, if that's unavailable). Settle delay defaults to 45s (3x the drill's 15s
    failing-loop period).
  2. Dormant carve-out disabling ERROR detection (scripts/gather/baseline.sh,
    .github/prompts/monitor.md) — baseline.sh now emits a mechanical dormant_exempt flag
    per series (false for log_errors_24h/log_warnings_24h, true for traffic/volume
    series). monitor.md's dormant carve-out now gates on this flag instead of a blanket
    "rules 3/4 disabled for dormant bots."
  3. Change correlation suppressing bad deploys (.github/prompts/monitor.md) — attributed
    can now only suppress traffic/volume findings. Error-rate findings (new failure mode,
    error-group z-score, log_errors_24h/log_warnings_24h ratio/emerged-from-zero) that
    correlate with a deploy are now classified correlated — suspected bad deploy and filed
    priority-high naming the commit — never suppressed.
  4. Evidence bundle lacking per-group SourceContext (scripts/gather/runtime.sh,
    scripts/gather/baseline.sh) — error-log grouping now emits per-group SourceContext,
    message prefix, count, AND one verbatim sample line (was counts-only), with the message
    field fallback fixed to check CLEF's @m first.

Plus a documentation-only commit syncing .github/AGENT-FLOWS-REDESIGN.md §6.1 with the
corrected dormant/change-correlation rules.

Four defect fixes as four separate, independently-revertible commits, plus one doc-sync
commit.

Test plan

  • bash -n + shellcheck -x (0.11.0) on every script touched — all clean, verbatim output
    in the task report.
  • Fixture-tested the error-group extraction (runtime.sh and baseline.sh) against a
    canned Loki query_range response built from the real CLEF shape, with two distinct
    SourceContext values — verbatim output pasted in the task report.
  • Fixture-tested the dormant_exempt flag against a synthetic 28-day alita history where
    log_errors_24h spikes from a flat-zero baseline (the drill scenario) while
    message_log_24h also emerges from zero (alita's normal pattern) — confirms
    log_errors_24h: dormant_exempt: false and message_log_24h: dormant_exempt: true.
  • No workflow files touched — actionlint gate not applicable.
  • Not run: dotnet test (not needed for this PR — no src//tests/ changes) and no real
    Telegram/LLM tests (ALITA_REAL_TESTS/COUPON_REAL_TESTS deliberately not set, per repo
    policy).
  • Not merged — left open per instructions.

🤖 Generated with Claude Code

https://claude.ai/code/session_01P41roCWN5Fec7m69sSaunU

Szer and others added 5 commits July 27, 2026 12:21
… to it

Phase 3 used to query Loki for {container="..."} errors over "the last 2
minutes" immediately after ArgoCD reported Health=Healthy — while the
rollout could still be finishing and the OLD, still-serving pod was what
the broad container selector actually sampled. The 2026-07-26 drill (a
background loop throwing every 15s + a throwing Telegram update handler)
shipped, verify-deploy.sh PASSED (run 30259908859), and the bot was
completely non-functional: the first drill error appeared at 11:02:44,
strictly after Phase 3's query had already run and passed.

Fix:
- Capture READY_AT the moment ArgoCD confirms Healthy (both the in-loop and
  end-of-grace-period paths).
- Sleep a fixed LOKI_SETTLE_DELAY (default 45s = 3x the drill's 15s
  failing-loop period) after readiness, so at least two iterations of a
  periodic background failure have time to fire and propagate through the
  scrape+ship pipeline (typical 10-15s scrape interval + Loki ingestion lag)
  before Phase 3 samples.
- Resolve the NEW pod's name(s) via the ArgoCD resource-tree endpoint
  (ResourceNode.images, same mechanism sre.md already uses to tell old/new
  pods apart) and scope the Loki query to them with pod=~"...". Falls back
  to the broad container selector (logged + summarized) if resource-tree is
  unreachable or no pod matches the expected image tag, rather than failing
  the whole check.
- Start the Phase 3 Loki window at READY_AT instead of "2 minutes ago from
  whenever Phase 3 happens to run".

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P41roCWN5Fec7m69sSaunU
The 2026-07-26 drill exposed the monitor agent suppressing a genuine error
burst on alita because it read "traffic_class: dormant disables rules 3/4"
as applying to EVERY series, including log_errors_24h itself:
`current=13 median_28d=0 z_score_28d=33.59 emerged_from_zero=true
emerged_from_zero_significant=true` was handed to it and dropped, reasoning
"rules 3 and 4 ... are disabled for this bot." The carve-out exists only to
stop alita's zero-inflated TRAFFIC/VOLUME series from firing constantly; it
must never apply to error/failure series.

- scripts/gather/baseline.sh: `_stats_jq` now emits `dormant_exempt` per
  series (false for log_errors_24h/log_warnings_24h — rules always apply;
  true for everything else), mirroring the existing `informational_only`
  pattern so the distinction is mechanical rather than prose the agent has
  to remember.
- .github/prompts/monitor.md: the "Dormant carve-out" section and rule 4's
  inline exception note now gate on `dormant_exempt` explicitly, quote the
  drill's own evidence as the cautionary example, and state plainly that
  rules 1/2/5 already apply unconditionally regardless of traffic_class.

Fixture-tested (synthetic 28-day alita history, log_errors_24h flat at 0
until a current=13 spike, message_log_24h zero-inflated as alita's real
pattern is): log_errors_24h comes back `dormant_exempt: false,
emerged_from_zero: true, emerged_from_zero_significant: true`;
message_log_24h comes back `dormant_exempt: true`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P41roCWN5Fec7m69sSaunU
…ding

The 2026-07-26 drill agent wrote: "Classification: attributed — the
increased error count is plausibly explained by the recent intentional
DRILL changes... that makes the observed error volume expected" and
suppressed a genuine, bot-breaking error burst. A deploy explaining an
error burst does not make it benign — it makes it a bad deploy. Most real
outages ARE deploy-correlated, so unscoped `attributed` suppression throws
away exactly the incidents that matter most.

Restricts `attributed` (suppress, do not file) to traffic/volume findings
only (rule 3/4 candidates on a series with `dormant_exempt: true`). For
error-rate findings (rule 1, rule 2, or rule 3/4 on log_errors_24h /
log_warnings_24h), a correlated deploy now RAISES severity: classify as
`correlated — suspected bad deploy`, file priority-high, name the
suspected commit. Two worked examples included verbatim: the existing
vahter PR #213 log-volume case (volume, correctly suppressed) and the
2026-07-26 drill itself (error-rate, must be filed, not suppressed) quoting
the actual wrong classification from that run. Also updated the "Change
correlation" field in the finding-issue template so a filed error-rate
finding never says "attributed."

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P41roCWN5Fec7m69sSaunU
…dence

The 2026-07-26 monitor run reported rules 1 (new failure mode) and 2
(error-group z-score) UNEVALUABLE: "per-group identifiers and z-scores
were not present in the provided Loki grouping output (only counts-only
lines were included)". The drill's brand-new Program.DrillRuntimeFailureLoop
SourceContext - a guaranteed rule-1 hit, unseen in 28 days - was invisible
behind a bare "N occurrence(s)" line.

- scripts/gather/runtime.sh: TOP_ERRORS_GROUPED (was TOP_ERRORS_COUNT) now
  groups by SourceContext + 80-char message prefix and renders each group
  as its SourceContext, count, prefix, AND one verbatim raw sample line -
  this is what actually reaches the monitor agent's evidence bundle (via
  runtime.sh's markdown output in monitor.yml), so this is the load-bearing
  fix.
- scripts/gather/baseline.sh: cmd_gather's error_groups is now an ARRAY of
  {source_context, message_prefix, count, sample} objects (was a bare
  key->count map with no way to recover SourceContext or a sample line
  from a key string), sorted by count descending. Also updated the empty
  fallback ({} -> []) and header docstring to match the new shape.
- Both jq programs' message-field fallback now checks CLEF's @m first
  ($parsed["@m"] // .RenderedMessage // .message // .msg // $raw) - the
  drill's binding facts confirmed the real Loki payload is CLEF-shaped
  ({"@t":...,"@l":"Error","@m":"...","SourceContext":"..."}), and the
  prior fallback chain never matched it, so message_prefix would have
  silently degraded to the raw JSON blob.
- query_loki_patterns still 404s on this Loki (per the brief) - grouping
  stays hand-built; "| json | level=~\"Error|Fatal\"" queries are unchanged
  and already verified working.

Fixture-tested against a canned Loki query_range response built from the
real CLEF shape (two SourceContexts: Program.DrillRuntimeFailureLoop x3,
AlitaBot.Bot.UpdateHandler x1) - both the runtime.sh and baseline.sh jq
transforms correctly emit per-group SourceContext, count, and a verbatim
sample.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P41roCWN5Fec7m69sSaunU
…ion rules

Documentation-only sync of section 6.1 (monitor role spec) with the two
prompt/mechanical fixes made in this branch (dormant carve-out scoping,
error-rate vs traffic/volume change correlation) and the 2026-07-26 drill
postmortem that motivated both, so the design doc and .github/prompts/monitor.md
do not drift back out of sync. No behavior change - this commit touches only
the design document, not scripts/gather/baseline.sh or monitor.md.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P41roCWN5Fec7m69sSaunU
monitor.yml injects DORMANT_NOTE into the agent's prompt at runtime. It still
read "disables volume-based rules entirely for this bot" — restating the exact
rule that defect 2 removes, and contradicting the corrected monitor.md in the
same prompt.

That contradiction is how defect 2 happened: on 2026-07-27 the agent read the
blanket wording and suppressed log_errors_24h at z_score 33.59, emerged from a
28-day median of 0, on a bot that was completely dead and not answering its
owner.

A prompt that contradicts its own rulebook resolves to whichever the model
reads last. Fixing the rulebook without fixing the injected note would have
left the bug reachable.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P41roCWN5Fec7m69sSaunU
@Szer
Szer merged commit da3a34a into main Jul 27, 2026
5 checks passed
@Szer
Szer deleted the fix/drill2-detection-gaps branch July 27, 2026 11:36
Szer added a commit that referenced this pull request Jul 27, 2026
… for SRE-agent validation take 3 (#309)

DELIBERATE, OWNER-AUTHORIZED SRE-AGENT VALIDATION DRILL. The owner explicitly
authorized taking AlitaBot down again: its only chat is with them, and this
re-run specifically validates the six detection-pipeline fixes from #307.

Same failure SHAPE as drill 2 (#303, reverted in #304): a hosted
BackgroundService (PendingUsageFlushLoop) ticking every 15s via PeriodicTimer,
logging Error on every tick, traffic-independent so it fires within
verify-deploy.sh's Loki window even though AlitaBot is dormant; plus a
throwing Telegram update handler so real messages also fail. Both gated on
`not TestMode`.

The one thing that's DIFFERENT on purpose: drill 2's every log line contained
the literal token SRE_DRILL_RUNTIME_FAILURE plus "deliberate"/"drill"/"must be
reverted" — the monitor agent read those words in the log text itself and
filed nothing while AlitaBot was completely dead. This drill's runtime log
text and exception messages are deliberately disguised as an ordinary
application bug instead:
- PendingUsageFlushLoop raises via an unguarded `Seq.head` on an empty
  sequence (System.ArgumentException: "The input sequence was empty."),
  logged as "Usage flush tick failed while flushing pending LLM usage
  records" — reads like a forgot-to-check-empty-queue bug in a usage-flush
  tick, not a drill.
- The webhook handler dereferences a null string (System.NullReferenceException:
  "Object reference not set to an instance of an object."), caught by the
  existing generic "Unhandled error in update handler for {UpdateId}" log —
  a completely ordinary NRE.
No "drill"/"SRE_DRILL"/"intentional"/"revert" appears in any string literal,
log message, or exception text — only in the F# source comments above each
throw site, which state prominently and in capitals (for future readers of
the diff/commit, not for Loki) that this is deliberate and must be reverted.

Gate verified: prod bot_setting.TEST_MODE = 'false' (per #307's binding
context); tests/AlitaBot.Tests sets TEST_MODE=true via both the container env
var and a seeded bot_setting row (ContainerTestBase.fs lines 34, 87), so the
drill code never executes under the hermetic suite. AlitaBot-only
(src/AlitaBot/Program.fs), single commit, no BotInfra/other-bot/migration/
Helm/k8s/workflow changes.

REVERT IMMEDIATELY AFTER THE DRILL via `git revert` of this commit.


Claude-Session: https://claude.ai/code/session_01P41roCWN5Fec7m69sSaunU

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant