foremerge: schedule the stale-intent reap on vpn (kanban nix-config#945) #1971

Merged
hermes merged 1 commit from kb/NCFG-945-foremerge-reap-timer into main 2026-10-13 10:36:04 -06:00
Collaborator

Kanban card nix-config#945.

nix-config#944 shipped foremerge work reap; nothing ran it. This wires it to an hourly systemd unit+timer on vpn, beside foremerge-intent-advisory.

  • hosts/vpn/foremerge-stale-reap.py - dry-run by default, --apply from the unit; walks the fleet's four ledgers (nix-config, nix-config-private, clientsv, ymrtech); never passes --stale-seconds, so the binary's own three gates decide (owner silence, live claim lease, open ChangeSet).
  • hosts/vpn/default.nix - Type=oneshot as hermes, Nice=19, hourly timer; the driver is py_compile + --selftested at build time.
  • .forgejo/tests/test-foremerge-reap-timer.sh - pins the scheduler: the driver selftest, three mutation arms, the unit's exact ExecStart, and (with a real binary) the shipped argv over a throwaway ledger.
  • .forgejo/workflows/nix-build-check.yml - hermetic foremerge-reap-timer job plus a step in foremerge-accept-order, where the pinned foremerge store path exists.

A ledger that cannot be reached is journalled and the run still exits 0 (systemctl --failed feeds vpn's deploy health gate); only a missing binary is fatal.

Measured dry-run over the live ledgers before the change: nix-config scanned=22 would reap=0, nix-config-private 27/0, clientsv 11/0, ymrtech 3/0 - all retained by the binary's own open_changeset gate.

Kanban card nix-config#945. nix-config#944 shipped `foremerge work reap`; nothing ran it. This wires it to an hourly systemd unit+timer on vpn, beside `foremerge-intent-advisory`. - `hosts/vpn/foremerge-stale-reap.py` - dry-run by default, `--apply` from the unit; walks the fleet's four ledgers (nix-config, nix-config-private, clientsv, ymrtech); never passes `--stale-seconds`, so the binary's own three gates decide (owner silence, live claim lease, open ChangeSet). - `hosts/vpn/default.nix` - Type=oneshot as hermes, Nice=19, hourly timer; the driver is py_compile + `--selftest`ed at build time. - `.forgejo/tests/test-foremerge-reap-timer.sh` - pins the scheduler: the driver selftest, three mutation arms, the unit's exact ExecStart, and (with a real binary) the shipped argv over a throwaway ledger. - `.forgejo/workflows/nix-build-check.yml` - hermetic `foremerge-reap-timer` job plus a step in `foremerge-accept-order`, where the pinned foremerge store path exists. A ledger that cannot be reached is journalled and the run still exits 0 (`systemctl --failed` feeds vpn's deploy health gate); only a missing binary is fatal. Measured dry-run over the live ledgers before the change: `nix-config scanned=22 would reap=0`, `nix-config-private 27/0`, `clientsv 11/0`, `ymrtech 3/0` - all retained by the binary's own `open_changeset` gate.
foremerge: schedule the stale-intent reap on vpn (kanban nix-config#945)
All checks were successful
PR base guard / Base drift / conflict check (pull_request) Successful in 4s
NixOS Build Check / Scope changed deployable hosts (pull_request) Successful in 7s
NixOS Build Check / Flake input regression guard (pull_request) Successful in 8s
Secret Scan / gitleaks scan (pull_request) Successful in 10s
NixOS Build Check / Build changed host closures (native, one eval) (pull_request) Successful in 41s
NixOS Build Check / Worker-scope reaper worker signature (mutation-tested) (pull_request) Successful in 3s
NixOS Build Check / Kanban pool capacity knob (runtime override + clamp, mutation-tested) (pull_request) Successful in 2s
NixOS Build Check / Sentinel pass budget contract (prompt forbids the full board walk, mutation-tested) (pull_request) Successful in 2s
NixOS Build Check / CI-scratch reaper class table (mutation-tested) (pull_request) Successful in 5s
NixOS Build Check / Hermes runtime gates (one eval of vpn's runtime) (pull_request) Successful in 1m6s
NixOS Build Check / Scheduled foremerge reap command line (mutation-tested) (pull_request) Successful in 4s
NixOS Build Check / .forgejo/tests python suites (hermetic) (pull_request) Successful in 37s
NixOS Build Check / foremerge accept-order contract (real binary) (pull_request) Successful in 1m11s
NixOS Build Check / Repo gates (the folded selftest battery) (pull_request) Successful in 2m49s
foremerge intent (advisory) all 4 changed path(s) covered by a live foremerge intent
b77cd7d8e0
nix-config#944 shipped `foremerge work reap` and nothing ran it: the verb is
CLI-only on purpose ("a GC is an operator or scheduled action, not a worker's
own lifecycle verb"), so without a clock the four fleet ledgers refill with
ghosts exactly as before the reap existed and the collision signal a worker
reads to decide "is someone else here?" goes noisy again -- the state #944 was
filed from (148 dead open intents against 23 live ones; 73 open conflicts on
hosts/vpn/default.nix alone, every one against an intent whose owner had no
session anywhere).

hosts/vpn/foremerge-stale-reap.py drives it. Dry-run is the DEFAULT; the
systemd unit is the one caller that passes --apply, so a reap is always the
explicit path and a stray manual run reports without changing a ledger. It
walks the fleet's four ledgers (nix-config, nix-config-private, clientsv,
ymrtech), skips a repo whose clone does not exist yet, and runs exactly
`foremerge --cwd <clone> --json work reap [--dry-run]`.

The GATES stay in the binary. The driver never passes --stale-seconds, so the
fleet reaps at the ledger's own threshold and a live claim lease or a ChangeSet
on the acceptance ladder withholds the reap there -- the three gates
`.forgejo/tests/test-foremerge-stale-reap.sh` already pins.

A ledger that cannot be reached is journalled and the run still exits 0:
`systemctl --failed` is an input to vpn's deploy health gate, and a GC that
cannot read one clone is the state before #944, not an incident. Only a missing
binary is fatal.

The service is a Type=oneshot as hermes, Nice=19 / IOSchedulingClass=idle beside
the existing foremerge-intent-advisory unit, on an hourly timer
(OnBootSec 20min, RandomizedDelaySec 5min). The driver is py_compile'd and
`--selftest`ed at build time (hermetic: the binary is stubbed), so a broken
driver reds a PR instead of a timer that silently stops reaping.

.forgejo/tests/test-foremerge-reap-timer.sh pins the half nothing else did --
the SCHEDULER. Part 1 runs the driver's selftest; part 2 mutates a copy of the
driver three ways (the dry-run default, the fleet repo list, the
no---stale-seconds rule) and requires each copy to go RED naming the rule it
lost; part 3 pins the unit's ExecStart by exact text (with a mutation proving
the pin is anchored on the real line) plus the build-time selftest and the
timer; part 4, when a foremerge binary is on PATH, runs the SHIPPED argv against
the pinned binary over a throwaway ledger: it is accepted (scanned=1), an
owner-present intent is RETAINED (reaped=0 -- the timer cannot eat a live
worker), a second apply run over the same ledger also reaps 0, and the fixture
intent's status is unchanged.

Wired as the `foremerge-reap-timer` job in nix-build-check.yml (hermetic, no
host condition: a tests-only diff scopes no host) and as a step of
foremerge-accept-order, where the pinned foremerge store path exists, so one
build carries both verdicts.

Co-Authored-By: Hermes Agent <noreply@nousresearch.com>
Author
Collaborator

foremerge intent coverage (advisory) — 2 of 4 changed path(s) in ymrtech/nix-config#1971 have no live foremerge intent:

  • .forgejo/tests/test-foremerge-reap-timer.sh
  • hosts/vpn/foremerge-stale-reap.py

Coverage was decided by reading the repo's foremerge ledger (43 in-flight intent(s) considered) against this PR's changed paths — no branch or session name is involved, so a force-push does not change the answer. Paths that legitimately need no claim (docs, .md, flake.lock) are excluded.

This is advisory and does not block the merge. To clear it, publish intent for the paths above (foremerge intent publish --agent <your agent id> --task <task> --summary <what and why> --scope file:<path>=modify), or ignore it if the work genuinely collides with nobody.

**foremerge intent coverage (advisory)** — 2 of 4 changed path(s) in ymrtech/nix-config#1971 have no live foremerge intent: - `.forgejo/tests/test-foremerge-reap-timer.sh` - `hosts/vpn/foremerge-stale-reap.py` Coverage was decided by reading the repo's foremerge ledger (43 in-flight intent(s) considered) against this PR's changed paths — no branch or session name is involved, so a force-push does not change the answer. Paths that legitimately need no claim (docs, `.md`, `flake.lock`) are excluded. This is advisory and does **not** block the merge. To clear it, publish intent for the paths above (`foremerge intent publish --agent <your agent id> --task <task> --summary <what and why> --scope file:<path>=modify`), or ignore it if the work genuinely collides with nobody.
Author
Collaborator

foremerge intent coverage (advisory) — update for ymrtech/nix-config#1971: all 4 claimable changed path(s) are now covered by a live foremerge intent. Nothing to do; this comment closes the earlier gap warning.

**foremerge intent coverage (advisory)** — update for ymrtech/nix-config#1971: all 4 claimable changed path(s) are now covered by a live foremerge intent. Nothing to do; this comment closes the earlier gap warning.
hermes merged commit 5194c53b13 into main 2026-10-13 10:36:04 -06:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
ymrtech/nix-config!1971
No description provided.