refactor(daemon): move CLI-shared daemon modules out of the daemon package and gate CLI reachability (R78)
Summary
L1b of the daemon single-entrypoint stack, on top of #3616: the CLI process reaches no module of @agent-device/daemon, and R78 now proves it by reachability instead of a folder ban.
- Modules moved out of the daemon (git moves; tests travel with them where the test fixtures allow):
daemon-contracts: the device-claim cluster (record, store, inspection, reboot, settlement, claims),session-artifact-paths(with the managed-path check),daemon-stop, and the claim reconciler + owner-scoped composer + recovery platform scope.capture-kit: the app-log durable resource, its store and recovery, and the app-log file policy (they sit with the other durable captures).kernel/device:decodeDeviceIdentity(was in capture-kit), sodaemon-contractsdoes not depend on capture-kit for claim records.DaemonCommandRouteis read fromcommand-registry.
device release --stalecomposes from root platform runtimes (src/platform-runtime-claim-recovery.ts) and reaches no daemon module.src/cli/commands/device-release.tsis gone.- R78 (
daemon-client-entry) keeps the folder ban oversrc/cli.ts,src/cli/,src/client/,src/remote/,src/daemon-client/and adds a breadth-first walk over static and dynamic edges fromsrc/bin.ts/src/cli.ts. The test plants the exact laundering chaincli.ts → cli/commands/device.ts → platform-runtime-stale-claim-release.ts → daemon, a type-only hop that must not count, and a stale-entry case. On the first run it also foundplatform-runtime-app-log-output.ts/-process.tsimportingapp-log-filesandmanaged-session-artifact-pathfrom the daemon; both moved too. - ADR 0032/0033 stop describing R78 as recording edges, and the
AGENTS.mdsession-artifact-paths pointer follows the move.
Subpath exports
| Package | Before | After |
|---|---|---|
daemon | 118 | 107 |
daemon-contracts | 12 | 17 (device-claims, device-claims-fixtures, device-claim-recovery, daemon-stop, session-artifact-paths) |
capture-kit | 80 | 83 (app-log-durable-resource, app-log-resource-recovery, app-log-files) |
kernel, host-kit, test-kit | 22 / 20 / 18 | unchanged |
Net: 210 → 207. The device-claim cluster is one explicit-name entry (device-claims) instead of seven files exported separately. Two judgment calls to review:
daemon-stopkeeps its own subpath. It lazily importsdaemon-registration-owner, and its test mocks that seam, so merging it into the owner module (or re-exporting it from one) would either make a static cycle or break the test.- The reconciler lives in
daemon-contracts, which depends oncapture-kit. The reverse (capture-kit → daemon-contracts) is a TypeScript project-reference cycle throughcommand-registry → selectors → capture-kit, and there is no package above both. The dependency stays acyclic; if the layering intent is "contracts below capture", the reconciler needs a new package, which I did not create.
Eager closure
scripts/__tests__/eager-closure-budgets.test.ts passes with no growth on any existing entry. Three new entries carry APPROVED_OVER_CEILING rows (app-log-durable-resource.ts, device-claim-recovery.ts, device-claims.fixtures.ts): each is code moved verbatim from a daemon entry of the same weight, and only the daemon and the lazily loaded device release --stale path import them. The first draft widened capture-kit's root barrel (32 → 63 modules) and daemon-resolution; both were reverted to separate entries for that reason. Four tests that need test-kit stay under packages/daemon (test-kit depends on daemon-contracts, and capture-kit/daemon-contracts tests cannot import it): the claim, settlement, reconciliation and app-log-files tests exercise the moved modules through their public entries.
Diff size
118 files, +744 −582 over #3616: over the 1,000-line budget. Most of it is moves counted twice (rename detection does not pair the merged or split files: the kernel decode move is +85/−90, the claim reconciler, owner recovery and scope moves another ~300) and import rewrites across the daemon tests. If you want this under budget I would split the kernel/capture-kit moves (decodeDeviceIdentity, app-log durable resource and file policy) into a lower layer.
Validation
pnpm check:affected --run passes on 6b960298f (L1b) and on 7d007f5cf (#3616), both rebased on origin/main 542fb77; pnpm check:daemon-wire-compat passes on both. Earlier L1b attempts failed on the eager-closure budget, production-exports and fallow (all fixed above) and twice on tmpdir-leaks-model while two other threads' gates ran on this host; that check passes alone twice and in the final runs. A fresh-context review of the first L1b draft found only stale path references (fixed). The R78 reachability walk changed the design after that review, so the final shape has not been reviewed cold.
No device-facing behavior changes; device release --stale runs the same reconciler code, composed from the same root gateway.
🤖 Generated with Claude Code