feat(launcher): prepare Campaign LA11 user gate

This commit is contained in:
Erik 2026-08-14 23:09:08 +02:00
parent 09d84387a8
commit f881e5b467
24 changed files with 3538 additions and 58 deletions

View file

@ -26,53 +26,41 @@ What does NOT go here:
## #397 — Windows: LauncherProcessSupervisor.Stop has no reliable graceful-stop signal for a no-window console host
**Status:** OPEN
**Status:** IN-PROGRESS — the isolated process-group implementation and real
Windows fixtures are complete; the LA11 connected acceptance row remains
required before closure.
**Severity:** MODERATE (a hard-killed `AcDream.Headless` leaves the ACE
account session stuck for several minutes — a documented project landmine;
see CLAUDE.md "Logout-before-reconnect")
**Filed:** 2026-08-14 (Campaign LA plan §LA3 review-fix round, finding F3)
**Component:** Launcher.Core / process supervision
**Description.** `LauncherProcessSupervisor.Stop` now attempts a graceful
stop signal (`ILauncherChildProcess.TryRequestGracefulStop`) BEFORE
`CloseMainWindow`. On Linux this sends `SIGINT` via a `libc` P/Invoke
(`kill(pid, 2)`), which the K4-proven headless host already turns into an
ACE-confirmed graceful logout. On Windows there is no equivalent today for a
console process with no message-pump window: `CloseMainWindow` is a no-op
for a console host (there is no `HWND` to target), and
`GenerateConsoleCtrlEvent` cannot usefully target an arbitrary child process
today — Windows delivers console control events to every process attached
to the SAME console as the calling process, so an unscoped call would also
signal the launcher itself (and anything else sharing that console), not
just the intended child. `TryRequestGracefulStop` therefore returns `false`
on Windows unconditionally, and `Stop` degrades straight to `CloseMainWindow`
(still a no-op for a console child) and then the timeout-driven `Kill()`
exactly the hard-kill behavior this finding was written to describe, just
with a documented (rather than silent) gap.
**Implementation checkpoint.** `LauncherProcessSupervisor.Stop` attempts
`ILauncherChildProcess.TryRequestGracefulStop` before `CloseMainWindow` and
the timeout/kill fallback. Linux retains its K4-proven targeted `SIGINT`.
On Windows, console-capable launcher specs now use a narrow no-shell
`CreateProcessW` seam with `CREATE_NEW_PROCESS_GROUP`, a suspended start, and
an explicit inherited-handle list that preserves only redirected stdin plus
stdout/stderr. A consoleless Avalonia parent briefly allocates and hides a
console for the creation transaction, detaches after the new group inherits
it, and later attaches only long enough to send
`GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, childProcessGroupId)`. Each such
child is therefore both the root of its own process group and, for the normal
Explorer-launched case, attached to its own console. Graphical children opt
out and retain the ordinary `Process`/`WM_CLOSE` path.
**Known fix direction (not yet implemented).** Spawn the Windows child with
the `CREATE_NEW_PROCESS_GROUP` creation flag (available via a native
`CreateProcess` call or by setting it on the `ProcessStartInfo`/`Process`
plumbing in `SystemChildProcess`) so the child gets its own console process
group, detached from the launcher's own group. Then
`TryRequestGracefulStop` on Windows calls
`GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, childProcessGroupId)`
`CTRL_BREAK` (unlike `CTRL_C`) can target a specific process group ID and,
unlike `CTRL_CLOSE`/`CTRL_LOGOFF`/`CTRL_SHUTDOWN`, is deliverable to a
process that has installed no console-control handler at all (the default
CRT handler treats it as a terminating signal, so `AcDream.Headless` doesn't
strictly need new code to receive SOME form of shutdown from it) — though
wiring a real `SetConsoleCtrlHandler` handler that routes `CTRL_BREAK` into
the same graceful-shutdown path K4 already built for Linux SIGINT is the
better long-term target, so a Windows headless launch gets the identical
ACE-confirmed graceful logout instead of just "exits somehow."
Two real Windows fixture gates cover both a console parent and a consoleless
WinExe parent. They prove exact complex argv, redirected stdin, receipt of a
targeted CTRL_BREAK marker, exit code 0 before timeout, no supervisor `Kill`,
and a sibling process group that remains running until it receives its own
targeted break. Safe-handle cleanup, early-failure termination, and the
Linux SIGINT gate remain covered by the Launcher.Core suite.
**Acceptance for closing this issue:** `SystemChildProcess` spawns Windows
children with `CREATE_NEW_PROCESS_GROUP`; `TryRequestGracefulStop` sends
`CTRL_BREAK_EVENT` to that child's process group on Windows; a live
connected gate proves `AcDream.Headless` exits gracefully (ACE clears the
session immediately, not after the ~3-minute stale-session window) when
stopped via `LauncherProcessSupervisor.Stop` on Windows, matching the
**Acceptance for closing this issue:** automated process-group and targeted-
signal coverage is complete. Keep the issue IN-PROGRESS until the LA11 live
connected row proves `AcDream.Headless` exits gracefully and ACE clears the
session immediately (not after the ~3-minute stale-session window) when
stopped through `LauncherProcessSupervisor.Stop` on Windows, matching the
Linux SIGINT behavior.
## #396 — Configure Keyboard: no capture-instruction dialog on a mapping-button click