SIGN IN SIGN UP

feat(daemon): a stop that names its reason, and an inventory that cannot drift

§7's shutdown choreography, taking the shape from exonum's `ApiManager` rather
than transliterating `serve.zig`.

**Teardown moves to the outer function.** `ApiManager::run` splits into `run`
and `run_inner` so the servers stop "in any case" — every early return from the
loop still tears down. `Supervisor::shutdown` consuming `self` already made the
ordering unforgeable at the TYPE level; nothing made it unforgeable at the call
site, where one `?` on an error path is enough to skip it. `Daemon::run` now
performs the whole sequence and there is no other way to stop.

**Three ways to stop, not one.** `serve.zig` models a signal arriving. exonum
selects over the signal AND the server's own termination; habitat's supervisor
returns a `shutdown_mode` from its loop. Both are right, and a daemon that waits
only for a signal HANGS when its listener dies of something else — a lost bind,
an accept loop that returned. That process is unkillable except by SIGKILL and
explains nothing on the way out. `StopCause` names which happened, and
`Outcome::is_clean` is false for a server that fell over however clean the join
was.

**The two Zig shutdown flags are retired, not ported.** They exist because a
watcher thread polling every 100ms cannot tell "the signal arrived" from "the
server stopped" without a second flag. In `Daemon::run` they are statements in
order, so there is nothing to race. The `select!` is `biased` so the answer is a
fact about the futures rather than about tokio's branch order —
`test_boot_window_sigterm` fails if the arms are swapped, which is the mutation
that proves the assertion is about the ordering and not about luck. It also
asserts the server was NEVER POLLED: the signal had already arrived, and that
window is precisely what the two flags protected.

**The task inventory is code.** Dimension 7.5 asks that every `concurrency.md`
thread-map row be supervised or explicitly deferred to a named milestone.
`inventory.rs` carries all eleven with a `Disposition`, and the suite asserts
the row count and that no row is silent — a deferral without a milestone is an
omission wearing a label. Two are supervised (`hub_pump`, `otlp_export`), seven
are deferred with milestones, and two are RETIRED because the thread existed
only to work around the absence of async:

- the signal watcher, above;
- the M139 deadline scheduler — a treap-backed registration map and a worker
  thread so one thread could interrupt another's blocked socket. `timeout` at
  the call site is the same guarantee with no shared map to keep consistent and
  no generation check to get wrong.

`docs/architecture/concurrency.md` gains the Rust task map beside the Zig thread
map, and the doc and the table are checked against each other rather than one
being prose about the other.

Measured, `cargo llvm-cov -p agentsfleetd --all-features`:

    lines 100.00% · functions 100.00% · regions 99.12%

Four uncovered regions, all `?` paths in `Display` impls that cannot be taken
because writing to a `String` cannot fail. The `log`-bridge trap bit once more
and is fixed at the source: an `error_code` field calling `.as_str()` inline was
hoisted to a `let`.

    make lint-rustd  →  ✓ [rustd] Lint passed
    bash audits/ufs.sh  →  OK: no violations across 2369 file(s)
    cargo test -p agentsfleetd --all-features  →  8 suites, 0 failed

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
K
Kishore Kumar committed
fc8093b3f32f7f89d24674bcd0b9bac4d38cc2a1
Parent: c6d3899