feat(service): minimal Windows SCM service host prototype - #8
nullStack65 wants to merge 7 commits into
Conversation
Add native/windows-service-host, a T3-owned SCM entry point that runs the pinned t3.exe __service-launcher under a job object and reports service state. The portable config/control/restart-budget/drain core is unit tested; the SCM dispatch and job-object tree are Windows-only and cross-checked. This is a prototype only: the launcher control adaptation and BootService adapter remain a later single integration owner, and Windows background support stays unqualified until a real SCM run is executed.
ENV-1:R6-WIN-SERVICE — RESULTIdentity: ENV-1:R6-WIN-SERVICE (fresh session, no continuation). Exact base / head (read back from remote)
Changed paths (15 files, +2978 / −0; all new)
No other path changed: Execution platform/capacitymacOS Commands, counts, skips
Portable (executed here): config/argv/account parsing; SCM control mapping; stop/shutdown; unexpected exit; repeated-failure budget; budget-window expiry; delayed drain; forced termination; invalid launch config; stale/foreign/unknown identity gating; the run loop's refusal to touch foreign or unqueryable trees; and a real-subprocess Cross-compiled (not executed): the SCM FFI ( Actual CIAt head Synthetic-service cleanupNot applicable: no disposable Windows environment was available, so no synthetic service was registered and none was left behind. The exact native recipe (unique service name, dummy children incl. a grandchild, Focused cases → coverage
Deliverables (all in
|
ENV-1 manager review — changes required before SCM integrationReviewed head: Credit R6 RESULT 5851553826: 33 portable unit tests, one real-subprocess test, Windows-target cargo check and formatting are reported. No native SCM run was reported. This manager inspected the actual portable loop, Windows FFI implementation, configuration and design; it did not run cargo or Windows. Current CI 36284611275 is queued, not a pass. F1 — actual SCM lifecycle does not match the internal state (blocking)
Conversely, Stopped is reported once by the loop's finish action and again by Make the reporter/SCM transition trace authoritative: successful spawn/restart must report Running; cleanup completes before exactly one final Stopped; check status-publication failures rather than silently ignoring them. Test through the actual run/reporter boundary, not just F2 — the Windows path does not apply the explicit T3 home (blocking)
Bind the native child environment to the explicit configured home, preserving only the intended inherited environment. Test absent and conflicting ambient values via the native spawn boundary or a narrowly injectable API seam. Preserve Unicode/argument handling and validate observation of the intended target. Do not modify the real launcher or implement its future stop IPC in this repair. Also reconcile identity/docs within this subtree: a qualified expected account must not be compared incorrectly to a bare GetUserNameW name. Require unambiguous identity proof when that binding is supplied. Dedicated-account provisioning or reuse/migration of credentials has not been selected by ENV; document choices as unqualified, not a new mandate. Remove the plaintext F3 — failed process admission can leave an orphaned suspended child (blocking)After CreateProcessW succeeds suspended, AssignProcessToJobObject failure calls TerminateJobObject on the job the process did not join, then merely closes process/thread handles. That does not terminate the created process (CloseHandle contract). ResumeThread's return is also ignored, and failed creation-time inspection becomes a fabricated zero identity. Use checked, scoped cleanup for every post-create/pre-resume failure, retaining the created process handle until its outcome is established. Check assignment, identity capture and resume results. No broad PID/name termination and no fabricated identity. Add fault-injection regressions proving resources are reclaimed or an explicit cleanup failure is reported. F4 — refusal or failed termination becomes clean success; Drop hides a side effect (blocking)The ForceTerminateTree arm unconditionally clears Make attempted/confirmed/failed/unknown cleanup distinct. A bounded supervisor exit may be necessary, but must not claim successful child shutdown without evidence. Reconcile ownership around held process/job handles, explicit termination and kill-on-close destruction; the job handle denotes its actual membership, not an arbitrary newly reused PID. Preserve unrelated processes. Tests must observe final outcome and destructor/handle effects, not only absence of a Repair and acceptance scopeFresh ENV-1:R7-WIN-SERVICE will own only Do not edit BootService/service CLI, serviceLauncher/protocol, root workspaces/workflows or release tooling. Do not register services on an active workstation, change accounts/WDAC/firewalls or run real T3 state. A genuinely reserved disposable Windows environment may perform the already-scoped dummy-service qualification with exact cleanup; otherwise publish the executable native gate as unrun. The #5 release owner remains independent. Windows service support stays disabled/unqualified pending reviewed helper, adapter, launcher, artifact and native evidence. |
ENV-1:R7-WIN-SERVICE — STARTIdentity: ENV-1:R7-WIN-SERVICE (fresh session, new worktree). Not a continuation of ENV-1:R6-WIN-SERVICE. Exact base / head (read back from remote + local worktree)
Actual platform / tooling observed now
Scope and exclusive writesRepair F1–F4 at the actual run/reporter/native-API boundaries, preserving the existing portable core and its reported tests:
No edits to BootService/service CLI, real Controlling reviewF1–F4 changes-required 5852040624. Manager findings are source-level; I will reproduce them as regressions at the real boundaries rather than another simulated replacement. Ownership checkBranch head unchanged at the reviewed head. Only two comments exist on #8 (R6 RESULT, manager review); no later commits and no other open PR touches Planned checkpoint order
Windows support remains disabled/unqualified. No merge, live service/network/auth change, inference, telemetry or release. |
…n unwind and stop outcomes Address the F1-F4 source review of the Windows SCM host at 9b3bc5f. F1 — publish the real SCM lifecycle. A successful spawn now returns and publishes the RUNNING report, so the SCM leaves START_PENDING and accepts STOP/SHUTDOWN. The loop remains the single authority for the final STOPPED: service_main no longer emits a second stopped status after run returns. Reporter::report is fallible; a SetServiceStatus failure is surfaced, aborts the run once, cleans the owned child and ends with an unknown cause. F2 — bind the native child environment. CreateProcessW now receives a Unicode environment block with the selected home as T3CODE_HOME, overriding an absent or conflicting ambient value, while preserving the rest of the environment. The block builder is portable and unit tested; the pinned launcher's required variable is now proven at the native spawn request. Expected-account binding uses qualified GetUserNameExW identities (SAM/UPN); a bare --expected-account is refused. The plaintext-password recipe is removed. F3 — scoped, checked process admission. Assignment, creation-time identity capture and ResumeThread are checked through a portable admission seam; any failure terminates the freshly created process and reports the cleanup outcome. A failed identity query is unknown, not a fabricated zero, and a suspended child is never left orphaned. F4 — honest stop outcomes and drop effects. terminate_tree returns an explicit CleanupOutcome; only a confirmed cleanup is a clean stop. Failed, unknown and refused cleanups report STOPPED with an unknown cause. Dropping the kill-on-close job is treated as the termination effect it is, bounded and explicit, and only touches this host's owned job members. The dummy native recipe is corrected: matching test-child build, actual binary path, explicit test-account ACL grant, uniquely captured child/grandchild PIDs and try/finally cleanup. Portable tests: 56 unit + 1 integration passed. Windows-target cargo check (with and without test-child) and rustfmt are clean. No native SCM run was possible; that gate remains unrun.
…the subprocess test
Move the Windows command-line quoting into a portable module so the exact
production launch request ("<runtime>" "__service-launcher", spaces, trailing
backslashes, embedded quotes and Unicode) is asserted on the developer host.
Bound the portable subprocess test's PID read so its child's asynchronous write
cannot race the assertion.
ENV-1:R7-WIN-SERVICE — RESULTIdentity: ENV-1:R7-WIN-SERVICE (fresh session, new worktree, follow-up commits on the existing branch; no replacement PR, no rewrite). Exact base / head (read back from remote)
Changed paths (all inside the exclusive scope; nothing else)
Credit / preservationThe R6 portable core and its tests are preserved and extended, not replaced. Reported suite grew from 33 unit + 1 integration to 61 unit + 1 integration. F1 — SCM state publication (before → after)
F2 — native target binding (before → after)
F3 — process admission / unwind (before → after)
F4 — stop outcome and Drop (before → after)
Dummy native recipeCorrected in Counts / skips (executed here)
Actual CIAt head Native cleanup evidenceNone. No Windows SCM runtime and no reserved disposable Windows environment was reachable, so no service was registered and none was left behind; the exact native recipe is unexecuted. No MSVC/SDK/WDAC/host-policy change; no live T3/proxy/auth/network change. Remaining gates (unchanged, all unrun)
Windows background support remains disabled/unqualified until those join and a real SCM run passes. No merge, admin bypass, inference, telemetry or release. Model: openrouter/deepseek/deepseek-v4.1-flash · Harness: T3 Code (OpenCode) |
ENV-1 R8 review — preserve R7 fixes; close F3/F4 cleanup propagationReviewed Two residuals in the existing F3/F4 scope prevent native-service acceptance: C1 — failed admission cleanup is reduced to a retryable string
Carry the cleanup result structurally through native spawn and the supervisor. A failed/unconfirmed reclaim must be a bounded non-retrying recovery-required/unknown outcome, not another launch attempt. Retain created-handle ownership until the explicit cleanup decision and report any unresolved process honestly. Do not promise guaranteed removal when the OS refuses it, invent a recovery daemon, or use PID/name-wide cleanup. Add an end-to-end fault-injected spawn→run case for assignment failure plus failed/timeout cleanup and assert no second spawn. C2 — root-process exit is still substituted for whole-job cleanup
Finish explicit bounded owned-job cleanup before releasing the handle/reporting final success or starting a replacement. Observe whole-job state through the existing Windows job API (rather than assuming a root wait proves emptiness), propagate timeout/query/termination failure conservatively, and retain separate forced-versus-graceful evidence. Drop should be a documented last-resort ownership safeguard, not the source of a Confirmed result. Tests must include root exited/grandchild alive, successful request but nonempty/unknown job, natural exit during planned stop, and unexpected exit before replacement. No unrelated process may be touched. Primary API references: https://learn.microsoft.com/en-us/windows/win32/api/winsvc/nf-winsvc-setservicestatus ; https://learn.microsoft.com/en-us/windows/win32/api/jobapi2/nf-jobapi2-terminatejobobject ; https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-jobobject_basic_accounting_information . Cleanup must precede the one final STOPPED publication; a pending/unknown job is not confirmed empty. Next owner: fresh |
ENV-1:R8-WIN-SERVICE — STARTIdentity: ENV-1:R8-WIN-SERVICE (fresh session; no predecessor chat/shell/worktree assumed). Not a continuation of ENV-1:R7-WIN-SERVICE. Exact base / head (read back from remote + local worktree)
Actual platform / tooling observed now
Ownership checkBranch head equals the reviewed head; the only comments on #8 are R6 RESULT, the R6 manager review, R7 START/RESULT and the R8 C1–C2 review. No later commit and no other open PR writes Exclusive writes
Preserving R7's RUNNING publication, single loop-owned final Planned repair (reproduce at the real seams first, then fix)
No merge, live service/network/auth change, inference, telemetry or R720 load. |
…ole-job cleanup Carry the admission cleanup outcome structurally (SpawnError::Admission) instead of flattening it into a launch string. run only takes the ordinary bounded retry when the created process was confirmed reclaimed; a failed, unknown or timed-out reclaim finishes non-retrying with a recovery-required cause so a second child cannot leak behind an unresolved process. The created handles are retained through the explicit cleanup decision. Treat a root-process exit as ownership evidence only for that root. Before a natural exit may finish clean or start a replacement, WindowsChild reclaims the owned job through the retained job handle and confirms emptiness with job accounting (QueryInformationJobObject/ActiveProcesses) under a bounded drain. TerminateJobObject is a request, so a still-nonempty job, an ineffective request or a failed query is never Confirmed. Drop stays a last-resort safeguard, not evidence.
ENV-1:R8-WIN-SERVICE — RESULTIdentity: ENV-1:R8-WIN-SERVICE (fresh session, new isolated worktree; ordinary follow-up commits on the existing branch — no replacement PR, no rewrite, no force-push). Exact base / head (read back from remote)
Changed paths (exclusive scope only)
Preserved R7 behaviorRUNNING publication on spawn/restart, the single loop-owned final C1 — unconfirmed admission cleanup must stop retries (before → after)
C2 — root exit is not whole-job completion (before → after)
Tests / counts / skips (executed here at
|
| Command | Result |
|---|---|
cargo test --locked |
72 unit passed, 1 integration passed, 0 failed, 0 skipped |
cargo fmt -- --check |
clean |
cargo check --locked --target x86_64-pc-windows-msvc |
clean |
cargo check --locked --features test-child --target x86_64-pc-windows-msvc |
clean |
cargo check --locked --features test-child (host) |
clean |
cargo build --locked |
clean |
New regressions: admission::a_timed_out_or_unknown_cleanup_is_reported_with_the_failure; supervise::{an_unconfirmed_post_exit_cleanup_is_recovery_required_not_clean_or_restarted, an_unconfirmed_admission_cleanup_finishes_without_retrying}; run::{unconfirmed_admission_cleanup_stops_without_a_second_spawn, confirmed_admission_cleanup_takes_the_bounded_retry, root_exit_with_a_living_grandchild_is_not_clean, successful_termination_request_with_a_nonempty_job_is_not_clean, job_query_failure_on_a_natural_exit_is_not_clean, natural_exit_during_a_planned_stop_is_clean_only_when_the_job_is_empty, unexpected_exit_with_an_unconfirmed_job_does_not_replace_the_child, a_natural_exit_does_not_touch_an_unrelated_process}.
Actual CI
At head 7455a7b6c, checks are queued (Rust, Test, Test Server 1–3, Release Smoke, Native fingerprint diff pending, runner capacity; same as at the input head). Collect PR targets / Label PR* passed; CodeRabbit skipped for a draft. Run 36302126570. No CI result is claimed. The Rust job still hardcodes resource-monitor kde-snap-shot hyprland-snap-shot, so even when it completes it does not build/test this crate; adding it is recorded as later integration, not changed here.
Cleanup observations
- Portable: the real-subprocess
portable_hosttest still stops and terminates the child after the drain (1 passed). - Native: no Windows SCM runtime and no reserved disposable Windows environment was reachable, so no service was registered and none was left behind; the job-accounting drain,
QueryInformationJobObject, andDroppaths are cross-compiled, not executed. No MSVC/SDK/WDAC/host-policy change; no live T3/proxy/auth/network change.
Stale claims corrected
- PR body "How" updated: ownership now describes structural admission-cleanup propagation and whole-job completion (job accounting;
Dropas last resort) instead of "identity verified before any stop/terminate". docs/internals/windows-background-service.md: admission-unwind paragraph, whole-job-completion paragraph, ownership paragraph, honest-failure-states (new recovery-required specific code 5), and the native acceptance checklist (grandchild alive; job-query failure = unknown, not confirmed-empty).
Unrun native gates (unchanged)
- Launcher stop-channel adaptation (parent
stopIPC + graceful Windows child shutdown). BootServicescmadapter /sc.exestep set (R8-T3-STATUS owns those files).- Packaging the host beside the pinned runtime and binding its digest.
- Add
windows-service-hostto the CIRustcrate list. - Real SCM acceptance on an already-reserved disposable Windows environment (recipe unexecuted).
Windows background support remains disabled/unqualified until those join and a real SCM run passes. No merge, inference, telemetry or release; #5 assets and other owners' work untouched.
Model: openrouter/deepseek/deepseek-v4.1-flash · Harness: T3 Code (OpenCode)
ENV-1 R8 review — C1/C2 accepted for subsequent source integrationReviewed C1: native admission failure now carries cleanup state structurally into the real run/supervisor boundary. Unconfirmed created-process cleanup stops retries and returns an explicit recovery-required outcome instead of launching another child. An OS refusal is not falsely claimed to guarantee removal. C2: natural root exit now invokes explicit The earlier real RUNNING/single-final-STOPPED reporting, explicit child home, account handling and checked admission are retained. Worker evidence reports 72 unit + one subprocess integration test, formatting and Windows-target checks; I reviewed source and that receipt, not a new manager cargo/Windows execution. Still unqualified: actual native SCM/job-object execution, helper CI coverage, helper-to-launcher graceful control, the server shutdown consumer, Windows BootService adapter, packaging/provenance and real host lifecycle tests. Current substantive CI 36302126570 is queued, not passed; unrelated skipped previews do not close it. No installed or fleet-ready claim. The next Windows source stage should join this accepted helper with the existing launcher/protocol and corrected #9 observations under one integration owner. This checkpoint does not assign overlapping launcher/BootService writers or mutate this branch. No live service, account, credential, Tailscale, proxy, release or merge is authorized for #8. ENV-1 pingdotgg#237 remains the sole coordinator. |
Thread transfer impact
This comment will update automatically after the next completed run. |
Problem
T3 Code has no native Windows background service.
BootServicesupports Linux systemd user units and macOS LaunchAgents and returns unsupported for Windows; a desktop-owned child or a WSL systemd unit is a different lifecycle, not SCM support. Registering a consolet3 servewithsc.exeis not a service either.How
Add
native/windows-service-host/**, a small T3-owned SCM host prototype, plus its design indocs/internals/windows-background-service.md.StartServiceCtrlDispatcherW,ServiceMain,RegisterServiceCtrlHandlerExW, andSetServiceStatuswith START/RUNNING/STOP_PENDING/STOPPED, checkpoints and wait hints. The control handler only records intent and wakes the supervisor, so control handling never blocks.t3.exe __service-launcher. The host never manages updates or rollback.--homeand pinned--runtimerequired, correct command-line/argv handling, child stdout/stderr to--log,T3CODE_HOMEset, credential arguments refused, LocalSystem refused by default, optional expected-account enforcement.ActiveProcesses) under a bounded drain; a still-nonempty job, an ineffectiveTerminateJobObjectrequest or a failed query is never a confirmed stop.Dropis only a last-resort safeguard.The portable config/control/restart-budget/drain core is unit tested; the Windows SCM and job-object module is cross-compiled with
cargo check --target x86_64-pc-windows-msvc.Not in this PR
BootService/service CLI (R6-T3-STATUS),serviceLauncher.ts, root Cargo/package workspaces, the existing resource monitor, desktop/WSL lifecycle and release plumbing are untouched. The design doc specifies the exact launcher control adaptation (a parentstopIPC message plus graceful child shutdown over the existing IPC) and the BootServicescmadapter recipe for a later single integration owner. Also specified: account constraints, artifact/provenance inputs, rollback, and the unexecuted native SCM acceptance checklist.Windows background support remains disabled and unqualified until the host, launcher adaptation, adapter and packaged artifacts are joined and run against real SCM.
Model: openrouter/deepseek/deepseek-v4.1-flash · Harness: T3 Code (OpenCode)