fix(power): exempt charge-limit oneshot from start rate limiting
All checks were successful
Check / eval (push) Successful in 3m9s

Round 1 of #101's V3 failed on TuringMachine (2026-07-13 18:16): a
SPACED dock/AC event storm — each run finishing before the next event —
never coalesces, so 5 successful sub-second starts inside systemd's
default 10s window tripped start-limit-hit and marked the unit failed
(doctor badge) although every run succeeded. The write is idempotent
and cheap; rate limiting it only manufactures false failures, so the
unit sets StartLimitIntervalSec=0.

checks.battery-charge-limit now asserts the exemption and runs 8
sequential starts expecting a clean, non-failed state; the original
mid-run coalescing phase is unchanged.

Verified: V2 — the check passes under KVM (exit 0). V0 flake check
green. V3 round 2 re-queued in HARDWARE-QUEUE.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-13 18:26:00 +01:00
parent 720d9a38fb
commit f7246941e8
4 changed files with 60 additions and 14 deletions

View File

@@ -11,20 +11,20 @@ the **T14s** (webcam case).
## Any machine (dev box is fine)
- [ ] **#101 charge-limit service survives USB-C power-event bursts**
after updating/rebuilding to the commit carrying this entry, clear the
old failure with `sudo systemctl reset-failed
nomarchy-battery-charge-limit.service`. Plug and unplug the powered
USB-C dock twice, allowing five seconds after the last transition.
**Pass:** `systemctl show nomarchy-battery-charge-limit.service -p
ActiveState -p Result` reports `ActiveState=inactive` and
`Result=success`; `systemctl is-failed
nomarchy-battery-charge-limit.service` is false; BAT0's
`charge_control_end_threshold` still matches the configured/live limit;
and `journalctl -b -u nomarchy-battery-charge-limit.service` contains
no new `SIGTERM`, `start-limit-hit`, or `Failed with result` lines after
the rebuild. Capture that journal plus `udevadm monitor
--subsystem-match=power_supply` if it fails.
- [ ] **#101 charge-limit service survives USB-C power-event bursts**
(round 2) — round 1 FAILED on TuringMachine 2026-07-13 18:16: a
*spaced* storm (each run finished before the next AC event) landed 5
successful starts in 10s → `start-limit-hit`, unit marked failed,
doctor badge — although every run succeeded. Fixed by exempting the
unit from start rate limiting (`StartLimitIntervalSec=0`) + a
spaced-storm guard in `checks.battery-charge-limit` (the commit
carrying this entry). **Re-check:** after pulling/rebuilding this
commit, `sudo systemctl reset-failed nomarchy-battery-charge-limit
.service`, then plug/unplug the powered USB-C dock several times in
quick succession. **Pass:** `systemctl is-failed
nomarchy-battery-charge-limit.service` stays false, `Result=success`,
BAT0's `charge_control_end_threshold` still matches the limit, and the
journal has no new `start-limit-hit`/`Failed with result` lines.
- [ ] **Docking recovery round 4 (#100, closed-lid BenQ sequence)** — after
updating to the commit carrying this entry, run `nomarchy-home` and
relogin. With the lid open, put visible windows on at least workspaces

View File

@@ -19,6 +19,21 @@ Template:
---
## 2026-07-13 — charge-limit start-limit-hit on spaced AC storms (this commit)
- **Task:** Live V3 failure of #101 (TuringMachine doctor badge, 18:16): a
spaced dock/AC event storm → 5 successful starts in 10s →
`start-limit-hit`, unit marked failed though every run succeeded.
- **Did:** `unitConfig.StartLimitIntervalSec = 0` on
`nomarchy-battery-charge-limit` (idempotent sub-second write — rate
limiting only manufactures false failures); extended
`checks.battery-charge-limit` with the exemption assert + an 8× sequential
start loop and a clean-state check.
- **Verified:** V2 — the check passes under KVM (exit 0: exemption asserted,
8 spaced starts, not failed, no start-limit-hit; original coalescing
phase still green). V0 flake check green.
- **Pending:** V3 round 2 re-queued in HARDWARE-QUEUE (#101 entry updated).
- **Next suggestion:** land the in-flight #93/#99/#107 evidence run.
## 2026-07-13 — #102 Nextcloud Desktop opt-in in the template (this commit)
- **Task:** BACKLOG #102 — document upstream HM `services.nextcloud-client`
as a downstream-template opt-in; no Nomarchy wrapper.

View File

@@ -805,6 +805,29 @@
)
assert "start-limit-hit" not in service_log, service_log
assert "Failed with result" not in service_log, service_log
# SPACED storm (T14s, 2026-07-13): when each run finishes
# before the next AC event arrives nothing coalesces, and 5
# successful starts inside systemd's default 10s window mark
# the unit failed (start-limit-hit) although every run
# succeeded. The unit must stay exempt from start rate
# limiting assert the exemption, then prove repeated
# back-to-back starts leave a clean state.
machine.succeed(
'test "$(systemctl show -p StartLimitIntervalUSec --value '
'nomarchy-battery-charge-limit.service)" = 0'
)
machine.succeed(
"for i in $(seq 1 8); do "
"systemctl start nomarchy-battery-charge-limit.service; done"
)
machine.fail(
"systemctl is-failed --quiet nomarchy-battery-charge-limit.service"
)
service_log = machine.succeed(
"journalctl -b -u nomarchy-battery-charge-limit.service --no-pager"
)
assert "start-limit-hit" not in service_log, service_log
'';
};

View File

@@ -64,6 +64,14 @@ in
description = "Apply battery charge end threshold from state or config";
wantedBy = [ "multi-user.target" ];
path = [ pkgs.jq pkgs.coreutils ];
# A sustained dock/AC event storm can land SPACED starts — each run
# finishes (~1s) before the next event, so nothing coalesces and 5
# successful starts in 10s trip systemd's default start limit: the
# unit is marked failed (start-limit-hit) although every run
# succeeded (T14s, 2026-07-13; the #101 coalescing only covers
# events that arrive DURING a run). The write is idempotent and
# sub-second — exempt it from rate limiting.
unitConfig.StartLimitIntervalSec = 0;
serviceConfig = {
Type = "oneshot";
};