fix(launcher): harden Campaign LA11 gate evidence

This commit is contained in:
Erik 2026-08-15 01:08:41 +02:00
parent 134edabed2
commit accd01a008
16 changed files with 1820 additions and 210 deletions

View file

@ -53,6 +53,7 @@ New-Item -ItemType Directory -Path $Gate | Out-Null
pwsh -NoProfile -File (Join-Path $Repo 'tools/run-campaign-la-preflight.ps1') `
-Repository $Repo `
-AllowedOutputRoot $Gate `
-OutputDirectory $Preflight
$Report = Get-Content -LiteralPath (Join-Path $Preflight 'report.json') -Raw |
@ -79,8 +80,13 @@ The expected matrix is:
`report.json` records the tested HEAD/dirty state, OS/RID, exact commands,
durations, exits, redacted logs, and SHA-256/size inventory. A normal preflight
plans 30 rows. It never launches App or Headless in connected mode and
never reads a credential. Every child starts with all inherited `ACDREAM_*`
plans 32 rows, including the connection-free PID/status/redaction and script-
safety contract suites. `-AllowedOutputRoot` must be a fresh, explicit
`campaign-la-*` gate root (or the repository `logs` root), and output must be a
fresh, empty, non-reparse strict descendant; repository, home, source, payload,
nonempty, and arbitrary existing directories are rejected. The helper never
launches App or Headless in connected mode and never reads a credential. Every
child starts with all inherited `ACDREAM_*`
variables removed, so a developer shell cannot accidentally enable live,
installed-DAT, fixture-regeneration, or diagnostic gates. Only the optional
row below adds the two named DAT variables back for its three exact tests.
@ -93,6 +99,7 @@ the DAT directory may be read by tests:
```powershell
pwsh -NoProfile -File (Join-Path $Repo 'tools/run-campaign-la-preflight.ps1') `
-Repository $Repo `
-AllowedOutputRoot $Gate `
-OutputDirectory (Join-Path $Gate 'automated-preflight-with-dat') `
-IncludeInstalledDat `
-InstalledDatDirectory '<ABSOLUTE_RETAIL_DAT_DIRECTORY>'
@ -103,7 +110,7 @@ The mandatory installed-DAT result is
`ACDREAM_DAT_DIR` set inside the child environment. The helper reads the TRX
and fails if the test skipped or did anything other than pass. The action-map
and portal-asset probes are additional coverage, never substitutes. Expected
matrix size: 34 rows.
matrix size: 36 rows.
On Ubuntu/WSL, invoke the same script with native `pwsh`, a Linux repository
path, and a Linux output path. Do not treat a Windows-hosted run over
@ -170,8 +177,11 @@ pwsh -NoProfile -File (Join-Path $Repo 'tools/new-campaign-la-update-fixture.ps1
```
The helper rejects nonempty output, invalid or non-monotonic versions, missing
root executables (including the co-deployed Bake CLI), and nonabsolute inputs.
It writes fixed-timestamp sorted ZIPs,
root executables (including the co-deployed Bake CLI), nonabsolute inputs,
output/source overlap in either direction, and any reparse point in source or
output ancestry. It enumerates normalized relative paths with ordinal ordering,
never its own output, and normalizes ZIP host metadata so Windows/Linux hashes
are identical under multiple cultures. It writes fixed-timestamp sorted ZIPs,
the exact LA10 v1 SHA/size manifest, `fixture-report.json`, a loopback-only
server (with optional bounded `-MaximumRequests` smoke mode), and an atomic A/B
selector. Both generated helpers reject a `-Root` other than their own fixture
@ -221,24 +231,56 @@ must preserve the same validated suffix through helper and confirmation
restarts. The launcher, profiles, installer, current-version store, updater,
session composer, and orchestrator must all use this one exact path set.
For every play/probe row, copy the session id shown in the launcher's Sessions
list into `<SESSION_ID>`, then run:
For every play/probe row, start this gate-only PID watcher immediately before
clicking Refresh/Play. It correlates only the unique isolated session-config
path, records neither command line nor config contents, and must finish while
the child is still live:
```powershell
$Status = Join-Path $WinCache 'launcher/sessions/<SESSION_ID>/status.jsonl'
$CapturePath = Join-Path $Evidence '<ROW>-process.capture.json'
$CaptureStart = [DateTimeOffset]::UtcNow
$CaptureInfo = [Diagnostics.ProcessStartInfo]::new()
$CaptureInfo.FileName = (Get-Command pwsh).Source
$CaptureInfo.UseShellExecute = $false
$CaptureInfo.CreateNoWindow = $true
foreach ($Value in @(
'-NoProfile', '-File', (Join-Path $Repo 'tools/capture-campaign-la-session-process.ps1'),
'-SessionsDirectory', (Join-Path $WinCache 'launcher/sessions'),
'-CreatedAfterUtc', $CaptureStart.ToString('O'),
'-ReportPath', $CapturePath, '-WaitSeconds', '60')) {
$CaptureInfo.ArgumentList.Add($Value)
}
$CaptureProcess = [Diagnostics.Process]::Start($CaptureInfo)
# Click exactly one Refresh/Play action now, then wait for capture.
$CaptureProcess.WaitForExit()
if ($CaptureProcess.ExitCode) { throw 'Stop: live child PID capture failed.' }
$Capture = Get-Content -LiteralPath $CapturePath -Raw | ConvertFrom-Json
$SessionConfig = Join-Path $WinCache "launcher/sessions/$($Capture.sessionId)/session.json"
$Status = Join-Path $WinCache "launcher/sessions/$($Capture.sessionId)/status.jsonl"
# After Stop and terminal status:
pwsh -NoProfile -File (Join-Path $Repo 'tools/test-campaign-la-session-status.ps1') `
-StatusFile $Status `
-Mode '<probe|guiSelect|gui|headless>' `
-ExpectedSessionId '<SESSION_ID>' `
-ExpectedProcessId $Capture.processId `
-SessionConfigPath $SessionConfig `
-CredentialProfilePath (Join-Path $WinConfig 'launcher-profiles.json') `
-ExpectedSessionId $Capture.sessionId `
-ReportPath (Join-Path $Evidence '<ROW>-status.validation.json')
```
Add `-ExpectedPlugin acdream.smoke` to rows DF. The validator enforces exact
v1 fields **and property order**, one session id, UTC monotonic timestamps,
mode-specific lifecycle order, exit code 0/reason, no unexpected plugin/login
command failure, credential redaction, and no surviving App/Headless process.
Its report contains event names and a hash, not account, character, command, or
error payloads. Keep raw `session.json`/`status.jsonl` local; never upload them.
command failure, exact terminal `disconnected.reason == stopped`, credential
redaction, and that exact captured PID is gone. Optional config-path correlation
uses Windows CIM or Linux `/proc/*/cmdline`; it never globally scans a process
name, so unrelated same-name processes and Linux's 15-character names do not
affect the result. The validator verifies owner-only profile access, reads only
password/secret fields in memory, recursively checks every allowed status
string (including command/error text), and reports only the forbidden-value
count and status hash—never credential content or a credential hash. Keep the
profile and raw `session.json`/`status.jsonl` local; never upload them.
## 5. Serial Windows user rows AH
@ -374,8 +416,10 @@ called. This connected row proves the actual ACE graceful-logout half. Issue
2. Confirm ordinary selection enables Enter/Delete and disables Restore. Click
Delete, inspect the retail confirmation dialog, cancel once, and confirm no
state change.
3. Delete again and confirm. Verify the wait dialog, greyed roster row/countdown,
disabled Enter/Delete, and enabled Restore. Save `G-deleted.png`.
3. Delete again and confirm. Verify the wait dialog, greyed/pending-delete
roster state, constant boolean-ish nonzero `secondsGreyedOut`, disabled
Enter/Delete, and enabled Restore. The UI must display no countdown. Save
`G-deleted.png`.
4. Click Restore and confirm the same GUID returns to ordinary state with
Enter/Delete enabled and Restore disabled. Save `G-restored.png`.
5. Close through launcher **Stop**, confirm graceful terminal status and ACE
@ -385,6 +429,9 @@ called. This connected row proves the actual ACE graceful-logout half. Issue
pwsh -NoProfile -File (Join-Path $Repo 'tools/test-campaign-la-session-status.ps1') `
-StatusFile (Join-Path $WinCache 'launcher/sessions/<SESSION_ID>/status.jsonl') `
-Mode guiSelect `
-ExpectedProcessId '<CAPTURED_CHILD_PID>' `
-SessionConfigPath (Join-Path $WinCache 'launcher/sessions/<SESSION_ID>/session.json') `
-CredentialProfilePath (Join-Path $WinConfig 'launcher-profiles.json') `
-ExpectNoEnteredWorld `
-ExpectedSessionId '<SESSION_ID>' `
-ExpectedPlugin acdream.smoke `
@ -489,7 +536,9 @@ Complete this exact serial matrix:
`stat -c '%a' "$LinuxConfig/launcher-profiles.json"`; the exact result must
be `600`.
3. **Probe twice:** run Refresh twice, validate both status streams in `probe`
mode with native `pwsh`, and confirm ACE clears the account after each.
mode with native `pwsh`, using the same pre-action watcher and exact PID,
Linux session-config path, and `$LinuxConfig/launcher-profiles.json`; confirm
ACE clears the account after each.
4. **Platform posture:** confirm GUI and GUI-select client buttons are disabled
and show the explicit Modern Runtime Slice-L message. Do not bypass this
disablement and do not claim a Linux graphical-client gate.
@ -521,6 +570,7 @@ logs/campaign-la-user-gate-<timestamp>/
evidence/A-install-hashes.json
evidence/B-*.png
evidence/C-probe-{1,2}-status.validation.json
evidence/*-process.capture.json
evidence/D-*.png + D-status.validation.json
evidence/E-*.png + E-status.validation.json
evidence/F-*.png + F-status.validation.json