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:
parent
498f1c1182
commit
c601942467
14 changed files with 830 additions and 35 deletions
|
|
@ -208,6 +208,208 @@ public sealed class HeadlessConfigurationLoaderTests
|
|||
Assert.Empty(declared!);
|
||||
}
|
||||
|
||||
// ── Campaign LA slice LA2: probe-mode `mode` field shape validation ──
|
||||
|
||||
[Fact]
|
||||
public void ProbeSessionOmittingCharacterAndPolicyLoads()
|
||||
{
|
||||
using TemporaryConfiguration file = TemporaryConfiguration.Create(
|
||||
"""
|
||||
{
|
||||
"version": 1,
|
||||
"sessions": [
|
||||
{
|
||||
"id": "probe-session",
|
||||
"endpoint": { "host": "127.0.0.1", "port": 9000 },
|
||||
"account": "account",
|
||||
"mode": "probe",
|
||||
"credential": { "provider": "environment", "reference": "PROBE_PASSWORD" }
|
||||
}
|
||||
]
|
||||
}
|
||||
""");
|
||||
|
||||
HeadlessConfiguration configuration =
|
||||
HeadlessConfigurationLoader.Load(file.Path);
|
||||
|
||||
HeadlessSessionDescriptor session = Assert.Single(configuration.Sessions)!;
|
||||
Assert.Null(session.Character);
|
||||
Assert.Null(session.Policy);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ProbeSessionDeclaringCharacterFailsLoadNamingTheField()
|
||||
{
|
||||
using TemporaryConfiguration file = TemporaryConfiguration.Create(
|
||||
"""
|
||||
{
|
||||
"version": 1,
|
||||
"sessions": [
|
||||
{
|
||||
"id": "probe-with-character",
|
||||
"endpoint": { "host": "127.0.0.1", "port": 9000 },
|
||||
"account": "account",
|
||||
"mode": "probe",
|
||||
"character": { "index": 0 },
|
||||
"credential": { "provider": "environment", "reference": "PROBE_PASSWORD" }
|
||||
}
|
||||
]
|
||||
}
|
||||
""");
|
||||
|
||||
HeadlessConfigurationException exception = Assert.Throws<
|
||||
HeadlessConfigurationException>(
|
||||
() => HeadlessConfigurationLoader.Load(file.Path));
|
||||
|
||||
Assert.Contains("probe", exception.Message, StringComparison.Ordinal);
|
||||
Assert.Contains("character", exception.Message, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ProbeSessionDeclaringPolicyFailsLoadNamingTheField()
|
||||
{
|
||||
using TemporaryConfiguration file = TemporaryConfiguration.Create(
|
||||
"""
|
||||
{
|
||||
"version": 1,
|
||||
"sessions": [
|
||||
{
|
||||
"id": "probe-with-policy",
|
||||
"endpoint": { "host": "127.0.0.1", "port": 9000 },
|
||||
"account": "account",
|
||||
"mode": "probe",
|
||||
"policy": { "id": "idle" },
|
||||
"credential": { "provider": "environment", "reference": "PROBE_PASSWORD" }
|
||||
}
|
||||
]
|
||||
}
|
||||
""");
|
||||
|
||||
HeadlessConfigurationException exception = Assert.Throws<
|
||||
HeadlessConfigurationException>(
|
||||
() => HeadlessConfigurationLoader.Load(file.Path));
|
||||
|
||||
Assert.Contains("probe", exception.Message, StringComparison.Ordinal);
|
||||
Assert.Contains("policy", exception.Message, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A play session (mode absent) missing `character` must still fail —
|
||||
/// the LA2 change moved this requiredness from `[JsonRequired]` (a raw
|
||||
/// <see cref="System.Text.Json.JsonException"/> at deserialize time) to
|
||||
/// <see cref="HeadlessConfigurationLoader.ValidateSession"/>'s semantic
|
||||
/// check (a <see cref="HeadlessConfigurationException"/> naming the
|
||||
/// missing field). Exit-code parity (both map to
|
||||
/// <c>HeadlessExitCode.ConfigurationError</c>) is proven at
|
||||
/// <c>HeadlessEntryPointTests</c>; this test pins the loader-level
|
||||
/// exception type/message.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void PlaySessionMissingCharacterStillFailsLoad()
|
||||
{
|
||||
using TemporaryConfiguration file = TemporaryConfiguration.Create(
|
||||
"""
|
||||
{
|
||||
"version": 1,
|
||||
"sessions": [
|
||||
{
|
||||
"id": "play-missing-character",
|
||||
"endpoint": { "host": "127.0.0.1", "port": 9000 },
|
||||
"account": "account",
|
||||
"policy": { "id": "idle" },
|
||||
"credential": { "provider": "environment", "reference": "PLAY_PASSWORD" }
|
||||
}
|
||||
]
|
||||
}
|
||||
""");
|
||||
|
||||
HeadlessConfigurationException exception = Assert.Throws<
|
||||
HeadlessConfigurationException>(
|
||||
() => HeadlessConfigurationLoader.Load(file.Path));
|
||||
|
||||
Assert.Contains(
|
||||
"requires a character selector",
|
||||
exception.Message,
|
||||
StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
/// <summary>Same parity claim as
|
||||
/// <see cref="PlaySessionMissingCharacterStillFailsLoad"/> for the
|
||||
/// `policy` field.</summary>
|
||||
[Fact]
|
||||
public void PlaySessionMissingPolicyStillFailsLoad()
|
||||
{
|
||||
using TemporaryConfiguration file = TemporaryConfiguration.Create(
|
||||
"""
|
||||
{
|
||||
"version": 1,
|
||||
"sessions": [
|
||||
{
|
||||
"id": "play-missing-policy",
|
||||
"endpoint": { "host": "127.0.0.1", "port": 9000 },
|
||||
"account": "account",
|
||||
"character": { "index": 0 },
|
||||
"credential": { "provider": "environment", "reference": "PLAY_PASSWORD" }
|
||||
}
|
||||
]
|
||||
}
|
||||
""");
|
||||
|
||||
HeadlessConfigurationException exception = Assert.Throws<
|
||||
HeadlessConfigurationException>(
|
||||
() => HeadlessConfigurationLoader.Load(file.Path));
|
||||
|
||||
Assert.Contains(
|
||||
"requires a non-empty policy id",
|
||||
exception.Message,
|
||||
StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PlaySessionKeepsTodaysStrictCharacterSelectorAndPolicyValidation()
|
||||
{
|
||||
// Unrelated to `mode` — proves the LA2 refactor of ValidateSession
|
||||
// did not loosen the existing selector-shape/policy-id checks for
|
||||
// ordinary play sessions (mode absent).
|
||||
using TemporaryConfiguration badSelector = TemporaryConfiguration.Create(
|
||||
"""
|
||||
{
|
||||
"version": 1,
|
||||
"sessions": [
|
||||
{
|
||||
"id": "bad-selector",
|
||||
"endpoint": { "host": "127.0.0.1", "port": 9000 },
|
||||
"account": "account",
|
||||
"character": { "index": 0, "name": "Two" },
|
||||
"policy": { "id": "idle" },
|
||||
"credential": { "provider": "environment", "reference": "A" }
|
||||
}
|
||||
]
|
||||
}
|
||||
""");
|
||||
using TemporaryConfiguration blankPolicy = TemporaryConfiguration.Create(
|
||||
"""
|
||||
{
|
||||
"version": 1,
|
||||
"sessions": [
|
||||
{
|
||||
"id": "blank-policy",
|
||||
"endpoint": { "host": "127.0.0.1", "port": 9000 },
|
||||
"account": "account",
|
||||
"character": { "index": 0 },
|
||||
"policy": { "id": "" },
|
||||
"credential": { "provider": "environment", "reference": "B" }
|
||||
}
|
||||
]
|
||||
}
|
||||
""");
|
||||
|
||||
Assert.Throws<HeadlessConfigurationException>(
|
||||
() => HeadlessConfigurationLoader.Load(badSelector.Path));
|
||||
Assert.Throws<HeadlessConfigurationException>(
|
||||
() => HeadlessConfigurationLoader.Load(blankPolicy.Path));
|
||||
}
|
||||
|
||||
private static string ConfigurationWith(params string[] sessions) =>
|
||||
$$"""{"version":1,"sessions":[{{string.Join(",", sessions)}}]}""";
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue