feat(windows): add native daemon lifecycle and control - #1363
feat(windows): add native daemon lifecycle and control#1363joshuajbouw wants to merge 48 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
Adds a native Windows daemon lifecycle/control implementation (start/stop/restart/status/doctor/self-update) while preserving existing Unix behavior and default persistent-daemon semantics. This extends the runtime’s local-transport and on-disk runtime artifacts to work robustly on Windows (named pipes, authenticated readiness, verified process identity), and adds native Windows CI coverage to exercise the real lifecycle.
Changes:
- Implement Windows-native daemon lifecycle semantics in the CLI (foreground/background spawn, authenticated readiness probing, verified stop escalation, and improved doctor/status behavior).
- Update kernel/uplink runtime-path handling to fail-closed on Windows (no
/tmpfallback) and to use private/atomic runtime artifact writes. - Add Windows self-update helper transaction flow plus Windows CI workflow + script to validate start/status/stop and confirmed exit.
Reviewed changes
Copilot reviewed 29 out of 30 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| scripts/windows-daemon-lifecycle.ps1 | Adds a PowerShell lifecycle smoke test used by Windows CI to exercise authenticated start/status/stop. |
| crates/astrid-uplink/src/socket_client.rs | Introduces fallible try_*_path APIs and makes Windows path resolution fail-closed. |
| crates/astrid-kernel/src/socket.rs | Makes runtime artifact paths/windows behavior fail-closed; uses private dirs and atomic private writes on Windows. |
| crates/astrid-kernel/src/lib.rs | Enables the native socket/named-pipe manager on Windows and updates shutdown cleanup semantics. |
| crates/astrid-daemon/src/main.rs | Keeps background Unix setsid behavior but preserves attachment in foreground mode via env marker. |
| crates/astrid-daemon/src/lib.rs | Updates wording/contracts to “local transport” and aligns error messaging with cross-platform uplink semantics. |
| crates/astrid-cli/src/socket_client.rs | Switches CLI re-exports to the new fallible try_* path APIs. |
| crates/astrid-cli/src/main.rs | Adds early Windows self-update helper dispatch before normal CLI parsing. |
| crates/astrid-cli/src/dispatch.rs | Updates start to accept args; adjusts stop call site (currently discards confirmation). |
| crates/astrid-cli/src/daemon.rs | Keeps daemon setsid behavior in background but not in foreground mode. |
| crates/astrid-cli/src/commands/who.rs | Uses fallible local-transport path resolution instead of implicit fallback path logic. |
| crates/astrid-cli/src/commands/self_update/windows.rs | Adds Windows self-update helper/staging/transaction logic (copy, digest recheck, replace, cleanup). |
| crates/astrid-cli/src/commands/self_update.rs | Enables Windows self-update flow and adds Windows platform target mapping and helper entrypoints. |
| crates/astrid-cli/src/commands/self_update_tests.rs | Extends tests for Windows target selection and managed binary naming (but needs cfg fixes for Windows builds). |
| crates/astrid-cli/src/commands/restart.rs | Tightens restart semantics to require confirmed stop and uses PID identity gating before respawn. |
| crates/astrid-cli/src/commands/ps.rs | Uses fallible local-transport path resolution. |
| crates/astrid-cli/src/commands/mod.rs | Adds the new daemon_process helper module. |
| crates/astrid-cli/src/commands/doctor.rs | Moves daemon health inference to authenticated status roundtrip instead of endpoint pathname presence. |
| crates/astrid-cli/src/commands/daemon.rs | Implements authenticated daemon probing/readiness, foreground mode, and stop confirmation plumbing. |
| crates/astrid-cli/src/commands/daemon_tests.rs | Extracts/extends lifecycle unit tests for new start/stop/foreground logic. |
| crates/astrid-cli/src/commands/daemon_process.rs | Adds Windows background process creation flags for detached daemon spawning. |
| crates/astrid-cli/src/commands/daemon_control/windows.rs | Adds Windows process identity verification and termination primitives using handles. |
| crates/astrid-cli/src/commands/daemon_control.rs | Integrates Windows termination + path identity, and validates PID file privacy on Windows. |
| crates/astrid-cli/src/commands/capsule/live_load.rs | Avoids panicking path resolution; treats unresolved transport path as “unreachable”. |
| crates/astrid-cli/src/cli.rs | Updates the Start command to accept StartArgs (help text needs refresh for --foreground). |
| crates/astrid-cli/src/bootstrap.rs | Updates companion binary discovery to use the native executable suffix (contains a formatting compile bug). |
| crates/astrid-cli/Cargo.toml | Adds windows-sys dependency for Windows-only lifecycle/update features. |
| CHANGELOG.md | Adds an [Unreleased] entry describing the Windows daemon lifecycle/control and self-update work. |
| Cargo.lock | Records the added windows-sys dependency. |
| .github/workflows/windows-daemon-lifecycle.yml | Adds Windows x64/ARM64 CI workflow to build, test, and run lifecycle smoke script. |
Comments suppressed due to low confidence (1)
crates/astrid-cli/src/commands/self_update_tests.rs:438
- This test uses
backup_and_swap(), butbackup_and_swapis compiled only on non-Windows (#[cfg(not(windows))]). As written, Windows test builds will fail to compile. Gate this test to non-Windows (or add an equivalent Windows-path test).
let extract = dir.path().join("new");
std::fs::create_dir_all(&install).unwrap();
std::fs::create_dir_all(&extract).unwrap();
let cli = MANAGED_BINARIES[0];
let daemon = MANAGED_BINARIES[1];
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
2f84f09 to
fb4177b
Compare
cefbe9c to
b3c9c07
Compare
|
@copilot review |
|
@copilot review |
Reviewed the full PR diff, including the latest commit (b3c9c07 "test(windows): secure router test homes"). No issues found.
CI: Changelog, Dependency Review, CodeQL, Analyze Rust, OCI builds, and Format all pass on the latest commit. No failures. |
Reviewed the full PR diff, including the latest commit (
The PR is in good shape. |
|
@copilot review Exact head |
Reviewed the full PR diff, including commit Root-cause analysis Fix correctness
Scope The PR is in good shape. |
Linked Issue
Closes #1350
Summary
Adds native Windows daemon lifecycle and control while preserving the existing
Unix behavior and persistent-daemon default. Windows startup now reaches the
real kernel composition root over the authenticated per-user named-pipe
transport, and lifecycle decisions are based on authenticated readiness and
verified process identity rather than filesystem socket assumptions.
Changes
stop escalation, restart, status, doctor, and
.execompanion discovery.automation cannot mistake an unverified stop for success.
fall through to exact-process termination while keeping daemon rejections and
unexpected authenticated responses fail-closed; preserve typed handshake
rejection through the uplink so process recovery cannot bypass it; re-probe
transport when no process remains so a lost acknowledgement is confirmed
without trusting a stale endpoint.
access-denied and unexpected liveness results remain fail-closed and are
never signaled without executable identity. Persist and verify the daemon's
process-creation timestamp on the same retained process handle so PID reuse
cannot target a newer process running the same installed image.
lifetime follows authenticated client presence and foreground mode propagates
daemon exit.
lock, readiness/PID/token publication, and graceful cleanup work on Windows;
shutdown removes the exact boot-home artifacts instead of re-resolving
process-global state.
Windows shutdown remains driven by console control and API requests.
preserving the existing public wrappers and Unix fallback behavior, and
apply private Windows ACLs to generated grant directories and files.
exact-parent PID/creation-time helper handshake, digest revalidation,
replacement of the complete daemon/build/emit/CLI executable set, rollback,
durable result receipts, crash recovery, and non-elevated stale-stage
cleanup. A provisional handoff cannot be recovered prematurely, while an
abandoned pre-mutation handoff is removed after the exact parent is confirmed
gone. Recovery scans the complete live executable set: a missing or unreadable
member remains durably
recovery_pendingwith the helper, transaction, andstaged payload retained; only a complete readable set can be terminalized.
Signed channel continuity state is provisioned with private Windows ACLs and
uses the recovery-aware private read/write transaction boundary.
PATHonlywhen all four release executables are present. Preserve
REG_SZversusREG_EXPAND_SZ, compare normalized paths case-insensitively, serializeAstrid writers, re-read immediately before mutation, verify after mutation,
and broadcast the environment change with a bounded synchronous Win32 call.
PATH writers. Open daemon boot diagnostics with true append-only Windows
access so concurrent startup handles cannot overwrite earlier bytes.
metadata extension bound to the legacy four-target manifest, preserving
compatibility for existing release-channel clients while allowing Windows
clients to authenticate exact archive digests.
and prove that an authenticated principal without
system:shutdowncannotstop or replace the daemon process.
regressions, persistent idle survival, and a real authenticated
start/status/stop/process-exit cycle using an immutable AOS
capsule-cliuplink fixture installed into a runtime-provisioned private home; hold and
release an MCP stdio client's named-pipe lease and require the auto-spawned
ephemeral daemon to exit promptly; preserve daemon boot logs as failure
artifacts, and keep every CLI probe and cleanup command bounded while
retaining stdout/stderr diagnostics.
release-profile matrix. It recursively checks out the pinned WIT input,
builds the exact four executable release set, rejects any artifact above the
authenticated 100 MiB per-binary bound, and then exercises the real helper,
transaction, receipt, rollback, and reconciliation paths with unchanged
operation deadlines.
fix(windows): provision capsule install home #1370, so the native lifecycle smoke starts from an absent home and exercises
the real secure setup path.
LocalAppData boundary instead of inheriting the hosted runner's broad
temporary-directory ACL.
Verification
cargo fmt --all -- --checkgit diff --checkx86_64-pc-windows-msvcandaarch64-pc-windows-msvccargo metadata --no-deps --locked --format-version 1python3 scripts/test_release_manifest.py(16 passed)python3 scripts/test_musl_release_manifest.py(7 passed)python3 scripts/test_windows_release_manifest.py(8 passed)python3 scripts/test_release_publication.py(11 passed)actionlint .github/workflows/windows-daemon-lifecycle.yml .github/workflows/release.ymlthe full daemon suite, kernel transport/readiness tests, updater
completion/recovery, persistent
PATH, and authenticated process smoke areenforced by the dedicated workflows. Debug native jobs compile and lint the
updater target and run its state-machine regressions; independent
release-profile jobs build and bound all four shipped executables before
running the real staged helper and executable. Stale-PID recovery uses a
confirmed exited native child rather than a synthetic process identifier.
process identity, exact-path shutdown cleanup, updater rollback and recovery
classification, signed release metadata and promotion verification, channel
continuity/ACL handling, release-profile CI fidelity, and public API shape.
Checklist
[Unreleased])