fix(launcher): Campaign LA close LA2 review findings

This commit is contained in:
Erik 2026-08-14 17:20:02 +02:00
parent 000ea979d5
commit 1c5e66c05b
8 changed files with 346 additions and 58 deletions

View file

@ -120,6 +120,15 @@ internal sealed class HeadlessSessionHost : IDisposable
/// rework of the shared-stdout diagnostics writer.
/// </summary>
private readonly SessionStatusWriter _statusWriter;
/// <summary>
/// Campaign LA LA2 review fix: the actual result returned by the process
/// start attempt. A configured probe mode is only intent; terminal status
/// may claim <c>reason:"probe"</c> after this records
/// <see cref="RuntimeSessionStartStatus.ProbeComplete"/>. Any other
/// non-connected result maps to the same connection-error code returned by
/// <see cref="HeadlessProcessHost"/>.
/// </summary>
private RuntimeSessionStartStatus? _startOutcome;
/// <summary>Guards <see cref="Stop"/>'s <c>disconnected</c> status event
/// so a Stop() on a session that never actually reached Connected (e.g.
/// disposing a fresh, never-started host) does not report a spurious
@ -465,7 +474,10 @@ internal sealed class HeadlessSessionHost : IDisposable
// Campaign LA slice LA1: "started" = session host start — the
// earliest point this session actually attempts to connect.
_statusWriter.Started(_descriptor.Id);
return Commands.Session.Start(Runtime.Generation);
RuntimeSessionStartResult result =
Commands.Session.Start(Runtime.Generation);
_startOutcome = result.Status;
return result;
}
internal RuntimeSessionStartResult Reconnect() =>
@ -647,18 +659,16 @@ internal sealed class HeadlessSessionHost : IDisposable
_stoppedGeneration);
// Campaign LA slice LA1: "exited" = terminal — the sole
// point every disposal path (graceful and post-
// quarantine) converges on. LA2: a probe session that
// never faulted reports reason "probe" here instead of
// "disposed" — the pinned contract's exit event for a
// successful probe.
// quarantine) converges on. LA2: only an actual
// ProbeComplete start outcome reports reason "probe";
// configured probe intent cannot turn a failed start into
// a successful terminal event.
(int exitCode, string exitReason) =
ResolveTerminalStatus();
_statusWriter.Exited(
_descriptor.Id,
_faulted ? 1 : 0,
_faulted
? "fault"
: _descriptor.Mode == HeadlessSessionMode.Probe
? "probe"
: "disposed");
exitCode,
exitReason);
_disposeStage++;
_disposed = true;
break;
@ -669,6 +679,33 @@ internal sealed class HeadlessSessionHost : IDisposable
}
}
/// <summary>
/// Produces the same terminal classification the owning process host uses.
/// Descriptor mode never participates: only an observed ProbeComplete may
/// report a successful probe. The surrounding disposal stage and LA1's
/// terminal/idempotent <see cref="SessionStatusWriter"/> make this event
/// exact-once even when disposal is retried.
/// </summary>
private (int Code, string Reason) ResolveTerminalStatus()
{
if (_faulted)
{
return (
(int)HeadlessExitCode.RuntimeError,
"runtime-fault");
}
return _startOutcome switch
{
RuntimeSessionStartStatus.ProbeComplete =>
((int)HeadlessExitCode.Success, "probe"),
null or RuntimeSessionStartStatus.Connected =>
((int)HeadlessExitCode.Success, "graceful"),
_ =>
((int)HeadlessExitCode.ConnectionError, "connection-error"),
};
}
private RuntimeSessionStartResult StartCore(
RuntimeGenerationToken expectedGeneration,
bool reconnect)