docs(runbook): replace the laptop cell in prometheus (#454) - #497
Merged
Merged
Conversation
The repository half of #454 landed in #461 on 2026-09-12 and the cell was bought on 2026-09-13. What was never written down is the procedure that actually settles the issue: the fit itself, and the test that proves the property the cell was bought for. It existed as two sentences inside a UPS runbook and one bullet in hardware.md, for a glued pouch cell in the machine the whole observability stack runs on. This gives it its own page, modelled on replace-the-smart-storage-battery.md (baseline, fit, confirm the metrics moved, what is still open) with one section neither sibling needed: what goes blind while the lid is off. shiva and mjolnir are not the monitoring host. prometheus is, so the window has to be bounded, backed up, and measured afterwards rather than assumed — and the only signal that survives it is the off-host healthcheck, which is told to be left alone precisely because it will fire. No collector, and the reason is now in two places rather than one. Alloy's node collector already exports node_power_supply_* from both laptops. The issue body asked for energy_full and energy_full_design; neither exists on either machine, both report the charge_* family, and a collector written to that text would have emitted an empty file. The runbook names all three spellings that do not survive a guess — cyclecount not cycle_count, charge_* not energy_*, charge_ampere for what would be charge_now — because a wrong metric name returns an empty result, which on a command line is indistinguishable from the cell being gone. The mains pull is deliberately not silenced, twice. HostOnBattery is critical on the power route and will really page, and that is the point: verify-the-alert-path.md proves the transport with Watchdog, which is vector(1) and carries no labels, so nothing else in the estate exercises this rule's for:, its join against node_power_supply_info{type="Mains"}, or its routing. Running it once on the old cell before removal — which is also how a cell gets discharged before it comes out — means a failure after the swap is the cell or the adapter and nothing else. Both sibling runbooks record deleting a silence late as their single regret; a silence never created cannot be forgotten. Three existing pointers retargeted at the new page: the laptop-cell paragraph in fit-the-ups-battery.md, the A1437 accessory entry in hardware.md, and the host.rules.yaml row in observability.md. README's runbook count goes 25 to 26 in the same commit, because check_docs.py counts the glob and CI fails otherwise. Deliberately not touched. host.rules.yaml and host.test.yaml: HostBatteryHealthLow's description names fit-the-ups-battery.md, so the alert for a laptop cell points at the UPS runbook — correcting it means editing the rule and the exp_annotations block that asserts the string verbatim, which is a rules-and-tests change rather than a docs one, and it is recorded under What is still open. roadmap.md: a record, excluded from check_docs.py, and its entry moves when the cell is fitted rather than when this page lands. The dashboards carry no battery panel at all, which is a real gap and its own issue. Three baseline figures were corrected against the host before publishing. charge_ampere, voltage_volt and temp_celsius are live-varying, and an instantaneous sample was being written as though it were a fixed fact — temp_celsius in particular, which the safety section leans on. It ranges 32.7 to 39.2 over 24 hours averaging 33.3, so it is recorded as a band and the page says a single reading near 39 is ordinary. charge_full is confirmed static at 6.196 across the retained window, so it is a real baseline. Separately, HostBatteryHealthLow had been firing unsilenced for oracle since 2026-09-14 at 72 % of design, against a cell that is unbought and second in line. Silenced 01cb81d7-5e19-4e6d-b386-f5c8c843032b until 2026-10-08, sharing an expiry with the #351 disk silence on the same host so one look covers both. The silence and what it also hides — the rule is < 0.8 and no label carries the ratio, so further decay is invisible too — are recorded in the new runbook's status banner and its open items, rather than only in Alertmanager. Verified: python3 scripts/check_docs.py prints "docs OK — 82 Prometheus + 18 Loki rules, 7 dashboards, 141 panels, 10 assertions" and exits 0; reverting the README count to 25 makes the same command print "README.md:96 claims 25 runbooks; the repository has 26" and exit 1, so the bump is load-bearing rather than cosmetic. ./scripts/lint.sh passes all five linters, and markdownlint-cli2 reports 0 issues on the new file. Every metric name in the runbook was read back from this host's Prometheus on 2026-09-17 and all twelve return a count: charge_full 6.196, charge_full_design 6.6, charge_ampere 6.122, capacity 93, cyclecount 108, present 1, voltage_volt 12.436, voltage_min_design 11.21, temp_celsius 39.1 at the sample and 32.7-39.2 over 24h, current_ampere 0, online{power_supply="ADP1"} 1, and an info series carrying manufacturer SMP, model_name bq20z451, technology Li-ion, status Full. The silence was read back active with the alert reporting state suppressed. Refs #454 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Six pull requests landed on main while this branch was open. The only conflict was README.md's counted-claims bullet: main bumped the ADR count for ADR-0041 while this branch bumped the runbook count for replace-the-laptop-cell.md, and both edits are wanted. Resolved by keeping both intents and then reading the real numbers off the repository rather than trusting either side's arithmetic — the merge brings in two runbooks besides this branch's, so the true figures are 42 ADRs and 28 runbooks, not the 41 and 26 the two sides each believed. Not fixed here, because it is main's and not this branch's: two ADRs both claim the number 0041, so check_docs.py fails on main by itself, at fc4490e and at 84418ae, with this branch absent. Renumbering one of them is a decision about another change's content and does not belong in a runbook PR. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This was referenced Sep 17, 2026
Brings in #503, which renumbered the duplicate ADR-0041 to 0042 and was the only thing failing this branch's Validate job. main now carries 0041 for the CRS326 and 0042 for the remote path, and check_docs.py passes on it again. The conflict was README.md's counted-claims bullet once more, and for the same reason as the last merge: main had corrected the runbook count to 27 for its own additions while this branch held 28 for those plus this one. Resolved to 28 and then read off the repository rather than reasoned about — 42 ADR files and 28 runbook files, which is what check_docs.py now agrees with. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Gerrrt
added a commit
that referenced
this pull request
Sep 18, 2026
HostBatteryHealthLow reads a laptop cell's measured full capacity against its design figure, and fires today for oracle's BAT0 at 72 %. Its description sent the reader to docs/runbooks/fit-the-ups-battery.md, which is the rack pack in mjolnir: an APCRBC115 in a Smart-UPS, a self-test to watch and an NMC to read. None of that is what you do with a thirteen-year-old cell in a laptop on a shelf, so the one alert whose whole action is "order the replacement" pointed at the wrong hardware. It now names docs/runbooks/replace-the-laptop-cell.md, which carries the baseline to take first, the fit, the mains-pull test that proves it, the stack-down window and the disposal. Because that runbook covers the before as well as the after, the sentence reads "what to check before and after the swap" rather than only after. That runbook lands with #497, which is still open — this change should not merge ahead of it, or the annotation names a path that does not yet exist on main. The description is asserted verbatim in two exp_annotations blocks in tests/host.test.yaml, so the rule and both assertions move together. Line-wrapping differs between the two files and always has; the folded scalars were compared as strings to confirm they still produce identical text. HostOnBattery and HostBatteryNotReported were checked and left alone: neither names a runbook, so neither had a wrong one. Each already carries its own next step in prose — UpsOnBattery and node_power_supply_capacity for the first, /sys/class/power_supply for the second. The fit-the-ups-battery.md reference in the block's header comment also stays: it cites that document for stating the shelf's mains-cut design, which is still true, not for how to change a cell. Verified: `promtool test rules` on tests/host.test.yaml via docker with the pinned prom/prometheus:v3.14.0 image (the Makefile's check-rules target calls a bare `promtool`, which is not on PATH on this host) — SUCCESS. Reverting one of the two assertions to the old path was confirmed to FAIL the run, proving the annotation is genuinely asserted and not silently ignored. `./scripts/validate.sh` exits 0, all checks passed, including "observability: promtool test rules (11 file(s))"; the 3 skips are the systemd unit checks that only run in the deployment checkout. Refs #454 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The cell was delivered a few hours before this commit. Two documents said "in transit" and now do not. The baseline was re-read on the day it arrived rather than assumed to have held: charge_full 6.196 Ah of 6.6 Ah, 108 cycles, capacity 93 — identical to the 2026-09-17 figures the runbook's table records. So the table is current as it stands and does not need retaking before the fit, which the status banner now says outright, because the one thing that would quietly invalidate the whole comparison is a baseline taken at a different time from the one written down. hardware.md's entry also carried a condition that has now come due. It records the cell as compatible rather than genuine "until it is in hand", because the listing called it genuine while its brand field said unbranded. It is in hand, so that sentence can now be settled by looking at the pack instead of at the listing — the line is left as it is, deliberately, and says it stays that way until someone reads the markings on the cell itself. Recording it as an Apple part on the strength of a listing that contradicts itself would be the same error as trusting a UPS card's fabricated pack. Nothing is fitted and nothing is measured differently. What settles #454 is still the fit and the mains pull afterwards, so this is Refs and the issue stays open. Verified: python3 scripts/check_docs.py prints "docs OK — 82 Prometheus + 18 Loki rules, 7 dashboards, 141 panels, 10 assertions" and exits 0; ./scripts/lint.sh passes all five linters. Refs #454 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Gerrrt
force-pushed
the
gerrrt/prometheus-battery-monitoring-3b5e8f
branch
from
September 18, 2026 02:42
9c161bd to
a009709
Compare
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.
What changed
A runbook for swapping the A1437 cell in
prometheusand proving the swapworked —
docs/runbooks/replace-the-laptop-cell.md. Three existing pointersretargeted at it, and README's runbook count goes 25 to 26 in the same commit
because
check_docs.pycounts the glob.Nothing about the running stack changes. This is documentation plus one
Alertmanager silence, described below.
Why
The repository half of #454
landed in #461 on 2026-09-12 and
the cell was bought on 2026-09-13. The procedure that actually settles that
issue — the fit, and the mains-pull test that proves the property the cell was
bought for — existed only as two sentences inside a UPS runbook and one bullet
in
hardware.md, for a glued pouch cell in the machine the whole observabilitystack runs on.
The issue stays open. It is settled by the fit and the test, not by this page,
so this is
Refsand not a closing keyword.No collector, and the reason is now in two places rather than one. Alloy's
node collector already exports
node_power_supply_*from both laptops. Theissue body asked for
energy_full/energy_full_design; neither exists oneither machine — both report the
charge_*family — so a collector written tothat text would have emitted an empty file.
Two judgement calls worth reviewing:
HostOnBatteryis critical on the power route and will really page. That isthe point:
verify-the-alert-path.mdproves the transport withWatchdog,which is
vector(1)and carries no labels, so nothing else exercises thisrule's
for:, its join againstnode_power_supply_info{type="Mains"}, or itsrouting. Running it first on the old cell — which is also how a cell gets
discharged before removal — means a failure after the swap is the cell or the
adapter and nothing else. Both sibling battery runbooks record deleting a
silence late as their single regret.
off.
shivaandmjolnirare not the monitoring host.prometheusis, sono alert can fire or be recorded during the window, and the only signal that
survives it is the off-host healthcheck — which the runbook says to leave
alone precisely because it will fire.
Blast radius
secrets/*.sops.yamlNo rules, tests, compose files or scripts are touched, so nothing deploys and
no alert behaviour changes from the diff itself.
One live change, made outside the diff:
HostBatteryHealthLowhad beenfiring unsilenced for
oraclesince 2026-09-14 at 72 % of design, against acell that is unbought and second in line. Silenced
01cb81d7-5e19-4e6d-b386-f5c8c843032buntil 2026-10-08, sharing an expiry withthe #351 disk silence on the
same host so one look covers both. Because the rule is
< 0.8and no labelcarries the ratio, that silence hides any further decay of the cell as well as
the 72 % it was created for — recorded in the new runbook's status banner and
its open items rather than only in Alertmanager.
Verification
make validatepasses — see the note belowpython3 scripts/check_docs.pyprintsdocs OK — 82 Prometheus + 18 Loki rules, 7 dashboards, 141 panels, 10 assertionsand exits 0. Reverting theREADME count to 25 makes it print
README.md:96 claims 25 runbooks; the repository has 26and exit 1, so the bump is load-bearing rather thancosmetic.
./scripts/lint.shpasses all five linters and markdownlint-cli2reports 0 issues on the new file. Re-run after rebasing onto
fc4490e.The local
./scripts/validate.shrun was abandoned rather than completed.Thirteen other worktree sessions were running it concurrently against the
shared Docker daemon at load 36, and it had not finished after twenty minutes.
Since this commit touches no rule, test or config, its promtool stages cannot be
affected by the diff, and CI on a clean runner is the better authority — so the
box above is ticked on CI rather than on that run. Flagging it rather than
implying a green local run.
Every metric name in the runbook was read back from the host's own Prometheus
on 2026-09-17 and all twelve return a count. Three baseline figures were
corrected before publishing:
charge_ampere,voltage_voltandtemp_celsiusare live-varying, and an instantaneous sample was being recordedas though it were a fixed fact.
temp_celsiusin particular, which the safetysection leans on, ranges 32.7–39.2 over 24 hours averaging 33.3, so it is now a
band with a note that a single reading near 39 is ordinary.
charge_fullisconfirmed static at 6.196 across the retained window, so it is a real baseline.
Left for a separate issue
HostBatteryHealthLow's description namesdocs/runbooks/fit-the-ups-battery.md, so the alert for a laptop cell pointsat the UPS runbook. Correcting it means editing the rule and the
exp_annotationsblock that asserts the string verbatim — a rules-and-testschange, not a docs one. Recorded under What is still open.
The dashboards carry no battery panel at all:
power_supplyappears in noGrafana JSON, while every other hardware signal has one.
🤖 Generated with Claude Code