merge: Campaign LA LA11 - automated closeout review-closed
# Conflicts: # docs/plans/2026-08-14-launcher-campaign.md
This commit is contained in:
commit
d39f3098d5
39 changed files with 6097 additions and 204 deletions
|
|
@ -26,53 +26,41 @@ What does NOT go here:
|
|||
|
||||
## #397 — Windows: LauncherProcessSupervisor.Stop has no reliable graceful-stop signal for a no-window console host
|
||||
|
||||
**Status:** OPEN
|
||||
**Status:** IN-PROGRESS — the isolated process-group implementation and real
|
||||
Windows fixtures are complete; the LA11 connected acceptance row remains
|
||||
required before closure.
|
||||
**Severity:** MODERATE (a hard-killed `AcDream.Headless` leaves the ACE
|
||||
account session stuck for several minutes — a documented project landmine;
|
||||
see CLAUDE.md "Logout-before-reconnect")
|
||||
**Filed:** 2026-08-14 (Campaign LA plan §LA3 review-fix round, finding F3)
|
||||
**Component:** Launcher.Core / process supervision
|
||||
|
||||
**Description.** `LauncherProcessSupervisor.Stop` now attempts a graceful
|
||||
stop signal (`ILauncherChildProcess.TryRequestGracefulStop`) BEFORE
|
||||
`CloseMainWindow`. On Linux this sends `SIGINT` via a `libc` P/Invoke
|
||||
(`kill(pid, 2)`), which the K4-proven headless host already turns into an
|
||||
ACE-confirmed graceful logout. On Windows there is no equivalent today for a
|
||||
console process with no message-pump window: `CloseMainWindow` is a no-op
|
||||
for a console host (there is no `HWND` to target), and
|
||||
`GenerateConsoleCtrlEvent` cannot usefully target an arbitrary child process
|
||||
today — Windows delivers console control events to every process attached
|
||||
to the SAME console as the calling process, so an unscoped call would also
|
||||
signal the launcher itself (and anything else sharing that console), not
|
||||
just the intended child. `TryRequestGracefulStop` therefore returns `false`
|
||||
on Windows unconditionally, and `Stop` degrades straight to `CloseMainWindow`
|
||||
(still a no-op for a console child) and then the timeout-driven `Kill()` —
|
||||
exactly the hard-kill behavior this finding was written to describe, just
|
||||
with a documented (rather than silent) gap.
|
||||
**Implementation checkpoint.** `LauncherProcessSupervisor.Stop` attempts
|
||||
`ILauncherChildProcess.TryRequestGracefulStop` before `CloseMainWindow` and
|
||||
the timeout/kill fallback. Linux retains its K4-proven targeted `SIGINT`.
|
||||
On Windows, console-capable launcher specs now use a narrow no-shell
|
||||
`CreateProcessW` seam with `CREATE_NEW_PROCESS_GROUP`, a suspended start, and
|
||||
an explicit inherited-handle list that preserves only redirected stdin plus
|
||||
stdout/stderr. A consoleless Avalonia parent briefly allocates and hides a
|
||||
console for the creation transaction, detaches after the new group inherits
|
||||
it, and later attaches only long enough to send
|
||||
`GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, childProcessGroupId)`. Each such
|
||||
child is therefore both the root of its own process group and, for the normal
|
||||
Explorer-launched case, attached to its own console. Graphical children opt
|
||||
out and retain the ordinary `Process`/`WM_CLOSE` path.
|
||||
|
||||
**Known fix direction (not yet implemented).** Spawn the Windows child with
|
||||
the `CREATE_NEW_PROCESS_GROUP` creation flag (available via a native
|
||||
`CreateProcess` call or by setting it on the `ProcessStartInfo`/`Process`
|
||||
plumbing in `SystemChildProcess`) so the child gets its own console process
|
||||
group, detached from the launcher's own group. Then
|
||||
`TryRequestGracefulStop` on Windows calls
|
||||
`GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, childProcessGroupId)` —
|
||||
`CTRL_BREAK` (unlike `CTRL_C`) can target a specific process group ID and,
|
||||
unlike `CTRL_CLOSE`/`CTRL_LOGOFF`/`CTRL_SHUTDOWN`, is deliverable to a
|
||||
process that has installed no console-control handler at all (the default
|
||||
CRT handler treats it as a terminating signal, so `AcDream.Headless` doesn't
|
||||
strictly need new code to receive SOME form of shutdown from it) — though
|
||||
wiring a real `SetConsoleCtrlHandler` handler that routes `CTRL_BREAK` into
|
||||
the same graceful-shutdown path K4 already built for Linux SIGINT is the
|
||||
better long-term target, so a Windows headless launch gets the identical
|
||||
ACE-confirmed graceful logout instead of just "exits somehow."
|
||||
Two real Windows fixture gates cover both a console parent and a consoleless
|
||||
WinExe parent. They prove exact complex argv, redirected stdin, receipt of a
|
||||
targeted CTRL_BREAK marker, exit code 0 before timeout, no supervisor `Kill`,
|
||||
and a sibling process group that remains running until it receives its own
|
||||
targeted break. Safe-handle cleanup, early-failure termination, and the
|
||||
Linux SIGINT gate remain covered by the Launcher.Core suite.
|
||||
|
||||
**Acceptance for closing this issue:** `SystemChildProcess` spawns Windows
|
||||
children with `CREATE_NEW_PROCESS_GROUP`; `TryRequestGracefulStop` sends
|
||||
`CTRL_BREAK_EVENT` to that child's process group on Windows; a live
|
||||
connected gate proves `AcDream.Headless` exits gracefully (ACE clears the
|
||||
session immediately, not after the ~3-minute stale-session window) when
|
||||
stopped via `LauncherProcessSupervisor.Stop` on Windows, matching the
|
||||
**Acceptance for closing this issue:** automated process-group and targeted-
|
||||
signal coverage is complete. Keep the issue IN-PROGRESS until the LA11 live
|
||||
connected row proves `AcDream.Headless` exits gracefully and ACE clears the
|
||||
session immediately (not after the ~3-minute stale-session window) when
|
||||
stopped through `LauncherProcessSupervisor.Stop` on Windows, matching the
|
||||
Linux SIGINT behavior.
|
||||
|
||||
## #396 — Configure Keyboard: no capture-instruction dialog on a mapping-button click
|
||||
|
|
|
|||
|
|
@ -322,7 +322,10 @@ src/
|
|||
|
||||
AcDream.Launcher.Core/ BCL-only launcher state/orchestration owner
|
||||
Profiles/ -> sole credential/profile document + CRUD owner
|
||||
Launching/ -> config composition and supervised process seams
|
||||
Launching/ -> config composition and supervised process seams;
|
||||
Windows console hosts are no-shell, redirected-
|
||||
stdin process-group leaders receiving targeted
|
||||
CTRL_BREAK, while Linux hosts receive SIGINT
|
||||
Status/ -> incremental host-status parsing/tailing
|
||||
Orchestration/ -> immutable UI snapshots, typed actions,
|
||||
capability gates, and running-session lifetime
|
||||
|
|
@ -350,6 +353,14 @@ src/
|
|||
-> references Platform only; no Avalonia or game-host dependency
|
||||
|
||||
AcDream.Launcher/ Avalonia 12 Windows/Linux desktop shell
|
||||
Startup/Program -> one immutable process-local option graph before
|
||||
owner construction; config/data/cache require
|
||||
three absolute normalized roots and one exact
|
||||
`ApplicationPathSet` reaches profiles, installer,
|
||||
versions/updater, sessions, cache, orchestration
|
||||
-> manifest override reaches only update composition,
|
||||
is never persisted, and permits HTTP only for a
|
||||
loopback fixture; production remains pinned HTTPS
|
||||
ViewModels/ -> thin MVVM projection over Launcher.Core,
|
||||
including the first-run DAT/bake wizard and
|
||||
nonfatal startup/manual update state, actions,
|
||||
|
|
|
|||
|
|
@ -697,7 +697,10 @@ forms `COM¹`/`COM²`/`COM³` and `LPT¹`/`LPT²`/`LPT³`, including extensions.
|
|||
|
||||
## LA11 — closeout
|
||||
|
||||
- One connected-gate script `docs/research/2026-XX-XX-campaign-la-test-script.md`
|
||||
- One exact operator script
|
||||
`docs/research/2026-08-14-campaign-la-test-script.md`, fronted by the
|
||||
connection-free `tools/run-campaign-la-preflight.ps1` and followed by
|
||||
serial user rows,
|
||||
covering: all three launch modes vs local ACE, probe round-trip ×2 (no
|
||||
lingering session), char-select visual matrix + delete flow, login-commands
|
||||
+ plugin behavior on both hosts, add-server/add-account purely in UI,
|
||||
|
|
@ -729,4 +732,4 @@ LA6 adds CH-regression scrutiny; LA0 adds guard-integrity scrutiny.
|
|||
| LA8 | **DONE + MERGED 2026-08-14** | `6cfab727`, `aeac874d`, `1dd5706e`, merge `fe63ce18` | Initial retail/architecture review found 4 issues; first narrow re-review left 2 retry-transaction/order gaps; final narrow re-review PASS | Installed DAT enum table 5 proves `0x10000005 -> 0x21000004`, root `0x1000039A`, exact flat list/buttons/templates/dialog assets, and no viewport. Runtime remains the only selection owner; row sizing, modal priority/retry, restore ordering, reset/disposal, and explicit live-DAT skip/probe are covered. Branch full suite 13,796+5 skip; LA11 owns physical visual/live-ACE acceptance. |
|
||||
| LA9 | **DONE + MERGED 2026-08-14** | `ff6ebb6a`, `3f688951`, `208a70ac`, merge `2198a0cc` | Initial integrity review found 5 issues; narrow re-review left one orphan-child publication race; final narrow re-review PASS | First-run installer validates four DATs, consumes strict v1 Bake JSONL, preserves/reverifies SHA+size+tool-version records, and co-publishes self-contained launcher+Bake. Cross-process install/publish locks plus durable nonce prevent post-recovery mutation across real parent-only hard kills on Windows/Linux. Branch full suite 13,799+4 skip; real retail-DAT bake remains LA11. |
|
||||
| LA10 | **DONE + MERGED 2026-08-14** | `2d2a5b50`, `1955ca8a`, `09d84387`, merge `da4fb3de` | Initial architecture/security review found 10 crash, trust, integrity, cleanup, and lifecycle issues; first narrow re-review left one rollback-source P1; final narrow re-review PASS | Production feeds and redirects are HTTPS-only, fixture loopback trust is explicit, downloads and archives are bounded and verified, version activation and rollback are atomic, active sessions hold the cross-process update lease, and schema-v3 self-update recovery verifies every prior/replacement file before apply, rollback, or restart. Real Windows/Linux process tests cover kill boundaries, staging races, lease deferral, corrupt backups, junctions/symlinks, and fail-closed recovery. Branch gates: Core 302/302 and Launcher 29/29 on Windows/WSL, full Release 13,945+4 skip, win/linux self-contained publishes. Integrated LA0–LA10 gate: 13,972+5 skip. |
|
||||
| LA11 | — | | | |
|
||||
| LA11 | **AUTOMATED CLOSEOUT REVIEW-CLOSED 2026-08-15 — USER GATE PENDING** | `f881e5b4`, `134edabe`, `accd01a0`, `9f9c1167`; merge pending | Initial dual-lens review found 7 startup/evidence/safety issues; first narrow re-review left 2 PID-reuse/ZIP-mode gaps; final narrow re-review PASS | Strict isolated roots and process-local feed override compose one exact launcher path graph. Windows targeted CTRL_BREAK is group-isolated and preserves stdin; exact-PID/start-identity status validation, credential-value scanning, deterministic Unix-mode A/B fixtures, Windows/native-Linux helper safety, and the exact A–I operator script are implemented. Clean branch preflight passed 32/32 with 13,985 tests + 4 skips. No connected/UI/real-DAT row has run; campaign shipment and #397 closure remain pending the user gate. |
|
||||
|
|
|
|||
631
docs/research/2026-08-14-campaign-la-test-script.md
Normal file
631
docs/research/2026-08-14-campaign-la-test-script.md
Normal file
|
|
@ -0,0 +1,631 @@
|
|||
# Campaign LA11 — automated preflight and connected user gate
|
||||
|
||||
**Status:** implementation checkpoint only. Run this script after the reviewed
|
||||
LA10/LA11 commits are integrated and the campaign branch is clean. Campaign LA,
|
||||
the Linux graphical client, and issue #397 remain open until the user records a
|
||||
verdict for every applicable row below.
|
||||
|
||||
This is the single Campaign LA operator script. The automated section is
|
||||
display-free and connection-free. Rows A–I are deliberately manual and serial:
|
||||
they use real retail DATs, a local ACE server, user-entered credentials, and
|
||||
visual judgment that automation cannot supply.
|
||||
|
||||
## 1. Safety boundary and required inputs
|
||||
|
||||
Use placeholders throughout; never paste a password into a terminal, this
|
||||
document, a screenshot, or a gate report.
|
||||
|
||||
- `<ABSOLUTE_REPOSITORY_ROOT>`: a clean Campaign LA worktree at the exact commit
|
||||
under test.
|
||||
- `<ABSOLUTE_RETAIL_DAT_DIRECTORY>`: a read-only source containing
|
||||
`client_portal.dat`, `client_cell_1.dat`, `client_highres.dat`, and
|
||||
`client_local_English.dat`.
|
||||
- `<ACE_PORT>`, `<LA11_SERVER>`, and `<LA11_ACCOUNT>`: a local ACE endpoint and
|
||||
account. Enter the account password only in the launcher's masked Password
|
||||
field. The launcher intentionally stores it as plaintext in the **isolated**
|
||||
`launcher-profiles.json`; children receive it through redirected stdin.
|
||||
- `<OBSERVER_CHARACTER>`: a second user-controlled character that can observe a
|
||||
private `/tell` from each play mode.
|
||||
- `<DISPOSABLE_CHARACTER>`: a server-operator-approved disposable character.
|
||||
Never substitute a primary character. If none exists, provision one with the
|
||||
local server's normal admin procedure before row G.
|
||||
- Windows 11 x64, PowerShell 7, .NET 10 SDK, a local ACE server, and a supported
|
||||
Vulkan Windows machine for rows A–H. Ubuntu x64 with PowerShell 7 and a Linux
|
||||
desktop/WSLg is required for row I. The Avalonia launcher is supported on
|
||||
Linux; `gui` and `guiSelect` **client** actions must remain disabled with the
|
||||
Modern Runtime Slice-L explanation.
|
||||
|
||||
Close every unrelated `AcDream.App`, `acdream-headless`, and acdream launcher
|
||||
before starting. Do not run another acdream gate in parallel. All generated
|
||||
files must stay below one new gate directory; the canonical `%APPDATA%`,
|
||||
`%LOCALAPPDATA%`, and XDG acdream roots are out of scope.
|
||||
|
||||
## 2. Automated preflight — no UI, connection, credential, or bake
|
||||
|
||||
In PowerShell 7 on Windows:
|
||||
|
||||
```powershell
|
||||
$Repo = [IO.Path]::GetFullPath('<ABSOLUTE_REPOSITORY_ROOT>')
|
||||
$Stamp = [DateTime]::UtcNow.ToString('yyyyMMdd-HHmmss')
|
||||
$Gate = Join-Path $Repo "logs/campaign-la-user-gate-$Stamp"
|
||||
$Preflight = Join-Path $Gate 'automated-preflight'
|
||||
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 |
|
||||
ConvertFrom-Json
|
||||
if (-not $Report.success -or $Report.dirty) {
|
||||
throw 'Stop: automated preflight failed or recorded a dirty worktree.'
|
||||
}
|
||||
if ($Report.head -cne (git -C $Repo rev-parse HEAD).Trim()) {
|
||||
throw 'Stop: preflight HEAD does not equal the current HEAD.'
|
||||
}
|
||||
```
|
||||
|
||||
The expected matrix is:
|
||||
|
||||
| Platform | Automated command group | Required result | Typical time |
|
||||
|---|---|---|---:|
|
||||
| Windows | Release `AcDream.slnx` build, `-m:1` | exit 0 | 5–15 min |
|
||||
| Windows | complete Release solution test, serial | exit 0; ordinary known skips only | 20–60 min |
|
||||
| Windows | focused Launcher.Core update tests and launcher update/startup-option tests | exit 0 | 1–4 min |
|
||||
| Windows | canonical portable project build/test closure plus Headless `--help` and empty-config `validate` from `headless-portability.yml` | every project/CLI row exits 0 | 10–25 min |
|
||||
| Windows | self-contained single-file launcher publish for `win-x64` and `linux-x64` | launcher + bake roots present, no root DLL fallback | 3–10 min |
|
||||
| Windows | native launcher `--verify-publish` and bake `--help` with bogus `DOTNET_ROOT*` | both exit 0 | <1 min |
|
||||
| Ubuntu/WSL | run the same helper natively from the Linux path to the worktree | Linux RID report and every row exit 0 | 35–90 min |
|
||||
|
||||
`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 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.
|
||||
|
||||
### Optional installed-DAT read-only row
|
||||
|
||||
This is not a bake and must not replace row A. Add the switches below only when
|
||||
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>'
|
||||
```
|
||||
|
||||
The mandatory installed-DAT result is
|
||||
`CharacterManagementLiveDatTests` with both `ACDREAM_PROBE_LIVE_MOUNT=1` and
|
||||
`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: 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
|
||||
`wsl.exe` as the Linux row.
|
||||
|
||||
## 3. Prepare the deterministic local A/B feed
|
||||
|
||||
Build distinct, version-stamped payloads so the staged launcher really changes
|
||||
from A to B. These commands write only below `$Gate` (normal project `obj/bin`
|
||||
incremental outputs are the already-authorized build outputs):
|
||||
|
||||
```powershell
|
||||
$VersionA = '1.0.1-la11.a'
|
||||
$VersionB = '1.0.1-la11.b'
|
||||
$Payloads = Join-Path $Gate 'update-payloads'
|
||||
$Fixture = Join-Path $Gate 'update-fixture'
|
||||
|
||||
function Publish-LaRelease([string]$Version, [string]$Label) {
|
||||
$ClientWin = Join-Path $Payloads "$Label/client-win-x64"
|
||||
$LauncherWin = Join-Path $Payloads "$Label/launcher-win-x64"
|
||||
$ClientLinux = Join-Path $Payloads "$Label/client-linux-x64"
|
||||
$LauncherLinux = Join-Path $Payloads "$Label/launcher-linux-x64"
|
||||
|
||||
dotnet publish (Join-Path $Repo 'src/AcDream.App/AcDream.App.csproj') `
|
||||
-c Release -r win-x64 --self-contained true -p:Version=$Version `
|
||||
-o $ClientWin --nologo
|
||||
if ($LASTEXITCODE) { throw "App win-x64 publish failed: $Label" }
|
||||
dotnet publish (Join-Path $Repo 'src/AcDream.Headless/AcDream.Headless.csproj') `
|
||||
-c Release -r win-x64 --self-contained true -p:Version=$Version `
|
||||
-o $ClientWin --nologo
|
||||
if ($LASTEXITCODE) { throw "Headless win-x64 publish failed: $Label" }
|
||||
dotnet publish (Join-Path $Repo 'src/AcDream.Launcher/AcDream.Launcher.csproj') `
|
||||
-c Release -r win-x64 --self-contained true -p:PublishSingleFile=true `
|
||||
-p:Version=$Version -o $LauncherWin --nologo
|
||||
if ($LASTEXITCODE) { throw "Launcher win-x64 publish failed: $Label" }
|
||||
|
||||
dotnet publish (Join-Path $Repo 'src/AcDream.App/AcDream.App.csproj') `
|
||||
-c Release -r linux-x64 --self-contained true -p:Version=$Version `
|
||||
-o $ClientLinux --nologo
|
||||
if ($LASTEXITCODE) { throw "App linux-x64 publish failed: $Label" }
|
||||
dotnet publish (Join-Path $Repo 'src/AcDream.Headless/AcDream.Headless.csproj') `
|
||||
-c Release -r linux-x64 --self-contained true -p:Version=$Version `
|
||||
-o $ClientLinux --nologo
|
||||
if ($LASTEXITCODE) { throw "Headless linux-x64 publish failed: $Label" }
|
||||
dotnet publish (Join-Path $Repo 'src/AcDream.Launcher/AcDream.Launcher.csproj') `
|
||||
-c Release -r linux-x64 --self-contained true -p:PublishSingleFile=true `
|
||||
-p:Version=$Version -o $LauncherLinux --nologo
|
||||
if ($LASTEXITCODE) { throw "Launcher linux-x64 publish failed: $Label" }
|
||||
}
|
||||
|
||||
Publish-LaRelease $VersionA 'A'
|
||||
Publish-LaRelease $VersionB 'B'
|
||||
|
||||
pwsh -NoProfile -File (Join-Path $Repo 'tools/new-campaign-la-update-fixture.ps1') `
|
||||
-OutputDirectory $Fixture `
|
||||
-ClientWinX64DirectoryA (Join-Path $Payloads 'A/client-win-x64') `
|
||||
-LauncherWinX64DirectoryA (Join-Path $Payloads 'A/launcher-win-x64') `
|
||||
-ClientLinuxX64DirectoryA (Join-Path $Payloads 'A/client-linux-x64') `
|
||||
-LauncherLinuxX64DirectoryA (Join-Path $Payloads 'A/launcher-linux-x64') `
|
||||
-ClientWinX64DirectoryB (Join-Path $Payloads 'B/client-win-x64') `
|
||||
-LauncherWinX64DirectoryB (Join-Path $Payloads 'B/launcher-win-x64') `
|
||||
-ClientLinuxX64DirectoryB (Join-Path $Payloads 'B/client-linux-x64') `
|
||||
-LauncherLinuxX64DirectoryB (Join-Path $Payloads 'B/launcher-linux-x64')
|
||||
```
|
||||
|
||||
The helper rejects nonempty output, invalid or non-monotonic versions, missing
|
||||
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 origin to Unix on both hosts so
|
||||
Windows/Linux hashes are identical under multiple cultures while native Linux
|
||||
extraction retains 0755 for App/Headless/Launcher/Bake and 0644 for ordinary
|
||||
files. 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
|
||||
directory. The generator does not download or mutate payload sources.
|
||||
|
||||
Start the Windows loopback server without a shell or visible helper window:
|
||||
|
||||
```powershell
|
||||
$ServerInfo = [Diagnostics.ProcessStartInfo]::new()
|
||||
$ServerInfo.FileName = (Get-Command pwsh).Source
|
||||
$ServerInfo.UseShellExecute = $false
|
||||
$ServerInfo.CreateNoWindow = $true
|
||||
foreach ($Value in @(
|
||||
'-NoProfile', '-File', (Join-Path $Fixture 'serve-fixture.ps1'),
|
||||
'-Root', $Fixture, '-Port', '43119')) {
|
||||
$ServerInfo.ArgumentList.Add($Value)
|
||||
}
|
||||
$FixtureServer = [Diagnostics.Process]::Start($ServerInfo)
|
||||
$ManifestUri = 'http://127.0.0.1:43119/manifest.json'
|
||||
if ((Invoke-RestMethod -Uri $ManifestUri).version -cne $VersionA) {
|
||||
throw 'Stop: local fixture did not begin on release A.'
|
||||
}
|
||||
```
|
||||
|
||||
## 4. Windows isolated launcher command and evidence rule
|
||||
|
||||
```powershell
|
||||
$WinRoot = Join-Path $Gate 'windows-roots'
|
||||
$WinConfig = Join-Path $WinRoot 'config'
|
||||
$WinData = Join-Path $WinRoot 'data'
|
||||
$WinCache = Join-Path $WinRoot 'cache'
|
||||
$Evidence = Join-Path $Gate 'evidence'
|
||||
New-Item -ItemType Directory -Path $Evidence | Out-Null
|
||||
|
||||
$LauncherA = Join-Path $Payloads 'A/launcher-win-x64/acdream-launcher.exe'
|
||||
$LauncherArguments = @(
|
||||
'--config-dir', $WinConfig,
|
||||
'--data-dir', $WinData,
|
||||
'--cache-dir', $WinCache,
|
||||
'--update-manifest-uri', $ManifestUri)
|
||||
& $LauncherA @LauncherArguments
|
||||
```
|
||||
|
||||
All four options are process-local. The three roots are an indivisible set;
|
||||
the local feed reaches only the updater and is never persisted. A self-update
|
||||
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, start this gate-only PID watcher immediately before
|
||||
clicking Refresh/Play. It correlates only the unique isolated session-config
|
||||
path, records neither raw command line nor config contents, and must finish
|
||||
while the child is still live. Its safe sidecar contains the normalized config
|
||||
path, a sanitized command fingerprint, and PID plus an OS-native process-start
|
||||
identity so later PID reuse cannot become a false leak:
|
||||
|
||||
```powershell
|
||||
$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>' `
|
||||
-ProcessCapturePath $CapturePath `
|
||||
-CredentialProfilePath (Join-Path $WinConfig 'launcher-profiles.json') `
|
||||
-ExpectedSessionId $Capture.sessionId `
|
||||
-ReportPath (Join-Path $Evidence '<ROW>-status.validation.json')
|
||||
```
|
||||
|
||||
Add `-ExpectedPlugin acdream.smoke` to rows D–F. 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, exact terminal `disconnected.reason == stopped`, credential
|
||||
redaction, and that exact captured process instance is gone. Independent exact
|
||||
config-path correlation uses Windows CIM or Linux `/proc/*/cmdline`; it never
|
||||
globally scans a process name, treats a different start identity on a reused PID
|
||||
as a different process, and is unaffected by unrelated same-name processes or
|
||||
Linux's 15-character names. 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, raw `session.json`/`status.jsonl`, and raw process-capture sidecar
|
||||
(which contains the absolute isolated path) local; never upload them.
|
||||
|
||||
## 5. Serial Windows user rows A–H
|
||||
|
||||
### A — isolated first run, real DAT bake, and release-A client baseline
|
||||
|
||||
1. Confirm the launcher opens First-run setup and all launch buttons are
|
||||
unavailable. Save a redacted screenshot as `A-first-run-required.png`.
|
||||
2. Enter `<ABSOLUTE_RETAIL_DAT_DIRECTORY>` in the wizard, select a sensible
|
||||
worker count, and click **Validate**. Confirm all four DATs pass.
|
||||
3. Click **Build and install**. Do not cancel or close the launcher. The real
|
||||
bake may take 30–180 minutes. Confirm every phase reaches **Completed** and
|
||||
the status says `Client content installed and verified. Launch is enabled.`
|
||||
4. Open **Check for updates**. Confirm available release A, click **Install
|
||||
client**, and wait for `Client update installed and activated.` Do not stage
|
||||
launcher A; the test launcher already has version A.
|
||||
5. Confirm these exact isolated artifacts exist and no `.previous-install`
|
||||
remains after success:
|
||||
|
||||
```powershell
|
||||
$RequiredA = @(
|
||||
(Join-Path $WinData 'install.json'),
|
||||
(Join-Path $WinData 'pak/acdream.pak'),
|
||||
(Join-Path $WinData 'app/current.json'))
|
||||
foreach ($Path in $RequiredA) {
|
||||
if (-not (Test-Path -LiteralPath $Path -PathType Leaf)) { throw "Missing $Path" }
|
||||
}
|
||||
$RequiredA | ForEach-Object {
|
||||
$Item = Get-Item -LiteralPath $_
|
||||
[ordered]@{
|
||||
name = $Item.Name
|
||||
size = $Item.Length
|
||||
sha256 = (Get-FileHash -LiteralPath $_ -Algorithm SHA256).Hash.ToLowerInvariant()
|
||||
}
|
||||
} | ConvertTo-Json | Set-Content -LiteralPath (Join-Path $Evidence 'A-install-hashes.json')
|
||||
```
|
||||
|
||||
Do not copy `install.json` into shared evidence because it records the local DAT
|
||||
path. Expected time: 45–200 minutes including bake.
|
||||
|
||||
### B — server/account CRUD entirely through the UI
|
||||
|
||||
1. Add `<LA11_TEMP_SERVER>` at `127.0.0.1:<UNUSED_LOCAL_PORT>`, edit its name
|
||||
and port, then remove it. Confirm Cancel/Escape makes no mutation.
|
||||
2. Add `<LA11_SERVER>` at `127.0.0.1:<ACE_PORT>`.
|
||||
3. Under it add `<LA11_TEMP_ACCOUNT>` with a user-invented throwaway field
|
||||
value, edit its account name/value, then remove it. Do not reuse a real
|
||||
password for this temporary row.
|
||||
4. Add `<LA11_ACCOUNT>` and enter its real password only in the masked field.
|
||||
5. Close and reopen the launcher with the **same** `$LauncherArguments`. Confirm
|
||||
only the real server/account persisted. Save redacted before/reopen images as
|
||||
`B-crud-before-reopen.png` and `B-crud-after-reopen.png`.
|
||||
6. Record only the profile file's size/hash, never its contents:
|
||||
|
||||
```powershell
|
||||
$Profile = Join-Path $WinConfig 'launcher-profiles.json'
|
||||
$Item = Get-Item -LiteralPath $Profile
|
||||
[ordered]@{
|
||||
size = $Item.Length
|
||||
sha256 = (Get-FileHash -LiteralPath $Profile -Algorithm SHA256).Hash.ToLowerInvariant()
|
||||
} | ConvertTo-Json | Set-Content -LiteralPath (Join-Path $Evidence 'B-profile-hash.json')
|
||||
```
|
||||
|
||||
Expected time: 10–15 minutes.
|
||||
|
||||
### C — live character probe twice, no stale ACE session
|
||||
|
||||
1. Select `<LA11_ACCOUNT>`, click **Refresh characters**, and wait for the
|
||||
probe row to finish. Confirm the roster appears without entering world.
|
||||
2. Run the validator in `probe` mode for its session id. Confirm its exact
|
||||
event order is `started, connected, characterList, disconnected, exited`,
|
||||
with no `enteredWorld`, terminal code 0, and terminal reason `probe`.
|
||||
3. In the ACE console/session administration view, confirm the account is no
|
||||
longer logged in. Save a redacted `C-probe-1-ace-cleared.png`.
|
||||
4. Repeat steps 1–3 immediately, producing a different session id,
|
||||
`C-probe-2-status.validation.json`, and `C-probe-2-ace-cleared.png`.
|
||||
5. Confirm Refresh is re-enabled and no `acdream-headless` process remains.
|
||||
|
||||
Expected time: 5–10 minutes. A stale ACE account or timeout is a gate failure;
|
||||
do not wait three minutes and call the next attempt a pass.
|
||||
|
||||
### D — `guiSelect`, retail character screen, plugin, and login command
|
||||
|
||||
1. Select one non-disposable roster character. Set its mode to `guiSelect`,
|
||||
Plugins to exactly `acdream.smoke`, and its one login command to
|
||||
`/tell <OBSERVER_CHARACTER>, LA11-D-<UNIQUE_NONSECRET_NONCE>`. Save settings.
|
||||
2. Click **GUI — character select**. Confirm the flat retail character list,
|
||||
selection highlight, Enter button, Delete/Restore swap state, dialogs, and
|
||||
absence of any invented rotating 3D preview. Save redacted
|
||||
`D-character-select.png`.
|
||||
3. Select the configured character and enter world. Confirm the observer gets
|
||||
the exact D nonce once. Save `D-observer-tell.png` with names redacted.
|
||||
4. Click **Stop** in the launcher. Confirm the game closes gracefully and ACE
|
||||
releases the account. Validate `guiSelect` with
|
||||
`-ExpectedPlugin acdream.smoke`.
|
||||
|
||||
Expected time: 5–10 minutes.
|
||||
|
||||
### E — direct `gui`, plugin, and login command
|
||||
|
||||
1. Change the same character to `gui`, retain `acdream.smoke`, and change the
|
||||
command nonce to `LA11-E-<UNIQUE_NONSECRET_NONCE>`.
|
||||
2. Click **GUI — enter world**. Confirm it selects the exact cached character,
|
||||
reaches the world, loads the plugin once, and the observer gets the E nonce
|
||||
once.
|
||||
3. Stop from the launcher, confirm ACE logout, and validate `gui` with the
|
||||
expected plugin. Save `E-world.png`, `E-observer-tell.png`, and
|
||||
`E-status.validation.json` with identifying text redacted.
|
||||
|
||||
Expected time: 5–10 minutes.
|
||||
|
||||
### F — headless, plugin/login command, and connected #397 acceptance
|
||||
|
||||
1. Change the same character to `headless`, retain `acdream.smoke`, and use
|
||||
`LA11-F-<UNIQUE_NONSECRET_NONCE>`.
|
||||
2. Click **Headless**. Confirm `pluginLoaded(acdream.smoke)`, `enteredWorld`,
|
||||
and the observer's single exact F nonce.
|
||||
3. Click **Stop** once. On Windows this must target that child's distinct
|
||||
process group with `CTRL_BREAK`; it must reach `disconnected` then
|
||||
`exited(code:0, reason:graceful)` before the timeout, without a hard kill.
|
||||
ACE must release the account immediately and the launcher must stay open.
|
||||
4. Validate `headless` with the expected plugin and save
|
||||
`F-status.validation.json` plus redacted ACE-clear evidence.
|
||||
|
||||
The real automated fixture separately proves complex argv and redirected stdin
|
||||
survive native `CreateProcessW`, the target receives `CTRL_BREAK`, a sibling
|
||||
process group receives nothing, exit 0 precedes timeout, and `Kill` is never
|
||||
called. This connected row proves the actual ACE graceful-logout half. Issue
|
||||
#397 remains open if either half is missing. Expected time: 5–10 minutes.
|
||||
|
||||
### G — disposable delete and restore
|
||||
|
||||
1. Launch `guiSelect` for `<DISPOSABLE_CHARACTER>`. Do not enter world.
|
||||
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/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
|
||||
release. Validate with:
|
||||
|
||||
```powershell
|
||||
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 `
|
||||
-ProcessCapturePath (Join-Path $Evidence 'G-process.capture.json') `
|
||||
-CredentialProfilePath (Join-Path $WinConfig 'launcher-profiles.json') `
|
||||
-ExpectNoEnteredWorld `
|
||||
-ExpectedSessionId '<SESSION_ID>' `
|
||||
-ExpectedPlugin acdream.smoke `
|
||||
-ReportPath (Join-Path $Evidence 'G-status.validation.json')
|
||||
```
|
||||
|
||||
If restore fails, stop the row, preserve evidence, and restore only that
|
||||
disposable character with the server's normal admin recovery. Never continue
|
||||
with another character. Expected time: 5–10 minutes.
|
||||
|
||||
### H — local A→B client update, active-session refusal, rollback, self-update
|
||||
|
||||
1. Record release A's `app/current.json`. Start one headless session and wait
|
||||
for `enteredWorld`.
|
||||
2. Switch the fixture atomically to B:
|
||||
|
||||
```powershell
|
||||
pwsh -NoProfile -File (Join-Path $Fixture 'set-active-release.ps1') `
|
||||
-Release B -Root $Fixture
|
||||
if ((Invoke-RestMethod -Uri $ManifestUri).version -cne $VersionB) {
|
||||
throw 'Stop: fixture did not switch to B.'
|
||||
}
|
||||
```
|
||||
|
||||
3. Open **Check for updates** and **Check again**. While the session is active,
|
||||
confirm Install client, Rollback client, and Stage launcher are disabled or
|
||||
refuse without changing `app/current.json`. Save `H-active-refusal.png`.
|
||||
4. Stop the headless session and validate its graceful status. Install client
|
||||
B. Confirm `app/current.json` names B, A is previous, all installed-file
|
||||
hashes verify, and new sessions resolve from the B directory.
|
||||
5. Click **Rollback client**. Confirm A becomes current and B becomes previous.
|
||||
Check again and install B once more, leaving B current. Save sanitized copies
|
||||
of the three pointer states as `H-pointer-a.json`, `H-pointer-b.json`, and
|
||||
`H-pointer-rollback-a.json`; they contain no credentials.
|
||||
6. Click **Stage launcher**. Confirm restart is required, then close the
|
||||
launcher normally. The copied helper must apply B and restart the launcher
|
||||
with the same config/data/cache/feed suffix.
|
||||
7. Confirm profiles, install record, and update state still come from the
|
||||
isolated roots; `campaign-la-fixture-release.txt` beside the relaunched
|
||||
executable says `release=B`; `launcher-update/pending.json` is gone; and no
|
||||
transaction backup remains. Check again and confirm launcher B is current.
|
||||
Save `H-self-update-confirmed.png` and a hash-only post-state inventory.
|
||||
|
||||
Never edit a manifest to force this row and never point the launcher at a
|
||||
non-loopback HTTP endpoint. Expected time: 15–30 minutes.
|
||||
|
||||
## 6. Row I — native Ubuntu/WSL launcher, XDG-shaped isolated roots
|
||||
|
||||
Stop the Windows launcher and fixture server only after every Windows session
|
||||
is terminal:
|
||||
|
||||
```powershell
|
||||
if (-not $FixtureServer.HasExited) {
|
||||
$FixtureServer.Kill()
|
||||
$FixtureServer.WaitForExit()
|
||||
}
|
||||
```
|
||||
|
||||
In a native Ubuntu/WSL PowerShell 7 terminal, set Linux paths. The repository
|
||||
and fixture may be read from a mounted Windows path, but roots must live on the
|
||||
Linux filesystem. Run the generated server natively so its `127.0.0.1` URLs
|
||||
cannot escape the Linux environment:
|
||||
|
||||
```powershell
|
||||
$RepoLinux = [IO.Path]::GetFullPath('<ABSOLUTE_LINUX_REPOSITORY_PATH>')
|
||||
$FixtureLinux = [IO.Path]::GetFullPath('<ABSOLUTE_LINUX_FIXTURE_PATH>')
|
||||
$PayloadsLinux = [IO.Path]::GetFullPath('<ABSOLUTE_LINUX_PAYLOADS_PATH>')
|
||||
$LinuxGate = [IO.Path]::GetFullPath('<NEW_ABSOLUTE_LINUX_GATE_ROOT>')
|
||||
$env:XDG_CONFIG_HOME = Join-Path $LinuxGate 'xdg-config-home'
|
||||
$env:XDG_DATA_HOME = Join-Path $LinuxGate 'xdg-data-home'
|
||||
$env:XDG_CACHE_HOME = Join-Path $LinuxGate 'xdg-cache-home'
|
||||
$LinuxConfig = Join-Path $env:XDG_CONFIG_HOME 'acdream'
|
||||
$LinuxData = Join-Path $env:XDG_DATA_HOME 'acdream'
|
||||
$LinuxCache = Join-Path $env:XDG_CACHE_HOME 'acdream'
|
||||
$LinuxEvidence = Join-Path $LinuxGate 'evidence'
|
||||
New-Item -ItemType Directory -Path $LinuxEvidence | Out-Null
|
||||
|
||||
pwsh -NoProfile -File (Join-Path $FixtureLinux 'set-active-release.ps1') `
|
||||
-Release A -Root $FixtureLinux
|
||||
```
|
||||
|
||||
Start `serve-fixture.ps1 -Root $FixtureLinux -Port 43119` in a dedicated native
|
||||
terminal and leave it running. In another terminal:
|
||||
|
||||
```powershell
|
||||
$LauncherLinuxA = Join-Path $PayloadsLinux 'A/launcher-linux-x64/acdream-launcher'
|
||||
& $LauncherLinuxA `
|
||||
--config-dir $LinuxConfig `
|
||||
--data-dir $LinuxData `
|
||||
--cache-dir $LinuxCache `
|
||||
--update-manifest-uri 'http://127.0.0.1:43119/manifest.json'
|
||||
```
|
||||
|
||||
Complete this exact serial matrix:
|
||||
|
||||
1. **Manual-DAT first run:** enter `<ABSOLUTE_LINUX_RETAIL_DAT_DIRECTORY>`;
|
||||
auto-detection may be empty by design. Validate, bake to
|
||||
`$LinuxData/pak/acdream.pak`, verify, then install release-A client.
|
||||
2. **CRUD:** add/edit/remove a temporary server and account entirely in the
|
||||
launcher, then add the real Linux-reachable ACE profile. Enter its password
|
||||
only in the masked field. Restart and confirm persistence. Run
|
||||
`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`, 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.
|
||||
5. **Headless:** configure `acdream.smoke` and
|
||||
`/tell <OBSERVER_CHARACTER>, LA11-I-<UNIQUE_NONSECRET_NONCE>`, launch, observe
|
||||
the tell, click Stop, and validate `headless` + expected plugin. Native Linux
|
||||
sends SIGINT and must reach graceful terminal status with no process leak.
|
||||
6. **Update:** switch the native fixture to B, prove update actions refuse while
|
||||
a headless session is active, stop it gracefully, install B, rollback to A,
|
||||
reinstall B, stage launcher B, and close normally. Confirm the relaunched
|
||||
binary's B marker, preserved explicit roots/feed, cleaned pending journal,
|
||||
and executable owner bits on App, Headless, Launcher, and Bake.
|
||||
|
||||
Copy only redacted screenshots, validation reports, pointer JSON, hashes, and
|
||||
file-mode results into `$LinuxEvidence`. Keep the Linux profile and raw session
|
||||
files local. Expected time: 60–220 minutes, dominated by the real bake.
|
||||
|
||||
## 7. Evidence, redaction, verdict, and cleanup
|
||||
|
||||
Expected evidence tree:
|
||||
|
||||
```text
|
||||
logs/campaign-la-user-gate-<timestamp>/
|
||||
automated-preflight/report.json
|
||||
automated-preflight/commands/*.log
|
||||
automated-preflight/publish/{win-x64,linux-x64}/...
|
||||
update-fixture/fixture-report.json
|
||||
update-fixture/{A,B}/manifest.json
|
||||
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
|
||||
evidence/G-*.png + G-status.validation.json
|
||||
evidence/H-*.png + H-pointer-*.json
|
||||
evidence/I-*.png + I-status.validation.json + I-modes.txt
|
||||
verdict.json
|
||||
```
|
||||
|
||||
Before sharing evidence:
|
||||
|
||||
- remove or mask account names, character names, DAT paths, hostnames other than
|
||||
loopback, and server-admin identifiers from screenshots;
|
||||
- never copy `launcher-profiles.json`, raw session configs/status streams,
|
||||
stdout/stderr that may contain user text, or environment values;
|
||||
- search the shareable evidence for the exact user-entered password and any
|
||||
gate-only sentinel secret; the match count must be zero;
|
||||
- retain SHA-256 and sizes so local raw artifacts remain auditable.
|
||||
|
||||
No additional raw child/plugin diagnostic sink is required: `pluginLoaded`,
|
||||
the strict terminal status, the observer's redacted tell evidence, and the
|
||||
automated targeted-signal fixture cover the acceptance questions without
|
||||
capturing credentials or arbitrary chat.
|
||||
|
||||
Create `verdict.json` manually with schema version 1, exact tested HEAD, rows
|
||||
A–I as `pass`, `fail`, or `notApplicable`, a short redacted note per row, and
|
||||
the user's overall verdict. Row I is not applicable only when no native
|
||||
Ubuntu/WSL desktop is available; it blocks Campaign LA shipping under the
|
||||
current Linux requirement, so it cannot be silently omitted.
|
||||
|
||||
Cleanup is graceful-first and serial:
|
||||
|
||||
1. Restore `<DISPOSABLE_CHARACTER>` and verify it is ordinary before closing
|
||||
its session.
|
||||
2. Stop every launcher session once; require a passing validator and ACE-clear
|
||||
observation. If a child survives the timeout, record the gate failure and
|
||||
its PID before any emergency termination.
|
||||
3. Close each launcher normally, then stop only the fixture-server process
|
||||
created above. Do not kill ACE as a substitute for logout evidence.
|
||||
4. Leave update pointers on B or roll the **isolated** client back to A through
|
||||
the UI; never edit pointers or journals by hand.
|
||||
5. Remove the real account through the isolated launcher UI. After review,
|
||||
delete only the explicitly recorded `$WinConfig`/`$LinuxConfig` gate roots
|
||||
that held plaintext passwords, or change the test account password. Do not
|
||||
recursively delete a computed, empty, canonical, home, repository, or XDG
|
||||
parent path.
|
||||
6. Preserve the redacted evidence and reports. The large isolated pak/payload
|
||||
trees may be removed only after resolving and checking their full paths are
|
||||
descendants of the recorded gate roots.
|
||||
|
||||
Estimated total: 3–7 hours, primarily the two real DAT bakes and full serial
|
||||
test suites. A failure stops the current row; restore/stop/collect evidence,
|
||||
then diagnose before advancing. Do not mark LA11, Campaign LA, or #397 shipped
|
||||
until the user accepts the complete applicable matrix.
|
||||
|
|
@ -331,7 +331,12 @@ the process supervisor executes.
|
|||
Campaign V); visuals settle at the user gate.
|
||||
- **Headless plugin host:** fixture plugin in the Headless suite
|
||||
(load, capability flag, teardown).
|
||||
- **Connected gates (user-driven):** every launch mode against local ACE
|
||||
- **Connected gates (user-driven):** execute the exact serial matrix in
|
||||
`docs/research/2026-08-14-campaign-la-test-script.md` only after its
|
||||
connection-free automated preflight passes. The launcher uses one immutable
|
||||
process-local config/data/cache path set for the whole matrix; the local feed
|
||||
URI reaches only updater composition and is never persisted. Cover every
|
||||
launch mode against local ACE
|
||||
(gui / guiSelect / headless), the character probe (fresh account →
|
||||
refresh → roster appears, and repeated probes leaving no stale ACE
|
||||
session), clean-profile first-run wizard end-to-end, staged-manifest
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue