wip: Campaign LA LA2 probe mode + idle policy — INCOMPLETE, stopped mid-task

Agent was stopped for token budget. Landed here: probe flag through
LiveSessionConnectOptions + the StartCore short-circuit, the mode field
with JsonRequired-to-semantic-validation move, host exit-code mapping,
and 34 passing tests including 3 new probe tests (agent last reported
green before the stop). NOT DONE: the idle-policy unit tests (next
step), full-suite verification, and the WSL run.

Build/test state UNVERIFIED at this commit. Next session: finish idle
policy tests, run Runtime+Headless Release suites Windows and WSL, then
dispatch the Opus dual-lens review.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-08-14 16:33:00 +02:00
parent 498f1c1182
commit c601942467
14 changed files with 830 additions and 35 deletions

View file

@ -191,6 +191,76 @@ public sealed class HeadlessEntryPointTests
Assert.Contains(expected, error.ToString());
}
/// <summary>
/// Campaign LA slice LA2: before this change, an omitted `character` or
/// `policy` field failed deserialization itself with a raw
/// <see cref="System.Text.Json.JsonException"/> ("missing required
/// properties") — this test pins that the LA2 move to semantic
/// validation (<see cref="AcDream.Headless.Configuration.HeadlessConfigurationException"/>
/// naming the exact missing field) preserves the SAME exit code
/// (3, <c>HeadlessExitCode.ConfigurationError</c>) end to end through
/// <see cref="HeadlessEntryPoint.Run(IReadOnlyList{string}, TextWriter, TextWriter)"/>.
/// The message text change (generic → field-naming) is a deliberate,
/// accepted improvement, not a contract break.
/// </summary>
[Theory]
[InlineData(
"""
{"version":1,"sessions":[{"id":"s","endpoint":{"host":"127.0.0.1","port":9000},"account":"account","policy":{"id":"idle"},"credential":{"provider":"environment","reference":"X"}}]}
""",
"character selector")]
[InlineData(
"""
{"version":1,"sessions":[{"id":"s","endpoint":{"host":"127.0.0.1","port":9000},"account":"account","character":{"index":0},"credential":{"provider":"environment","reference":"X"}}]}
""",
"policy id")]
public void PlaySessionMissingCharacterOrPolicyKeepsConfigurationErrorExitCode(
string json,
string expectedMessageFragment)
{
using var file = TemporaryConfiguration.Create(json);
using var output = new StringWriter();
using var error = new StringWriter();
int exitCode = HeadlessEntryPoint.Run(
["validate", "--config", file.Path],
output,
error);
Assert.Equal((int)HeadlessExitCode.ConfigurationError, exitCode);
Assert.Contains(
expectedMessageFragment,
error.ToString(),
StringComparison.OrdinalIgnoreCase);
Assert.Equal(string.Empty, output.ToString());
}
/// <summary>
/// Campaign LA slice LA2: a valid probe-mode session (mode "probe",
/// character/policy both omitted) passes `validate` — the launcher's
/// "refresh characters" flow only needs the process to accept the
/// document, not to run it.
/// </summary>
[Fact]
public void ValidateAcceptsProbeSessionOmittingCharacterAndPolicy()
{
using var file = TemporaryConfiguration.Create(
"""
{"version":1,"sessions":[{"id":"probe","endpoint":{"host":"127.0.0.1","port":9000},"account":"account","mode":"probe","credential":{"provider":"environment","reference":"X"}}]}
""");
using var output = new StringWriter();
using var error = new StringWriter();
int exitCode = HeadlessEntryPoint.Run(
["validate", "--config", file.Path],
output,
error);
Assert.Equal((int)HeadlessExitCode.Success, exitCode);
Assert.Contains("1 session(s)", output.ToString());
Assert.Equal(string.Empty, error.ToString());
}
[Fact]
public void UnknownCommandReturnsUsageError()
{