t355: rebase idle-sleep guard PR #355 onto current main #374

Closed
agent wants to merge 0 commits from worker/t355-8b321c-1 into main
Member

What this is

Rebases the idle-sleep guard (originally origin/worker/sleepguard-82076d-1 commit
130962e) onto current main. That branch was 39 commits behind and no longer merged
cleanly — three conflicts, all in config-record plumbing that grew new fields on main
in the meantime (memberSkills, fleetd #362) while the branch grew its own
(idleSleepGuard).

Why the guard exists

A fleetd host was measured idle-sleeping after as little as one minute idle. Overnight
the daemon's AMQP link dropped 13 times, and every drop minute had a sleep or wake event
in pmset -g log in the same minute or the one before — a member mid-turn freezes with
the host. IdleSleepGuard (dev.ltms.fleet.power) holds a macOS caffeinate -i OS-level
assertion for exactly as long as at least one member is live, wired through
SessionManager's existing onAcquire/onRelease hooks. New idleSleepGuard: config
block, on by default.

Conflict resolution

main was treated as the source of truth throughout — nothing main added was reverted.

  • ConfigRef.java — auto-merged everywhere except one javadoc paragraph both sides
    edited (the "denominator" count of FleetConfig's top-level components). Merged the
    prose and corrected the count to 24 components / 13 deferred (was 23/12 on main
    alone), matching the record's real shape after idleSleepGuard was added on top of
    main's own memberSkills.
  • FleetConfig.java — main had already appended memberSkills as the 24th
    canonical-constructor argument with its own back-compat overload (the standard pattern
    in this file: append at the end, add a back-compat constructor at the old arity). Took
    main's shape and appended idleSleepGuard as the 25th argument the same way, with its
    own "before idleSleepGuard: was added" back-compat constructor forwarding to the
    memberSkills-only one. Same treatment for the two other places that enumerate every
    top-level key by hand: KNOWN_TOP_LEVEL_KEYS and withDefaults()'s final constructor
    call (verified this uses the true 24-arg canonical constructor, not an accidentally
    narrower back-compat overload — see below).
  • ConfigRefTopLevelReportingCoverageTest.java — both sides added a BASE/ALT map
    entry for their own new field (memberSkills on main, idleSleepGuard on the
    branch). Kept both.

A test that only exists on main and needed the same fix

FleetConfigWithDefaultsPreservesEveryComponentTest (not part of the original conflict
set — it's a new file on main, unrelated to this branch) exists specifically to catch
the exact hazard this rebase risks: withDefaults()'s final return new FleetConfig(...)
call is written at a literal arg count, and adding a component's back-compat constructor
at the old arity can silently make a stale call rebind to it, defaulting the new field
to null on every load. Its own class javadoc says this happened live on this exact
sleep-guard branch once already. Its baseValues() map didn't know about
idleSleepGuard yet, so the coverage test itself failed on drift. Added a real value for
it (separate commit, 6442a58) — confirmed the actual withDefaults() call is the
correct 24-arg canonical form, not a back-compat overload, so the hazard this test guards
against did not reproduce here.

Build

mvn clean install from fleetd/, run unpiped, full output read (not just the tail):

Tests run: 1439, Failures: 0, Errors: 0, Skipped: 0
BUILD SUCCESS

Lead's baseline on main this turn was 1425 tests, 0 failures. 1439 − 1425 = 14, which
matches exactly: CaffeinateSleepAssertionMechanismTest (4) + IdleSleepGuardTest (5) +
IdleSleepGuardWiringTest (1) + FleetConfigTest's 4 new idleSleepGuard* tests = 14 —
all four counted individually from the source, not assumed.

Test coverage from the branch — every test still present and green

  • dev.ltms.fleet.power.CaffeinateSleepAssertionMechanismTest — 4 tests, all pass
  • dev.ltms.fleet.power.IdleSleepGuardTest — 5 tests, all pass
  • dev.ltms.fleet.power.IdleSleepGuardWiringTest — 1 test, passes
  • FleetConfigTest.idleSleepGuard* — 4 tests, all pass

None deleted, none weakened.

power package — untouched

git diff 130962e 24b96d2 -- fleetd/src/main/java/dev/ltms/fleet/power/ fleetd/src/test/java/dev/ltms/fleet/power/ is empty — byte-for-byte identical to the
original branch commit. Only the config-record plumbing changed to accommodate main's
new fields.

IdleSleepGuardWiringTest — confirmed it actually asserts the wiring

It is not a construction-only test. It builds a real SessionManager (FakeHerdr-backed),
wires it to a real IdleSleepGuard through the exact two lines Fleetd.main uses
(sessions.onAcquire(_ -> guard.recheck()) / sessions.onRelease(...)), then drives real
acquire()/release() calls and asserts guard.isHeld() flips false→true on the first
acquire and true→false on the last release, plus asserts the underlying mechanism's
acquire/close call counts. That is a real proof of the wiring, not just object
construction.

One thing to report, not fix (per the brief)

CaffeinateSleepAssertionMechanism.acquire() checks isSupportedPlatform() (looks for
"mac" in os.name) before ever starting a process. On Linux (e.g. fleet01) this returns
false immediately: acquire() returns null (a no-op SleepAssertion), logging once
at INFO ("idle-sleep guard: not running on macOS ... the idle-sleep guard is a no-op on
this platform") — never repeated, never a throw, never an ERROR/WARN. It degrades
cleanly.

Same shape spotted elsewhere (not fixed, one line per the brief)

The mechanism's own javadoc already flags a second gap of the identical shape right next
to the code this PR touches: caffeinate -i only blocks idle sleep — a laptop lid
closing (or an operator-requested sleep) still sleeps the host out from under a live
member's turn, unaddressed by this guard.

Caveats for review

  • The "24 components / 13 deferred" recount in ConfigRef.java's class javadoc is my own
    arithmetic from reading DEFERRED_KEYS/SPLIT_KEYS/COLD_KEYS and the record's actual
    component list — worth a second pair of eyes given how many times this exact
    denominator has drifted on this file (per the file's own history: fleetd #330/#333/#337).
  • No code in dev.ltms.fleet.power needed to change for the rebase — this PR is
    config-plumbing conflict resolution only.
## What this is Rebases the idle-sleep guard (originally `origin/worker/sleepguard-82076d-1` commit `130962e`) onto current `main`. That branch was 39 commits behind and no longer merged cleanly — three conflicts, all in config-record plumbing that grew new fields on `main` in the meantime (`memberSkills`, fleetd #362) while the branch grew its own (`idleSleepGuard`). ## Why the guard exists A fleetd host was measured idle-sleeping after as little as one minute idle. Overnight the daemon's AMQP link dropped 13 times, and every drop minute had a sleep or wake event in `pmset -g log` in the same minute or the one before — a member mid-turn freezes with the host. `IdleSleepGuard` (`dev.ltms.fleet.power`) holds a macOS `caffeinate -i` OS-level assertion for exactly as long as at least one member is live, wired through `SessionManager`'s existing `onAcquire`/`onRelease` hooks. New `idleSleepGuard:` config block, on by default. ## Conflict resolution `main` was treated as the source of truth throughout — nothing `main` added was reverted. - **`ConfigRef.java`** — auto-merged everywhere except one javadoc paragraph both sides edited (the "denominator" count of `FleetConfig`'s top-level components). Merged the prose and corrected the count to 24 components / 13 deferred (was 23/12 on `main` alone), matching the record's real shape after `idleSleepGuard` was added on top of `main`'s own `memberSkills`. - **`FleetConfig.java`** — `main` had already appended `memberSkills` as the 24th canonical-constructor argument with its own back-compat overload (the standard pattern in this file: append at the end, add a back-compat constructor at the old arity). Took `main`'s shape and appended `idleSleepGuard` as the 25th argument the same way, with its own "before `idleSleepGuard:` was added" back-compat constructor forwarding to the `memberSkills`-only one. Same treatment for the two other places that enumerate every top-level key by hand: `KNOWN_TOP_LEVEL_KEYS` and `withDefaults()`'s final constructor call (verified this uses the true 24-arg canonical constructor, not an accidentally narrower back-compat overload — see below). - **`ConfigRefTopLevelReportingCoverageTest.java`** — both sides added a `BASE`/`ALT` map entry for their own new field (`memberSkills` on `main`, `idleSleepGuard` on the branch). Kept both. ## A test that only exists on `main` and needed the same fix `FleetConfigWithDefaultsPreservesEveryComponentTest` (not part of the original conflict set — it's a new file on `main`, unrelated to this branch) exists specifically to catch the exact hazard this rebase risks: `withDefaults()`'s final `return new FleetConfig(...)` call is written at a literal arg count, and adding a component's back-compat constructor at the *old* arity can silently make a stale call rebind to it, defaulting the new field to `null` on every load. Its own class javadoc says this happened live on this exact sleep-guard branch once already. Its `baseValues()` map didn't know about `idleSleepGuard` yet, so the coverage test itself failed on drift. Added a real value for it (separate commit, `6442a58`) — confirmed the actual `withDefaults()` call is the correct 24-arg canonical form, not a back-compat overload, so the hazard this test guards against did not reproduce here. ## Build `mvn clean install` from `fleetd/`, run unpiped, full output read (not just the tail): ``` Tests run: 1439, Failures: 0, Errors: 0, Skipped: 0 BUILD SUCCESS ``` Lead's baseline on `main` this turn was 1425 tests, 0 failures. 1439 − 1425 = 14, which matches exactly: `CaffeinateSleepAssertionMechanismTest` (4) + `IdleSleepGuardTest` (5) + `IdleSleepGuardWiringTest` (1) + `FleetConfigTest`'s 4 new `idleSleepGuard*` tests = 14 — all four counted individually from the source, not assumed. ## Test coverage from the branch — every test still present and green - `dev.ltms.fleet.power.CaffeinateSleepAssertionMechanismTest` — 4 tests, all pass - `dev.ltms.fleet.power.IdleSleepGuardTest` — 5 tests, all pass - `dev.ltms.fleet.power.IdleSleepGuardWiringTest` — 1 test, passes - `FleetConfigTest.idleSleepGuard*` — 4 tests, all pass None deleted, none weakened. ## `power` package — untouched `git diff 130962e 24b96d2 -- fleetd/src/main/java/dev/ltms/fleet/power/ fleetd/src/test/java/dev/ltms/fleet/power/` is empty — byte-for-byte identical to the original branch commit. Only the config-record plumbing changed to accommodate `main`'s new fields. ## `IdleSleepGuardWiringTest` — confirmed it actually asserts the wiring It is not a construction-only test. It builds a real `SessionManager` (FakeHerdr-backed), wires it to a real `IdleSleepGuard` through the exact two lines `Fleetd.main` uses (`sessions.onAcquire(_ -> guard.recheck())` / `sessions.onRelease(...)`), then drives real `acquire()`/`release()` calls and asserts `guard.isHeld()` flips false→true on the first acquire and true→false on the last release, plus asserts the underlying mechanism's acquire/close call counts. That is a real proof of the wiring, not just object construction. ## One thing to report, not fix (per the brief) `CaffeinateSleepAssertionMechanism.acquire()` checks `isSupportedPlatform()` (looks for "mac" in `os.name`) before ever starting a process. On Linux (e.g. fleet01) this returns `false` immediately: `acquire()` returns `null` (a no-op `SleepAssertion`), logging once at INFO ("idle-sleep guard: not running on macOS ... the idle-sleep guard is a no-op on this platform") — never repeated, never a throw, never an ERROR/WARN. It degrades cleanly. ## Same shape spotted elsewhere (not fixed, one line per the brief) The mechanism's own javadoc already flags a second gap of the identical shape right next to the code this PR touches: `caffeinate -i` only blocks *idle* sleep — a laptop lid closing (or an operator-requested sleep) still sleeps the host out from under a live member's turn, unaddressed by this guard. ## Caveats for review - The "24 components / 13 deferred" recount in `ConfigRef.java`'s class javadoc is my own arithmetic from reading `DEFERRED_KEYS`/`SPLIT_KEYS`/`COLD_KEYS` and the record's actual component list — worth a second pair of eyes given how many times this exact denominator has drifted on this file (per the file's own history: fleetd #330/#333/#337). - No code in `dev.ltms.fleet.power` needed to change for the rebase — this PR is config-plumbing conflict resolution only.
agent added 2 commits 2026-09-07 15:27:07 +02:00
Adds a small IdleSleepGuard (dev.ltms.fleet.power) that holds a macOS
caffeinate -i child while at least one fleet member is live, and
releases it once none are. It hangs off SessionManager's existing
onAcquire/onRelease hooks and SessionManager#size() rather than
tracking members a second way. New idleSleepGuard: config block,
on by default, following the FleetConfig.Health/ConfigReload pattern.
t355: give the withDefaults() coverage test a value for idleSleepGuard
CI / contract (pull_request) Successful in 53s
CI / build (pull_request) Successful in 2m6s
6442a583ae
FleetConfigWithDefaultsPreservesEveryComponentTest was added on main after
the sleep-guard commit was cherry-picked, specifically to catch this exact
rebase hazard (a withDefaults() call silently rebinding to a stale-arity
back-compat constructor). Its baseValues() map didn't know about the new
idleSleepGuard component yet, so the coverage test itself failed the
name-drift check. Add a real, non-null value for it, consistent with how
the sibling ConfigRefTopLevelReportingCoverageTest already covers it.
Owner

Merged to main as 127e683.

The rebase was the job here, and it is clean: the three conflicts were all config plumbing (ConfigRef, FleetConfig, ConfigRefTopLevelReportingCoverageTest), and the power package is byte-identical to the original branch commit.

Verified on merge, measured rather than taken from the report:

  • mvn clean install → Tests run: 1439, Failures: 0, Errors: 0, Skipped: 0 — BUILD SUCCESS. That is 1425 on main plus the 14 new ones.
  • The denominator recount, which the report flagged as its own weakest number because this count has drifted three times before (#330 / #333 / #337). I counted it mechanically instead of reading it: FleetConfig has 24 canonical record components; COLD_KEYS 5, DEFERRED_KEYS 13, SPLIT_KEYS 3, and the javadoc names 3 more as hot-excluded (placement, memberCredentials, memberLoginShell). 5+13+3+3 = 24. The javadoc at ConfigRef.java:139 is correct.

One correction to the report's prose: it calls idleSleepGuard the 25th constructor argument while also calling the constructor "the true 24-arg canonical" one. It is the 24th. The code is right; only the sentence was off by one.

Mutation run on merge. I picked the half that was verified by reading rather than by proving — the report says it "explicitly verified" that withDefaults()'s final call binds the true canonical constructor. So I dropped the trailing idleSleepGuard argument, making the call silently bind the 23-arg back-compat overload. It still compiles, which is the whole hazard. Caught:

Tests run: 1439, Failures: 1, Errors: 3 — BUILD FAILURE
FleetConfigWithDefaultsPreservesEveryComponentTest:
  "withDefaults() component-survival coverage — 24 components, 24 checked, 0 excluded, 23 survived"
  withDefaults() silently dropped these real, given components: [idleSleepGuard: ... returned null]
plus 3 errors in FleetConfigTest.idleSleepGuard{ExplicitlyDisabled,ExplicitlyEnabled,BlockPresentButEmpty}

It names the dropped component and prints its own denominator, which is what this guard is for. Worth saying why that matters: FleetConfigWithDefaultsPreservesEveryComponentTest was added to main after this exact defect happened live. Adding the missing entry to it is what makes the guard cover this component at all — without that edit the new component would have been invisible to the very test that exists to catch it.

Documented in the wiki Features page: The host no longer idle-sleeps while a member is working — the knob, the 13 overnight AMQP drops that motivated it, and the three by-design gotchas (macOS-only, -i is idle sleep only, and it fails safe and silently, so "enabled" is not proof the host is awake).

Linux is a clean no-op, so fleet01 does not get this fix — filed as a follow-up.

Merged to `main` as `127e683`. The rebase was the job here, and it is clean: the three conflicts were all config plumbing (`ConfigRef`, `FleetConfig`, `ConfigRefTopLevelReportingCoverageTest`), and the `power` package is byte-identical to the original branch commit. **Verified on merge, measured rather than taken from the report:** - `mvn clean install` → `Tests run: 1439, Failures: 0, Errors: 0, Skipped: 0` — BUILD SUCCESS. That is 1425 on main plus the 14 new ones. - The denominator recount, which the report flagged as its own weakest number because this count has drifted three times before (#330 / #333 / #337). I counted it mechanically instead of reading it: `FleetConfig` has **24** canonical record components; `COLD_KEYS` 5, `DEFERRED_KEYS` 13, `SPLIT_KEYS` 3, and the javadoc names 3 more as hot-excluded (`placement`, `memberCredentials`, `memberLoginShell`). 5+13+3+3 = 24. **The javadoc at `ConfigRef.java:139` is correct.** One correction to the report's prose: it calls `idleSleepGuard` the *25th* constructor argument while also calling the constructor "the true 24-arg canonical" one. It is the **24th**. The code is right; only the sentence was off by one. **Mutation run on merge.** I picked the half that was verified by *reading* rather than by proving — the report says it "explicitly verified" that `withDefaults()`'s final call binds the true canonical constructor. So I dropped the trailing `idleSleepGuard` argument, making the call silently bind the 23-arg back-compat overload. **It still compiles, which is the whole hazard.** Caught: ``` Tests run: 1439, Failures: 1, Errors: 3 — BUILD FAILURE FleetConfigWithDefaultsPreservesEveryComponentTest: "withDefaults() component-survival coverage — 24 components, 24 checked, 0 excluded, 23 survived" withDefaults() silently dropped these real, given components: [idleSleepGuard: ... returned null] plus 3 errors in FleetConfigTest.idleSleepGuard{ExplicitlyDisabled,ExplicitlyEnabled,BlockPresentButEmpty} ``` It names the dropped component and prints its own denominator, which is what this guard is for. Worth saying why that matters: `FleetConfigWithDefaultsPreservesEveryComponentTest` was added to `main` *after* this exact defect happened live. Adding the missing entry to it is what makes the guard cover this component at all — without that edit the new component would have been invisible to the very test that exists to catch it. Documented in the wiki Features page: *The host no longer idle-sleeps while a member is working* — the knob, the 13 overnight AMQP drops that motivated it, and the three by-design gotchas (macOS-only, `-i` is idle sleep only, and it fails safe and silently, so "enabled" is not proof the host is awake). Linux is a clean no-op, so **fleet01 does not get this fix** — filed as a follow-up.
ltms closed this pull request 2026-09-07 15:38:19 +02:00
Some checks are pending
CI / contract (pull_request) Successful in 53s
CI / build (pull_request) Successful in 2m6s

Pull request closed

Sign in to join this conversation.