fix(gates): require secret-safe observer credentials

This commit is contained in:
Erik 2026-08-22 14:20:14 +02:00
parent 772526d0cd
commit fa3e7978ec
5 changed files with 36 additions and 22 deletions

View file

@ -505,9 +505,10 @@ The exact commands, commits, metrics, artifacts, and evidence limits are in the
ACE session, proving exact resource convergence and no stale maps/owners. ACE session, proving exact resource convergence and no stale maps/owners.
The implemented two-client gate proved the observer login and movement, but The implemented two-client gate proved the observer login and movement, but
ACE rejected the concurrent primary login when both used the available ACE rejected the concurrent primary login when both used the available
account. The gate now accepts separate primary and observer credentials and account. The gate now requires dedicated observer environment credentials,
keeps them out of its artifacts; a distinct-account connected run must close keeps them out of its artifacts, and permits independent character indexes;
the authoritative nonzero `RemotePlayers` row. a distinct-account connected run must close the authoritative nonzero
`RemotePlayers` row.
2. **EXTERNAL HARDWARE GATE.** Repeat the current clean-source RX 9070 XT 2. **EXTERNAL HARDWARE GATE.** Repeat the current clean-source RX 9070 XT
pack-off/Low/Medium/High/Auto × 1080p/1440p/4K × capped/uncapped matrix on pack-off/Low/Medium/High/Auto × 1080p/1440p/4K × capped/uncapped matrix on
every other supported physical GPU class. The integrated-AMD Auto-to-retail every other supported physical GPU class. The integrated-AMD Auto-to-retail

View file

@ -49,9 +49,11 @@ known from this audit. Campaign AR remains deliberately **not shipped** until
all three evidence classes below pass: all three evidence classes below pass:
1. **Distinct-account remote player.** Run 1. **Distinct-account remote player.** Run
`tools/run-connected-render-pack-remote-player-gate.ps1` with separate `tools/run-connected-render-pack-remote-player-gate.ps1` with the primary
primary and observer ACE credentials. Require a nonzero authoritative `ACDREAM_TEST_USER`/`ACDREAM_TEST_PASS` and distinct observer
`RemotePlayers` caster count plus a visible moving remote-player shadow. `ACDREAM_TEST_OBSERVER_USER`/`ACDREAM_TEST_OBSERVER_PASS` environment
variables. Require a nonzero authoritative `RemotePlayers` caster count plus
a visible moving remote-player shadow.
The same-account attempt is recorded in The same-account attempt is recorded in
[`artifacts/atmospheric-rendering/connected-remote-player-20260822-140355/report.json`](../../artifacts/atmospheric-rendering/connected-remote-player-20260822-140355/report.json): the observer entered and moved, then ACE rejected the concurrent primary session. The corrected gate does not serialize credentials. [`artifacts/atmospheric-rendering/connected-remote-player-20260822-140355/report.json`](../../artifacts/atmospheric-rendering/connected-remote-player-20260822-140355/report.json): the observer entered and moved, then ACE rejected the concurrent primary session. The corrected gate does not serialize credentials.
2. **Unavailable physical adapters.** On every other supported physical Vulkan 2. **Unavailable physical adapters.** On every other supported physical Vulkan

View file

@ -122,11 +122,14 @@ primary client then timed out waiting for `CharacterList`: ACE did not permit a
second simultaneous character from the same account. Both clients were closed second simultaneous character from the same account. Both clients were closed
cleanly, and the failed row is not represented as renderer evidence. cleanly, and the failed row is not represented as renderer evidence.
The gate now accepts a separate observer account and password, keeps credentials The gate now reads separate observer credentials from
out of its report, fails fast on session failure, and records whether distinct `ACDREAM_TEST_OBSERVER_USER`/`ACDREAM_TEST_OBSERVER_PASS`, rejects same-account
accounts were configured. The authoritative nonzero `RemotePlayers` caster row configuration before launch, keeps credentials out of its report, and fails
and visible moving remote-player shadow therefore require only access to a fast on session failure. Character indexes are independently selectable because
second account; no missing renderer or test-harness implementation remains. index zero on two accounts still names distinct characters; the entered-world
identity assertion is authoritative. The nonzero `RemotePlayers` caster row and
visible moving remote-player shadow therefore require only access to a second
account; no missing renderer or test-harness implementation remains.
## Final automated gate ## Final automated gate

View file

@ -240,10 +240,10 @@ Remove-ConnectedGraphicalSessionConfig -State $state -Path $sessionConfigPath
{ {
string source = ReadTool(RemotePlayerScript); string source = ReadTool(RemotePlayerScript);
Assert.Contains("PrimaryCharacterIndex = 0", source, StringComparison.Ordinal); Assert.Contains("PrimaryCharacterIndex = 0", source, StringComparison.Ordinal);
Assert.Contains("ObserverCharacterIndex = 1", source, StringComparison.Ordinal); Assert.Contains("ObserverCharacterIndex = 0", source, StringComparison.Ordinal);
Assert.Contains("[string]$ObserverAccount", source, StringComparison.Ordinal); Assert.Contains("[string]$ObserverAccount = $env:ACDREAM_TEST_OBSERVER_USER", source, StringComparison.Ordinal);
Assert.Contains("[string]$ObserverPassword", source, StringComparison.Ordinal); Assert.Contains("[string]$ObserverPassword = $env:ACDREAM_TEST_OBSERVER_PASS", source, StringComparison.Ordinal);
Assert.Contains("Primary and observer character indexes must differ", source, StringComparison.Ordinal); Assert.Contains("Remote-player shadow evidence requires distinct ACE accounts", source, StringComparison.Ordinal);
Assert.Contains("Get-ConnectedGateBinaryIdentity", source, StringComparison.Ordinal); Assert.Contains("Get-ConnectedGateBinaryIdentity", source, StringComparison.Ordinal);
Assert.Contains("-CharacterIndex $ObserverCharacterIndex", source, StringComparison.Ordinal); Assert.Contains("-CharacterIndex $ObserverCharacterIndex", source, StringComparison.Ordinal);
Assert.Contains("-CharacterIndex $PrimaryCharacterIndex", source, StringComparison.Ordinal); Assert.Contains("-CharacterIndex $PrimaryCharacterIndex", source, StringComparison.Ordinal);
@ -254,6 +254,14 @@ Remove-ConnectedGraphicalSessionConfig -State $state -Path $sessionConfigPath
Assert.Contains("Restore-ConnectedRenderPackGateEnvironment $primaryState", source, StringComparison.Ordinal); Assert.Contains("Restore-ConnectedRenderPackGateEnvironment $primaryState", source, StringComparison.Ordinal);
Assert.Contains("Restore-ConnectedRenderPackGateEnvironment $observerState", source, StringComparison.Ordinal); Assert.Contains("Restore-ConnectedRenderPackGateEnvironment $observerState", source, StringComparison.Ordinal);
int reportStart = source.IndexOf("$report = [pscustomobject][ordered]@{", StringComparison.Ordinal);
Assert.True(reportStart >= 0);
string reportSource = source[reportStart..];
Assert.DoesNotContain("$Account", reportSource, StringComparison.Ordinal);
Assert.DoesNotContain("$Password", reportSource, StringComparison.Ordinal);
Assert.DoesNotContain("$ObserverAccount", reportSource, StringComparison.Ordinal);
Assert.DoesNotContain("$ObserverPassword", reportSource, StringComparison.Ordinal);
string observer = ReadTool("connected-render-pack-remote-observer.route.txt"); string observer = ReadTool("connected-render-pack-remote-observer.route.txt");
AssertAppearsInOrder( AssertAppearsInOrder(
observer, observer,

View file

@ -3,10 +3,10 @@ param(
[string]$Repository = (Resolve-Path (Join-Path $PSScriptRoot '..')).Path, [string]$Repository = (Resolve-Path (Join-Path $PSScriptRoot '..')).Path,
[string]$Account = $env:ACDREAM_TEST_USER, [string]$Account = $env:ACDREAM_TEST_USER,
[string]$Password = $env:ACDREAM_TEST_PASS, [string]$Password = $env:ACDREAM_TEST_PASS,
[string]$ObserverAccount, [string]$ObserverAccount = $env:ACDREAM_TEST_OBSERVER_USER,
[string]$ObserverPassword, [string]$ObserverPassword = $env:ACDREAM_TEST_OBSERVER_PASS,
[ValidateRange(0, [int]::MaxValue)][int]$PrimaryCharacterIndex = 0, [ValidateRange(0, [int]::MaxValue)][int]$PrimaryCharacterIndex = 0,
[ValidateRange(0, [int]::MaxValue)][int]$ObserverCharacterIndex = 1, [ValidateRange(0, [int]::MaxValue)][int]$ObserverCharacterIndex = 0,
[switch]$SkipBuild, [switch]$SkipBuild,
[int]$LoginTimeoutSeconds = 90 [int]$LoginTimeoutSeconds = 90
) )
@ -19,8 +19,9 @@ if ([string]::IsNullOrWhiteSpace($Account)) { $Account = 'testaccount' }
if ([string]::IsNullOrWhiteSpace($Password)) { $Password = 'testpassword' } if ([string]::IsNullOrWhiteSpace($Password)) { $Password = 'testpassword' }
if ([string]::IsNullOrWhiteSpace($ObserverAccount)) { $ObserverAccount = $Account } if ([string]::IsNullOrWhiteSpace($ObserverAccount)) { $ObserverAccount = $Account }
if ([string]::IsNullOrWhiteSpace($ObserverPassword)) { $ObserverPassword = $Password } if ([string]::IsNullOrWhiteSpace($ObserverPassword)) { $ObserverPassword = $Password }
if ($PrimaryCharacterIndex -eq $ObserverCharacterIndex) { if ([string]::Equals($Account, $ObserverAccount, [StringComparison]::OrdinalIgnoreCase)) {
throw 'Primary and observer character indexes must differ.' throw ('Remote-player shadow evidence requires distinct ACE accounts. ' +
'Set ACDREAM_TEST_OBSERVER_USER and ACDREAM_TEST_OBSERVER_PASS.')
} }
$startedUtc = [DateTime]::UtcNow.ToString('O') $startedUtc = [DateTime]::UtcNow.ToString('O')
@ -313,8 +314,7 @@ finally {
TrackedSourceStatus = if ($null -eq $binaryIdentity) { @() } else { @($binaryIdentity.SourceTrackedStatus) } TrackedSourceStatus = if ($null -eq $binaryIdentity) { @() } else { @($binaryIdentity.SourceTrackedStatus) }
PrimaryCharacterIndex = $PrimaryCharacterIndex PrimaryCharacterIndex = $PrimaryCharacterIndex
ObserverCharacterIndex = $ObserverCharacterIndex ObserverCharacterIndex = $ObserverCharacterIndex
DistinctAccountsConfigured = -not [string]::Equals( DistinctAccountsConfigured = $true
$Account, $ObserverAccount, [StringComparison]::OrdinalIgnoreCase)
Result = $result Result = $result
Failure = $failure Failure = $failure
} }