Repository navigation
fix(observability): make the shiva battery alert say what to order (#76) - #200
Merged
Merged
Conversation
IloBatteryCondition fired unbroken for the whole retained window and was correct to. It still could not be acted on: it read cpqHeSysBackupBatteryCondition, one identity-free scalar for the entire battery sub-system, so it could say "degraded" and nothing else — while its summary claimed to cover the RAID cache, which that metric cannot see. Which pack, why, and what to order were all in tables no rule touched. The fault is real and terminal. Chassis 0 battery 1 reads cpqHeSysBatteryStatus 13 (shutdownPermanentFailure) and Condition 4 (failed): an HP Smart Storage Batt 96, spare part 815983-001. 13 is not a flat pack and not a seating fault, so there was never anything to try before ordering. On a Gen9 that pack backs the Smart Array's flash-backed write cache, so the controller has permanently disabled the accelerator — cpqDaAccelStatus 5, errCode 25 (noCapacitorAttached), read and write cache percent both 0 — and the array runs write-through. That is slower, not less durable, and cpqDaAccelBadData 2 (none) confirms nothing dirty was lost. Re-key IloBatteryCondition onto the per-battery table, carrying cpqHeSysBatteryStatus as the value so $value is the diagnosis rather than a condition code, and group_left the spare part number in so the page names the part to buy. Add IloWriteCacheDisabled on cpqDaAccelStatus > 3 — the whole cpqDaAccel* family was scraped and read by nothing, and it holds the one series that states what the fault costs. > 3 rather than != 3 because 1 and 2 are what a controller with no cache module reports; for: 1h because a pack recharging after a power cut sits in write-through legitimately. Extend IloHardwareDegraded to the controller rollups at > 3, not > 2. At > 2 they fire today: measured, exactly three of them, because a flat cache battery degrades all three. That would put three critical alerts on the urgent receiver every 4h for a fault two warnings already describe. Add network.test.yaml, the file's first tests. Seven mutations were checked and all seven fail it, including dropping the group_left, keying the join on instance alone, and reverting to the old scalar. Guard the unit-test coverage claim in check_docs.py. It read "six rules of 39 ... ContainerHighMemory and Watchdog" — six claimed, two named, three more covered since blackbox.test.yaml landed. Nothing matched it, which is #72's failure mode surviving inside the fix for it. Both numbers are now checked, mutation-tested in both directions. Also: an eighth amtool route assertion, since nothing proved category=hardware reached a receiver at all; the Hardware health panel extended to the array and battery rollups (no panel-count change); and the residual recorded in roadmap.md, hardware.md and a new runbook rather than in SECURITY.md, whose Known exposure table is a security register and not a parts list. Silenced for device=shiva until 2026-10-01 (3ffc313f-7154-459c-9f62-7c9a432bc97e) — narrow matchers, and to be deleted when the pack goes in rather than left to expire. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This was referenced Aug 31, 2026
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.
Closes #76.
The alert was right, and unusable
IloBatteryConditionfired unbroken from2026-08-18 21:00Zto2026-08-31 03:00Z— the entire retained window. It was correct the whole timeand still could not be acted on, because it read
cpqHeSysBackupBatteryCondition: one identity-free scalar for the whole batterysub-system. It could say "degraded" and nothing more, while its summary claimed
to cover the RAID cache — which that metric cannot see. Nothing in the repo
referenced
cpqDaAccel*at all.So the issue's pointer to
cpqDaAccel*was aimed at the right subtree and thewrong rule, and that gap is most of what this PR closes.
The fault, decoded against the repo's own
snmp.yamlcpqHeSysBatteryProductNameHP Smart Storage Batt 96815983-001cpqHeSysBatteryConditioncpqHeSysBatteryStatusshutdownPermanentFailurecpqDaAccelStatuspermDisabledcpqDaAccelErrCodenoCapacitorAttachedcpqDaAccelRead/WriteCachePercentcpqDaAccelBadDatanone— nothing dirty was lost13is terminal — not a flat pack, not a seating fault — so "reseat orreplace" was only ever replace. On a Gen9 this pack backs the Smart Array's
flash-backed cache, so it is one fault seen through two MIB subtrees, not two
problems. Everything else on the box reads
ok: both PSUs, both physicaldrives, the logical drive, ~30 temperature sensors.
One correction worth carrying: write-through is slower, not riskier. It
closes the cache-loss window rather than opening one. The cost is write latency
and IOPS on the hypervisor, which is why the new rule is
warning.Changes
IloBatteryConditionre-keyed onto the per-battery table. It carriescpqHeSysBatteryStatusas the value, so$valueis the diagnosis (13)rather than a condition code, and
group_lefts the spare part number in sothe page names the part to buy. Name kept, so the silence is not orphaned.
IloWriteCacheDisabled(new) oncpqDaAccelStatus > 3— the family thatwas scraped and read by nothing holds the one series stating what the fault
costs.
> 3not!= 3because 1/2 are what a controller with no cachemodule reports;
for: 1hbecause a pack recharging after a power cut sits inwrite-through legitimately.
IloHardwareDegradedextended to the controller rollups at> 3, not> 2. At> 2they fire today — measured, exactly three — because a flatcache battery degrades all three. That would put three critical alerts on
the
urgentreceiver every 4h for a fault two warnings already describe.network.test.yaml— the file's first unit tests.check_docs.pynow guards the unit-test coverage claim.amtoolroute assertion, since nothing provedcategory=hardwarereached a receiver at all.
an existing panel, so the 84-panel count is unchanged.
Verification
./scripts/validate.shpasses end to end, includingamtool config routes test (8 assertions)anddocs OK — 40 Prometheus + 13 Loki rules, 5 dashboards, 84 panels.Every expression was evaluated against the real retained series before being
written, not just unit-tested: the join returns one series at value
13carrying
cpqHeSysBatterySparePartNum="815983-001",cpqDaAccelStatus > 3returns one at
5, and the extendedIloHardwareDegradedreturns empty.Seven mutations were checked against the new tests and all seven fail,
including dropping the
group_left, keying the join oninstancealone, andreverting to the old scalar.
Two things found on the way
docs/observability.mdclaimed "Coverage is six rules of 39 ...ContainerHighMemoryandWatchdog" — six claimed, two named, three morecovered since
blackbox.test.yamllanded. No pattern matched it, which isAlert-rule and panel counts are stale in 8 places, including a deploy-runbook check #72's failure mode surviving inside the fix for it. Both numbers are now
checked and mutation-tested in both directions.
roadmap.md,hardware.mdand a new runbook —not in
SECURITY.md. That table is a security register (credentials, bindaddresses, an unencrypted disk); a dead battery is not an exposure, and its
house phrase "not a fix in progress" would be false while a part is on order.
Silence
3ffc313f-7154-459c-9f62-7c9a432bc97e, matchingalertname=~"IloBatteryCondition|IloWriteCacheDisabled"anddevice="shiva",until 2026-10-01. Deliberately narrow, so a genuinely new fault on this box
is not suppressed with it — and to be deleted when the pack goes in rather than
left to expire.
Not in this PR
shivais not being scraped right now: the runningprometheuscontainer isbind-mounted from a deleted worktree, so its
file_sdtargets directory isempty. That is local container state, not a repo defect — a
make upfrom acurrent checkout restores it. It does mean the rules here are proven by unit
tests and by queries against retained history, not by a live firing alert.
🤖 Generated with Claude Code