fix(headless,runtime): OP7 review fixes + docs: OP3 re-review REOPEN (narrow)
TWO work products share this commit (a staged-index collision between the
coordinator's docs commit and the OP7 fixer's staged files — content
verified complete and coherent; only this message was wrong before the
amend):
1. OP7 review fixes (all nine findings from
docs/research/2026-08-11-op7-review.md):
- M1: HeadlessSessionDescriptor is a record; WithAccount uses 'with' non-destructive record copy,
so a future property cannot be silently dropped; direct-CLI
regression test proves CharacterOptions survives --user/--password.
- M2 root fix: LiveSessionEventRouter skips BOTH Replace and the
options notification on a trailer-truncated PlayerDescription — a
truncated re-seed can no longer install zeroed words under an armed
latch for OP7's automation to flush into 0x01A1.
- SF1: schema keys validate as ordinal strings against the allowed
names (numeric / comma-combined aliases rejected). SF2: both-true
fellowship exclusion rejected at load, naming both keys. SF3: the
onLoginCompleteSent observer moved after transit.EndTeleport().
SF4: production-hook coverage for all three LoginComplete sites.
SF5: test-script OP7 wire expectation corrected (batched ids ride
only the 0x01A1).
2. docs/research/2026-08-11-op3-rereview.md — OP3 re-review verdict
REOPEN (narrow): M1 byte-decode independently re-verified (6a 07 at
all six sites); residuals R1 (gate script promises a timestamp prefix
acdream doesn't render), R2 (null-controller player-mode still
refuses), R3 (dormancy pin lacks stimulus) — coordinator fixes follow.
Full Release suite at this tree: 12,956 passed / 4 skipped / 0 failed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
386076af0f
commit
7b60e71b85
11 changed files with 1007 additions and 23 deletions
|
|
@ -89,6 +89,82 @@ public sealed class HeadlessConfigurationLoaderTests
|
|||
StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NumericKeyFailsLoadInsteadOfAliasingIntoAnAllowedId()
|
||||
{
|
||||
// SF-1 (OP7 review fix, 2026-08-11): Enum.TryParse on a non-[Flags]
|
||||
// enum accepts a decimal numeric string — "15" parsed to 0x0F
|
||||
// (FellowshipShareXP), which IS allow-listed, so the old
|
||||
// parsed-value check let a key that is not an enum-member spelling
|
||||
// at all silently pass. Validation must reject the STRING.
|
||||
using TemporaryConfiguration file = TemporaryConfiguration.Create(
|
||||
ConfigurationWith(Session(
|
||||
"bot",
|
||||
"BOT_PASSWORD",
|
||||
"\"characterOptions\":{\"15\":true}")));
|
||||
|
||||
HeadlessConfigurationException exception = Assert.Throws<
|
||||
HeadlessConfigurationException>(
|
||||
() => HeadlessConfigurationLoader.Load(file.Path));
|
||||
|
||||
Assert.Contains("15", exception.Message, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CommaCombinedKeyFailsLoadInsteadOfOrCombiningIntoAnAllowedId()
|
||||
{
|
||||
// SF-1 companion case: Enum.TryParse OR-combines a comma-separated
|
||||
// member list — "ToggleRun,AutoTarget" (0x0A | 0x0D) parsed to
|
||||
// 0x0F (FellowshipShareXP), again allow-listed, so a config that
|
||||
// reads as two movement options would have silently set fellowship
|
||||
// XP sharing instead.
|
||||
using TemporaryConfiguration file = TemporaryConfiguration.Create(
|
||||
ConfigurationWith(Session(
|
||||
"bot",
|
||||
"BOT_PASSWORD",
|
||||
"\"characterOptions\":{\"ToggleRun,AutoTarget\":true}")));
|
||||
|
||||
HeadlessConfigurationException exception = Assert.Throws<
|
||||
HeadlessConfigurationException>(
|
||||
() => HeadlessConfigurationLoader.Load(file.Path));
|
||||
|
||||
Assert.Contains(
|
||||
"ToggleRun,AutoTarget",
|
||||
exception.Message,
|
||||
StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BothFellowshipExclusionOptionsTrueFailsLoadNamingBothKeys()
|
||||
{
|
||||
// SF-2 (OP7 review fix, 2026-08-11): retail's own OnChanged mutual
|
||||
// exclusion means IgnoreFellowshipRequests and
|
||||
// FellowshipAutoAcceptRequests can never both be true at once —
|
||||
// turning one on always clears the other. A config declaring both
|
||||
// true would have the seeder oscillate (re-send) on every connect
|
||||
// forever with neither value ever actually honoured. Reject it at
|
||||
// load instead.
|
||||
using TemporaryConfiguration file = TemporaryConfiguration.Create(
|
||||
ConfigurationWith(Session(
|
||||
"bot",
|
||||
"BOT_PASSWORD",
|
||||
"\"characterOptions\":{\"IgnoreFellowshipRequests\":true,"
|
||||
+ "\"FellowshipAutoAcceptRequests\":true}")));
|
||||
|
||||
HeadlessConfigurationException exception = Assert.Throws<
|
||||
HeadlessConfigurationException>(
|
||||
() => HeadlessConfigurationLoader.Load(file.Path));
|
||||
|
||||
Assert.Contains(
|
||||
"IgnoreFellowshipRequests",
|
||||
exception.Message,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains(
|
||||
"FellowshipAutoAcceptRequests",
|
||||
exception.Message,
|
||||
StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NonBoolValueFailsLoad()
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue