diff --git a/agent/HARDWARE-QUEUE.md b/agent/HARDWARE-QUEUE.md index 69aa97b..cf6c6fc 100644 --- a/agent/HARDWARE-QUEUE.md +++ b/agent/HARDWARE-QUEUE.md @@ -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 diff --git a/agent/JOURNAL.md b/agent/JOURNAL.md index e1ac31c..20bb88e 100644 --- a/agent/JOURNAL.md +++ b/agent/JOURNAL.md @@ -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. diff --git a/flake.nix b/flake.nix index e0d9883..086d6b6 100644 --- a/flake.nix +++ b/flake.nix @@ -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 ''; }; diff --git a/modules/nixos/power.nix b/modules/nixos/power.nix index 67d0861..c853b76 100644 --- a/modules/nixos/power.nix +++ b/modules/nixos/power.nix @@ -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"; };