feat(daemon): quiesce request that retires an idle daemon atomically

Merged
TH
10h ago
+688-15

Summary

Layer 1 of 3 for #3754 (umbrella #3747): the daemon half of replacing an incompatible daemon safely.

  • A daemon now answers daemon_quiesce, authenticated with its token and fenced to its pid and start time. It closes admission and counts in-flight requests, sessions, repair sessions and live leases in one synchronous step. Any of them refuses with daemon-busy and reopens admission; otherwise it answers draining and exits.
  • A remote-origin request is refused. The request is answered before dispatch, so it works on the socket and HTTP transports.
  • The daemon publishes capabilities: ['quiesce'] in daemon.json; a daemon that predates it omits the field.
  • Shared vocabulary lives in daemon-resolution.ts, and the daemon side in daemon-idle-reap.ts, because both are already in the eager closures. ADR 0006 gains a short section, including what the census does not count (uploads, lease-less holds).

No client behavior changes here. 10 files. The last PR in the stack closes #3754.

Validation

pnpm check:affected --run --head ad938223f9: passed (format, lint, typecheck, layering, fallow, build, vitest-related). New runtime tests boot the real daemon and quiesce it over its socket and HTTP: idle drain, busy refusal per cause with admission reopened, wrong token, pid and start-time fences, remote-origin refusal.

🤖 Generated with Claude Code