An adversarial audit of the headless peer-to-peer scenario suite
(crates/lanspread-peer-cli/scripts/run_extended_scenarios.py, driven by
`just peer-cli-tests`) found assertions that passed even when the behavior
they claim to test was not happening, plus timing races and doc-vs-code
divergences. A full baseline run (S1-S47) passed beforehand, confirming these
were test-quality gaps, not peer regressions; the baseline output itself
exposed the worst offenders -- e.g. S14 chunk totals {128 MiB, 1 MiB} (a
two-chunk file whose "balanced within one chunk" check can never fail) and
S16/S18 serving the whole ~120 MiB alienswarm.eti from a single source, so
fanout and retry were never exercised.
Test-correctness fixes (a broken behavior could previously pass green):
- S18: the "no download-failed" check was dead -- it reused a LineWaiter
already advanced past download-finished, so it scanned an empty tail.
Replaced with assert_no_event_since over the whole window. Switched to a
4*CHUNK_SIZE sparse archive so both peers get chunks; the test now proves the
download SURVIVES a mid-download source kill (every byte delivered, survivor
served part, no download-failed, clean diff). Retry-onto-survivor is the
mechanism but is not asserted: the kill/serve race against `docker rm -f`
cannot be forced, so asserting an exact split would be flaky.
- S7: the only check was a diff against byte-identical ggoo fixtures, so it was
source-agnostic. Added assertions that the download committed exactly once,
every chunk came from the validated two-peer set, both peers served, and no
chunk was fetched twice.
- S14: enlarged to 4*CHUNK_SIZE so the balance check can fail under a 3+1
imbalance; asserts an exact 2+2 split summing to the file size.
- S16: inflated the .eti to 2*CHUNK_SIZE so it fans out across both
catalog-version peers (the stock 120 MiB fixture is a single chunk).
- S37: validate the throughput rate fields (positive, self-consistent
mbit/mib == 8.388608, mib_per_s == bytes/duration), not just the byte count.
- S35: assert the source actually advertises the unknown game before checking
it is filtered, so "absent" means "filtered" and not "never sent".
- S15: cross-check each peer's raw advertised eti_version via list-peers; the
list-games eti_game_version is synthesized from the catalog and can only ever
equal the asserted value.
- S2: poll for library convergence and verify the bidirectional exchange
(bravo sees alpha's 3 games, not just alpha seeing bravo's 4).
- S12/S28: require the gating unit test to appear as "<name> ... ok" so an
#[ignore]d (un-run) test no longer satisfies the check.
- S24/S25: assert the requested install=false final state.
- S34: assert exactly 21 coherent chunks (20 files + version.ini), 21 distinct
paths, no duplicates, instead of a >= 21 floor.
Flake fixes:
- S19: force-kill the sole source right after download-begin on a 4*CHUNK_SIZE
file and accept download-failed or download-peers-gone. The old graceful
shutdown on a single-chunk file could let the transfer finish first, turning
the expected failure into a download-finished. A chunk may complete before
the kill lands, but the full transfer cannot, so the failure is deterministic.
- S26: use a large sparse source so the first operation is reliably still
active when the duplicate request is issued (TOCTOU on active_operations);
also assert the active operation == "Downloading".
- S11: drop the "listener address must change" assertion -- it tested the OS
ephemeral-port allocator and could fail spuriously; keep the same-identity /
no-duplicate invariant.
Coverage and determinism:
- S27: add handshake::tests::inbound_hello_from_self_is_ignored for the
protocol-level self guard. The CLI scenario only exercises the CLI
string-compare guard, which short-circuits before any network call, so the
peer-crate guard had no test.
- find_fixture_game now iterates sorted(FIXTURES) so the ambiguous cnctw
(fixture-bravo/multi/solid) resolves deterministically to fixture-bravo.
Reviewed and deliberately left as-is (documented in the run log): S20, S21,
S30, S32/S39/S44 absence checks, S42 IP-order precondition, S45.
PEER_CLI_SCENARIOS.md rows S2, S11, S14, S16, S18, S19, S27 are updated to
match the harness, and a dated run-log entry records the audit, the fixes, the
accepted items, and the live-run evidence.
Test Plan:
- `just peer-cli-tests` (rebuilds the image, runs S1-S47 in Docker): baseline
passed; post-fix passed; a final run on the exact committed code passed
47/47. Evidence: S14 {268435456, 268435456} balanced 2+2; S16 .eti split
across B and C {134217728, 134217728}; S18 all 536870912 bytes delivered with
no download-failed; S19 deterministic download-failed; S37 ~874 MiB/s.
- `just test` (incl. inbound_hello_from_self_is_ignored), `just clippy`
(-D warnings, all-targets), and `just fmt` all pass.
Refs: PEER_CLI_SCENARIOS.md scenario matrix and 2026-06-21 run-log entry.
48 KiB
Peer CLI P2P Scenarios
This matrix tracks the headless peer-to-peer contract exercised through
lanspread-peer-cli. It intentionally avoids the GUI and uses direct connect
for deterministic local runs; mDNS/macvlan remains an environment smoke path.
Scenario Matrix
| ID | Scenario | Setup | Expected result |
|---|---|---|---|
| S1 | Startup scan | Start one peer with fixture-alpha. |
Peer emits local-peer-ready and local-library-changed; catalog fixture games are downloaded=true, installed=false, availability=Ready. |
| S2 | Direct connect handshake | Start alpha and bravo, send alpha connect to bravo's ready address. |
Both peers record one remote peer, no self-peer entry appears, and each peer receives the other's library. |
| S3 | Remote aggregation | Empty client connects to alpha and bravo. | list-games shows remote-only games once; shared ggoo has peer_count=2, unique games have peer_count=1. |
| S4 | Single-source download, no install | Empty client connected to bravo downloads bfbc2 with install=false. |
Client emits got-game-files, download-begin, download-finished, then local bfbc2 is downloaded=true, installed=false; root files exist and local/ does not. |
| S5 | Auto-install download | Empty client connected to bravo downloads cnctw with default install. |
Download finishes, install begins and finishes, and local cnctw is downloaded=true, installed=true with local/fixture-payload.txt. |
| S6 | Manual install and uninstall | After S4, client sends install bfbc2, then uninstall bfbc2. |
Install marks bfbc2 installed and creates local/; uninstall removes local/ while preserving downloaded root files. |
| S7 | Duplicate-source majority download | Empty client connects to alpha and bravo, then downloads shared ggoo. |
Metadata from both peers validates by majority/plurality, download completes once, and installed state matches the install flag. |
| S8 | Ambiguous metadata rejection | Two peers advertise the same game/version with conflicting file sizes. | Download fails with a download-failed event; no committed version.ini is left for the target game. |
| S9 | Missing game | Client asks for a game none of its peers can serve. | CLI reports a deterministic command failure and emits no-peers-have-game; no local files are created. |
| S10 | Shutdown and goodbye cleanup | Alpha and bravo are connected, then bravo shuts down. | Alpha receives peer loss/removal and remote games from bravo disappear. |
| S11 | Same identity reconnect | Bravo restarts with the same state dir (the OS assigns an ephemeral listener port that usually, but not necessarily, differs), then alpha connects again. | Alpha has exactly one bravo peer entry reusing the same peer ID, not a duplicate identity, at whatever address bravo now advertises. |
| S12 | Transfer serving gates | A peer has a non-catalog, missing-sentinel, active-operation, or local/ path request. |
The serving peer declines metadata/data; covered by unit tests where timing is too small for a stable CLI race test. |
| S13 | Exact transferred-file equality | Repeat small and large downloads, then compare every transferred regular file against its source with SHA-256 manifests. | Source and receiver manifests match exactly for each transferred file; no extra or missing files appear in the downloaded game root. |
| S14 | Large multi-peer chunked download | A source advertises a synthetic catalog game whose .eti is a sparse file of 4 * CHUNK_SIZE (four 128 MiB chunks). A second peer downloads it, then a third peer downloads it from both peers. |
The third peer's downloaded files match the source by SHA-256; download-chunk-finished shows the .eti split across exactly both peers, all four chunks accounted for, and the per-peer byte totals balanced within one CHUNK_SIZE (a fair 2+2 split; a 3+1 imbalance would trip the check). |
| S15 | Catalog-version skew | Three peers advertise the same catalog game ID. Peers A and B have stale version.ini values; peer C has the catalog's expected version. An empty client connects to all three and downloads the game with install=false. |
list-games shows one row for the game with peer_count=1 and the catalog eti_game_version. The got-game-files descriptor set and transfer source are peer C only; no chunks come from A or B. The receiver's version.ini and SHA-256 manifest match C exactly. |
| S16 | Catalog-version fanout with stale peers present | Peer A has a stale version of a game. Peers B and C both advertise the catalog version with matching manifests; the .eti is inflated to 2 * CHUNK_SIZE so it can fan out. |
The aggregated row counts only catalog-version ready peers. The .eti chunks split across exactly B and C; peer A is not listed as downloadable and contributes no manifest vote or file chunks. |
| S17 | Catalog-version conflict rejection | Peer A has a stale version. Peers B and C both advertise the catalog version, but their file sizes conflict. | Validation considers only the catalog-version peers, so A cannot rescue the majority. The download fails with download-failed, and no committed target version.ini remains. |
| S18 | Mid-download source drop with redundancy | Client downloads a large shared multi-chunk game (sparse 4 * CHUNK_SIZE, so both peers are assigned .eti chunks) from two ready peers, then one source is killed right after the download has begun. |
The download survives the source kill: it finishes, no download-failed is emitted over the whole download window, every byte is delivered (chunk totals sum to the file size) with the survivor serving part of it, and the receiver's files match the source by diff or SHA-256. (Retry-onto-survivor is the mechanism that makes this possible, exercised when the kill interrupts an unfinished chunk, but it is not asserted because the kill timing cannot be forced; the per-source split is likewise not asserted.) |
| S19 | Mid-download sole-source drop | Client downloads a large multi-chunk game (sparse 4 * CHUNK_SIZE) from one source, then that source is force-killed immediately after download-begin. An individual chunk may complete before the kill lands, but the full multi-chunk download cannot, so the failure is deterministic on a fast LAN. |
The download emits a terminal failure (download-failed, or download-peers-gone when the sole source vanishes) and no download-finished; no committed target version.ini remains; any partial payload is not advertised as ready; active operation state clears so a retry is possible. |
| S20 | Receiver write failure | Client downloads a large game into a constrained /games filesystem. |
The download fails deterministically, no committed version.ini is advertised, and active operation state clears so the peer can retry later. |
| S21 | Add-game propagation | Two connected peers are running; one peer gains a new catalog game root through a completed download or an external drop. | The other peer receives a library update without reconnecting, and list-games shows the new remote game under the existing peer. |
| S22 | Remove-game propagation | Two connected peers are running; one peer loses a previously advertised game root. | The other peer receives a library update without dropping the peer, and list-games no longer shows that remote game. |
| S23 | Version bump propagation | Two connected peers are running; one peer's ready game root starts with a stale version.ini, then changes to the catalog version. |
The other peer receives a library update without reconnecting; the stale row is absent before the change, then the catalog-version game appears as downloadable. |
| S24 | Two clients pull from one source | Two empty clients connect to the same source and download the same large game concurrently. | Both downloads finish, both receivers match the source by diff or SHA-256, and the source remains responsive. |
| S25 | One client downloads two games concurrently | One client connected to a source issues two different download commands without waiting for the first to finish. |
Both operations may run in parallel; both eventually finish, each game reaches the requested install state, and each transferred root matches its source. |
| S26 | Same-game duplicate download rejection | A client starts downloading a game, then issues a second download command for the same game while the first operation is active. |
The second request is rejected deterministically as an operation-in-progress condition; the first download is not corrupted and still reaches its documented final state. |
| S27 | Self-connect rejection | A peer sends connect to its own advertised listener address. |
The CLI command fails cleanly (CLI-level guard), no self-peer entry is created, and the peer remains responsive. The protocol-level guard (a hello whose peer_id equals the local id is acknowledged but never recorded) is covered by the handshake::tests::inbound_hello_from_self_is_ignored unit test, which the CLI string-compare never reaches. |
| S28 | Address change without identity change | A known peer is rediscovered with the same peer ID and a different listener address while its library is still known. | The peer record updates in place to the new address, the existing library stays attached to that peer ID, and no duplicate peer entry appears. This is covered with a deterministic unit-level check until the CLI can rebind a live listener without restart. |
| S29 | Empty-library peer participates | A peer with no games connects into the mesh. | Other peers list it as a peer with zero games; it can receive a download, advertise the new game without restart, and become a source. |
| S30 | 5+ peer mesh aggregation | Five peers advertise partially overlapping catalog games with a mix of unique and shared catalog-version games; a sixth client connects to all five. | The client shows one row per game ID, correct catalog-version ready-source peer_count, catalog eti_game_version, no duplicates, and no self entries. |
| S31 | Bootstrapped peer becomes source in same session | An empty client downloads a game from a source, the original source shuts down, then a fresh third peer downloads the same game from the bootstrapped client. | The third peer's files match the original source by diff or SHA-256, proving downloaded files become servable without restart. |
| S32 | Reinstall after uninstall | A downloaded game is installed, uninstalled, then installed again without another download. | local/ is recreated from preserved root files, no transfer events occur during reinstall, and the game returns to installed=true. |
| S33 | Install after external root mutation | A downloaded game root is externally mutated before install is issued. |
The CLI fixture installer installs from the current root bytes. The resulting local/fixture-payload.txt must match the mutated archive bytes exactly. |
| S34 | Many-small-files game without .eti |
A catalog game root contains version.ini plus many small regular files and no archive. |
Download with install=false transfers every file, chunk events are coherent for small files, and source/receiver manifests match exactly. |
| S35 | Unknown game ID from remote peer | A remote peer advertises a game ID that is not in the receiver's catalog. | The receiver does not list the unknown game as downloadable, download attempts fail deterministically, and no local files are created. |
| S36 | Catalog singleton beats stale majority | Five peers advertise one game; one peer has the catalog version and four peers have stale versions. | list-games reports peer_count=1 and the catalog eti_game_version; all descriptors and chunks come from the singleton catalog-version peer, while stale peers remain hidden and contribute zero bytes. |
| S37 | Single-source download throughput | A source peer advertises a temporary catalog game with one sparse 2 GiB .eti; an empty client downloads it with install=false. |
The client emits download-finished with throughput measurements (bytes, duration_ms, mib_per_s, mbit_per_s), and the downloaded archive size matches the source. |
| S38 | First-play launch-setting stamping | fixture-persona/css ships a real RAR .eti whose tree buries a CRLF SmartSteamEmu.ini with a stub PersonaName line under engine/bin/win64/steam_settings/, plus a stub account_name.txt and language.txt under profiles/local/. A peer installs css (with --unrar), then sends play css with a username and language, then play css again. |
After install the marker games/css/launch_settings_applied is absent and the stub files are intact under local/. The first play returns already_applied=false with account_name_written, language_written, and persona_name_written all true; the deep SmartSteamEmu.ini PersonaName value becomes the username with its \r\n ending and sibling lines preserved, account_name.txt becomes the username, language.txt becomes the passed language, and the marker now exists. A second play returns already_applied=true, rewrites nothing, and leaves the files untouched even if their values were reset externally. |
| S39 | Streamed install without keeping archive payload | Empty client connects to fixture-bravo, then sends stream-install cnctw. The source has real RAR .eti payload entries under bin/ and data/; the receiver uses the container-bundled unrar stream provider. |
Client emits download-begin, streamed download-chunk-finished, download-finished, and install-finished (the install-start transition is observable via active-operations-changed; there is no separate install-begin event). Local cnctw is downloaded=false, installed=true, availability=LocalOnly; root version.ini and .eti are absent; local/bin/cnctw-payload.bin and local/data/cnctw-assets.dat match unrar p output by SHA-256; the source reports no active outbound transfer for cnctw after completion. |
| S40 | Streamed install receiver is not a peer source | After S39, a third peer connects only to the streamed-install receiver. | The third peer may see the receiver's local-only summary in peer snapshots, but list-games remote aggregation does not expose cnctw as downloadable, peer_count remains zero/absent, and attempting download cnctw fails with no local files created. |
| S41 | Solid archive streamed install | Empty client connects to a peer serving fixture-solid/cnctw, whose .eti is a real solid RAR archive. The receiver uses the container-bundled unrar stream provider. |
The fixture is verified as solid with unrar lt; streamed install finishes with downloaded=false, installed=true, availability=LocalOnly; root archive and version.ini are absent; streamed byte count equals the extracted solid entries; local payload SHA-256 hashes match unrar p output. |
| S42 | Streamed install whole-stream retry | Empty client connects to two peers serving the same catalog-version cnctw: one broken source whose --unrar path is missing, followed by one good source. |
The broken source sorts before the good source in retry order, contributes zero chunks, and the good source completes a fresh whole-stream attempt. The final state is local-only installed, no root archive/sentinel, no .local.installing, byte count matches the extracted entries, and payload hashes match the good source. |
| S43 | Already-installed streamed install rejection | A client first stream-installs cnctw, then attempts stream-install cnctw again. |
The second request emits download-failed, does not emit a new success event, leaves the existing local-only install intact, and clears active operations. |
| S44 | Corrupt archive streamed install rollback | A source advertises catalog-version cnctw, but its root .eti is replaced with invalid bytes before the client requests stream-install cnctw. |
The stream emits download-failed, does not emit download/install success, clears active operations, and leaves no local/, .local.installing, root archive, or root version.ini on the receiver. |
| S45 | Sender disconnect during streamed install | A source serves large catalog-version alienswarm; after the client receives the first streamed chunk, the source container is killed. |
The operation reaches a terminal failure/peers-gone event, emits no download/install success, clears active operations, and rolls back local/staging state. |
| S46 | Receiver cancel during streamed install | A client starts streaming large catalog-version alienswarm, receives the first chunk, then sends cancel-download alienswarm. |
The receiver cancels without emitting download/install success or a user-visible download failure, clears active operations, and rolls back local/staging state. |
| S47 | Multi-archive streamed install order | A source serves fixture-multi/cnctw with two root .eti archives named to require sorted processing. |
Streamed chunk paths arrive in root archive sort order, both payloads install under local/, the receiver is local-only installed, and no root archives or sentinel are committed. |
Version-Skew Contract
Use S15-S17 to pin down what happens when several peers have the same game ID but only some match the local catalog version:
- The receiver's catalog is authoritative. A remote root whose
version.inidoes not match the catalog's expected version for that game ID is not downloadable. list-gamesaggregates by game ID. The game appears once;peer_countcounts only ready peers with that ID and the catalog version.- The aggregated
eti_game_versionmust be the catalog version. - The descriptor set emitted to the download path, file-size validation, and transfer planning are catalog-version-only. Stale peers must not supply download descriptors, majority votes, or chunks.
- If exactly one peer has the catalog version, that peer is the only transfer source. If several peers match the catalog version, validation and chunk fanout happen among that catalog-version set only.
- Capture proof with the
list-gamesrow,got-game-filesdescriptors,download-chunk-finishedsource addresses, and source/receiver SHA-256 manifests.
Extended Failure And Mutation Contracts
Use S18-S36 to pin down operational behavior that is awkward to prove with the GUI:
- A failed download must not commit the root
version.inisentinel. Partial payload files may remain, but they must not be advertised as a ready local game and must not leave an active operation stuck. - Source failure during a redundant download should retry failed chunks against another validated source for the same catalog-version file.
- Live local library changes are observable by connected peers through library deltas; reconnect is not required for add, remove, or version-bump cases.
- Same-game operations are single-flight. A duplicate download request while a game is already active is rejected instead of starting another writer.
- Unknown remote game IDs are filtered by the receiver's current catalog and are not downloadable.
For a manual run, prefer a catalog game ID already served by the fixture lab,
such as cnc4, then create temporary just peer-cli-run game roots where some
peers match the catalog version and others deliberately use stale
version.ini contents. The existing alpha/bravo/charlie fixtures cover
duplicate-source and shared-game cases; S15-S17 add the focused skew cases.
First-Play Launch-Setting Contract
Use S38 to pin down how launcher settings are stamped into an installed game:
- Stamping happens on the first
play, not during install/update. The install transaction only clears thegames/<id>/launch_settings_appliedmarker so the next play reapplies settings to a freshly (re)createdlocal/. - The first play stamps the username into the first
account_name.txtand the firstSmartSteamEmu.iniPersonaNameline, and the language into the firstlanguage.txt, searching the wholelocal/tree. The matchedPersonaNameline keeps its existing line ending (\nor\r\n). - The marker records only that we tried: it is written unconditionally after the first play, so a game with none of these files is still marked done.
- S38 needs a real archive expanded with
--unrar; the Docker matrix image now carries the Linux sidecar for streamed-install coverage, while the peer crate'slaunch_settingsunit tests cover the rewrite, line-ending, and marker logic deterministically.
Streamed Install Archive Contract
Use S39-S41 to pin down low-disk streamed installs:
- The stream provider performs one archive metadata pass and one payload pass
per
.eti, then frames entry boundaries for the receiver. - Non-solid and solid archives both install into
local/without committing a root archive or rootversion.ini, so the receiver is installed but not a downloadable source. - Streamed install integrity is currently sender archive integrity: size and RAR CRC32 must match the sender's archive metadata. The SHA-256 checks in the scenarios prove the Docker/provider path matches the source fixture; they are not catalog-owned trust anchors.
- S41 verifies the fixture is actually solid inside the source container, so solid handling stays covered by the same Docker harness as the existing streamed-install scenarios.
- S42 verifies retry/resume semantics: failed streamed attempts roll back their staging directory and retry the whole stream from another validated peer. There is no byte-offset resume contract.
- S43-S47 cover the remaining streamed-install failure and archive-shape edges: already-installed rejection, corrupt archive rollback, sender disconnect, receiver cancel, and multi-archive root sorting.
Run Log
2026-06-21 - Test-Suite Integrity Audit And Hardening
- An adversarial review of
run_extended_scenarios.pyfound assertions that passed vacuously, raced, or diverged from the spec. A full baseline run (S1-S47, rebuilt image) passed beforehand, confirming these were test-quality gaps, not peer regressions. Baseline evidence of the gaps: S14 chunk totals were{134217728, 1048576}(a 2-chunk file whose "balanced within one chunk" check can never fail), and S16/S18 each served the whole ~120 MiBalienswarm.etifrom a single source, so neither fanout (S16) nor retry-onto-survivor (S18) was actually exercised. - Fixes applied to the runner (and the matching rows above):
- S18: replaced the dead
assert_no_event(it reused aLineWaiteralready advanced pastdownload-finished, so it scanned an empty tail and could never fire) withassert_no_event_sinceover the whole download window; switched to a multi-chunk sparse archive (4 * CHUNK_SIZE) so both peers own.etichunks and the test proves the download survives a mid-download source kill (retry-onto-survivor is the mechanism, exercised when the kill interrupts an unfinished chunk, but not asserted since the race can't be forced). - S7: added chunk-source, both-sources-served, single-
download-finished, and no-duplicate-chunk checks (the byte-identicalggoofixtures made the old diff-only assertion source-agnostic). - S14:
4 * CHUNK_SIZEfile so the balance check is meaningful (a 3+1 split would now exceed one chunk); asserts an exact 2+2 split and full byte total. - S16: inflated
.etito2 * CHUNK_SIZEso it fans out across both catalog-version peers (the stock 120 MiB fixture is a single chunk). - S19: force-kill right after
download-beginon a multi-chunk file, acceptdownload-failed/download-peers-gone, assert nodownload-finished(the old graceful shutdown could let a single-chunk transfer finish first). - S26: large sparse source so the first op is reliably still active, and
asserts the active
operation == "Downloading"(no scenario checked it). - S37: validates the throughput rate fields (positive, self-consistent
mbit_per_s/mib_per_s == 8.388608,mib_per_s == bytes/duration), not just the byte count. - S35: asserts the source actually advertises
mystery-gamebefore checking it is filtered (distinguishes "filtered" from "never sent"). - S15: cross-checks each peer's raw advertised
eti_versionvia list-peers (the list-gameseti_game_versionis synthesized from the local catalog and can only ever equal the catalog value). - S2: polls for library convergence and verifies the bidirectional exchange (bravo sees alpha's 3 games, not just alpha seeing bravo's 4).
- S11: dropped the "listener address must change" assertion (it tested the OS ephemeral-port allocator and could fail spuriously).
- S12/S28: require the gating unit test to appear as
<name> ... okso an#[ignore]d (un-run) test no longer satisfies the check. - S24/S25: assert the requested
install=falsefinal state. - S34: assert exactly 21 coherent chunks (20 files + version.ini), 21 distinct
paths, no duplicates, instead of a
>= 21floor. - S27: added the
handshake::tests::inbound_hello_from_self_is_ignoredunit test for the protocol-level self guard; the CLI scenario only exercises the CLI string-compare guard, which short-circuits before any network call. - Harness:
find_fixture_gamenow iteratessorted(...), so the ambiguouscnctw(bravo/multi/solid) resolves deterministically tofixture-bravo.
- S18: replaced the dead
- Accepted as-is (reviewed, deliberately not changed): S20 (disk-full via chunk
write_allis equivalent coverage), S21 (inotify across the bind mount is inherent to the harness), S30 (dup-row/self-peer checks are cheap defensive guards), S32/S39/S44 absence checks (cheap regression guards against committing a root sentinel), S42 IP-order precondition (deterministic by container start order), S45 (the spec already names both terminal events). - Live runs against the rebuilt
lanspread-peer-cli:devimage: baseline S1-S47 passed; post-fix S1-S47 passed. Post-fix evidence: S14{268435456, 268435456}(balanced 2+2); S16.etisplit across B and C{134217728, 134217728}; S18 all536870912bytes delivered despite the source drop (the survivor served the whole archive in that run); S19 deterministicdownload-failed; S37874.24 MiB/s. Gates:just test(incl. the new handshake test),just clippy(-D warnings), andjust fmtall passed.
2026-06-20 - Prune Dead Lifecycle Events
- Code under test removed the unconsumed
InstallGameBegin,UninstallGameBegin, andRemoveDownloadedGameBeginPeerEventvariants (and their peer-cli JSONLinstall-begin/uninstall-begin/remove-download-beginevents), plus the Tauri webview emits that no frontend listener consumed (peer-local-ready,game-download-begin,game-download-pre,game-download-finished,game-uninstall-finished,peer-connected/-disconnected/-discovered/-lost).peer-runtime-failedwas kept pending a UI decision. - Rationale: the GUI is state-as-source-of-truth (it renders the
games-listsnapshot), and no scenario asserted these begin events; the install, uninstall, and removal start transitions stay observable viaactive-operations-changed. - Contract update: the S39 row no longer lists
install-begin. Older run-log entries below predate the removal and are left intact as historical records. - Gates:
just test,just clippy,just frontend-test, andjust buildpassed. (just fmt'stombistep needs network and was skipped; no TOML changed.) The Docker S39-S47 matrix was not re-run for this cleanup; S39-S47 never asserted the removed begin events, so coverage is unchanged.
2026-06-07 - Catalog-Version Matrix Alignment (S1-S47)
- Code under test aligned checked-in fixture
version.inisentinels with the catalog, maderun_extended_scenarios.pystamp generated fixture games with catalog versions by default, updated S15-S17/S23/S30/S36/S37 to assert catalog-authoritative aggregation, and wired S38 into the executable matrix. - Gates before Docker:
python3 -m py_compile crates/lanspread-peer-cli/scripts/run_extended_scenarios.pypassed. - Targeted rebuilt-image runner:
python3 crates/lanspread-peer-cli/scripts/run_extended_scenarios.py S3 S8 S14 S15 S16 S17 S21 S22 S23 S24 S29 S30 S31 S34 S36 S37 S39 S40 S41 S42 S43 S44 S45 S46 S47 --build-imagepassed. - S38 standalone runner:
python3 crates/lanspread-peer-cli/scripts/run_extended_scenarios.py S38passed, proving the real-RARcssfixture installs with the container/usr/local/bin/unrarsidecar and stamps launch settings only once. - Full matrix runner:
python3 crates/lanspread-peer-cli/scripts/run_extended_scenarios.pypassed for S1-S47 against the rebuiltlanspread-peer-cli:devimage. - The final full-run highlights included S3 aggregation, S15-S17 catalog-version skew/fanout/conflict, S23 stale-to-catalog propagation, S30 mesh aggregation, S36 catalog singleton over stale majority, S37 throughput, S38 first-play stamping, and S39-S47 streamed-install coverage.
2026-06-07 - Streamed Install Edge Coverage (S43-S47)
- Code under test added
cancel-downloadtolanspread-peer-cli, added the tinyfixture-multi/cnctwtwo-archive fixture, and added S43-S47 inrun_extended_scenarios.py. - Gates before Docker:
just fmtandpython3 -m py_compile crates/lanspread-peer-cli/scripts/run_extended_scenarios.pypassed. - Runner:
python3 crates/lanspread-peer-cli/scripts/run_extended_scenarios.py S43 S44 S45 S46 S47 --build-imagepassed against the rebuiltlanspread-peer-cli:devimage. - S43 stream-installed
cnctw, retriedstream-install cnctw, observeddownload-failed, and verified the existing local-only install stayed intact. - S44 replaced the source
cnctw.etiwith invalid bytes. The receiver emitteddownload-failed, cleared active operations, and left nolocal/,.local.installing, root archive, or rootversion.ini. - S45 killed the sole
alienswarmsource after the first streamed chunk. The receiver ended withdownload-failed, emitted no success, cleared active operations, and rolled back local/staging state. - S46 cancelled
alienswarmon the receiver after the first streamed chunk. The receiver emitted no success and no user-visibledownload-failed, cleared active operations, and rolled back local/staging state. - S47 streamed
fixture-multi/cnctwand observed chunk paths in sorted root archive order:cnctw/.local.installing/order/first.txt, thencnctw/.local.installing/order/second.txt.
2026-06-07 - Streamed Install Whole-Stream Retry (S42)
- Code under test added S42 in
run_extended_scenarios.py. - Gates before Docker:
python3 -m py_compile crates/lanspread-peer-cli/scripts/run_extended_scenarios.pypassed. - Runner:
python3 crates/lanspread-peer-cli/scripts/run_extended_scenarios.py S42passed against the currentlanspread-peer-cli:devimage. - S42 started a broken source with
--unrar /missing-unrarand a good source with the same catalog-versioncnctwmetadata. The broken source sorted first (10.66.0.2:32897) and the good source second (10.66.0.3:34092). - The broken source contributed zero chunks; the good source completed the fresh
whole-stream attempt with
3145728streamed file bytes. - The final client state was
downloaded=false,installed=true,availability=LocalOnly, with no rootversion.ini, no rootcnctw.eti, and no.local.installingstaging directory. Payload SHA-256 hashes matched the good source'sunrar poutput.
2026-06-07 - Solid Streamed Install Coverage (S41)
- Code under test added
fixture-solid/cnctw, a real solid RAR.eti, plus S41 inrun_extended_scenarios.py. - Gates before Docker:
just fmt,git diff --check, andpython3 -m py_compile crates/lanspread-peer-cli/scripts/run_extended_scenarios.pypassed. - Runner:
python3 crates/lanspread-peer-cli/scripts/run_extended_scenarios.py S41 --build-imagepassed against the rebuiltlanspread-peer-cli:devimage. - S41 verified the source archive with
unrar lt -cfg-inside the source container; the archive reportedDetails: RAR 5, solid. - The streamed install finished with
downloaded=false,installed=true,availability=LocalOnly, no rootversion.ini, and no rootcnctw.eti. - The client received
118streamed file bytes, matching the extracted solid entries. Payload SHA-256 hashes matchedunrar poutput:88764c9a6c9b5b846b4323cf7725cb7fd70766ddd7fba4168332804a839fa193(bin/cnctw-solid-payload.bin) and44afc308269b2381b7c707a056dd8d9d393274108ac4d880237fa6772c861d7a(data/cnctw-solid-assets.dat).
2026-06-07 - Streamed Install Prototype (S39-S40)
- Code under test added
stream-installtolanspread-peer-cli, a peerStreamInstallGamecommand, streamed install frames over QUIC, and an injectedunrar lt/unrar pprovider for archive-derived bytes. - Gates before Docker:
just fmtandRUSTC_WRAPPER= CARGO_BUILD_RUSTC_WRAPPER= just testpassed for the workspace. - Runner:
python3 crates/lanspread-peer-cli/scripts/run_extended_scenarios.py S39 S40 --build-imagepassed against the rebuiltlanspread-peer-cli:devimage. - S39 streamed a catalog-version-adjusted
cnctwfixture from a real RAR.etiinto the receiver'slocal/only. The receiver haddownloaded=false,installed=true,availability=LocalOnly, no rootversion.ini, no root.eti, and payload SHA-256 hashes82f4da22dc042166def2a5ee2eca19fc9e52785f99838e86c32167cb342e2588(bin/cnctw-payload.bin) andabf833a06c74ea9f17d505c2684186491898ce906405e0f098f0deac19476b06(data/cnctw-assets.dat) matchingunrar p. - S40 connected an observer only to that streamed-install receiver. The
observer saw the receiver's
cnctwsummary as local-only, remote aggregation hid it as a downloadable source, anddownload cnctwfailed withno peers have game cnctw.
2026-05-28 - First-Play Launch-Setting Stamping (S38)
- Code under test moved the
account_name.txt/language.txtoverwrite out of the install transaction and into a single first-play step (shared with the newSmartSteamEmu.iniPersonaNamerewrite) gated by thegames/<id>/launch_settings_appliedmarker. just testpassed the whole workspace, including the newlanspread_peer::launch_settingsunit tests andinstall::transaction::install_resets_launch_settings_marker.- S38 host run: built
crates/lanspread-peer-cli/fixtures/fixture-persona/csswith a stored RAR.eti(verified byunrar t) burying a CRLFSmartSteamEmu.iniplus stubaccount_name.txt/language.txt. A host peer installedcsswith--unrar /usr/bin/unrar, thenplay cssstamped the username into the deepPersonaNameline (CRLF preserved, sibling lines intact) andaccount_name.txt, the language intolanguage.txt, and created the marker. A secondplay cssreturnedalready_applied=trueand rewrote nothing even after the value was reset externally.
2026-05-19 - Snapshot Status Fix Docker Matrix Pass
- Code under test included
5c4976d(fix(peer): settle local state before clearing operations) and6651f02(fix(ui): derive operation status from snapshots). - Gates before the matrix:
just fmt,just test,just frontend-test, andjust buildpassed. The peer harness image was rebuilt withjust peer-cli-image. - Runner:
python3 crates/lanspread-peer-cli/scripts/run_extended_scenarios.pypassed S1-S36 against the rebuiltlanspread-peer-cli:devimage. - Auto-install coverage remained good: S5 downloaded and installed
cnctw, saw the fixture payload underlocal/, and the downloaded root diffed cleanly againstfixture-bravo/cnctwexcluding local metadata. - Large/exact transfer coverage remained good: S13 small and large downloads
diffed cleanly; S14 split
alienswarmbetween two sources with chunk totals67,108,864and58,721,049bytes and the final root diffed cleanly. - Failure and mutation coverage remained good: S17 latest-version conflict, S19 sole-source drop, S20 write failure, S26 duplicate operation, and S35 unknown catalog filtering all failed safely without advertising bad local state; S21-S23 propagation, S24-S25 concurrency, S29-S31 bootstrapping, S32 reinstall, S33 mutation install, S34 many-small-files, and S36 latest singleton all passed.
2026-05-18 - Full Automated Docker Matrix Pass
- Runner:
python3 crates/lanspread-peer-cli/scripts/run_extended_scenarios.pypassed S1-S36 against the currentlanspread-peer-cli:devimage. - S1-S17 rerun highlights: startup, direct connect, aggregation, download,
install/uninstall, duplicate-source, ambiguous metadata, missing game,
shutdown cleanup, identity reconnect, serve gates, exact equality, large
multi-peer chunking, and latest-version selection/conflict all passed. Exact
transfer scenarios used
diff -r/SHA-256 manifest checks; S14 chunk totals were58,721,049and67,108,864bytes, balanced within one32 MiBchunk. - S18-S36 rerun highlights: source-drop, disk-full, live mutation, concurrency,
duplicate-operation rejection, self-connect rejection, empty-peer sourcing,
5-peer aggregation, bootstrapped sourcing, reinstall, external mutation,
many-small-files, unknown catalog filtering, and stale-majority/latest
singleton cases all passed. File-copy scenarios used diff/manifests or
cmpfor the mutated install payload.
2026-05-18 - Extended Scenario Docker Pass
- Runner:
python3 crates/lanspread-peer-cli/scripts/run_extended_scenarios.pypassed for S18-S36 after rebuildinglanspread-peer-cli:devwithjust peer-cli-image. - S18 redundant source drop: one
alienswarmsource was killed afterdownload-begin; the client emitteddownload-finished, nodownload-failed, anddiff -r/SHA-256 manifest comparison matched the surviving source. Recorded large-file chunk bytes from the surviving source:58,721,049. - S19 sole-source drop: killing the only source after
download-beginemitteddownload-failed; the receiver had no committedalienswarm/version.ini, no ready local row, and no active operation left. - S20 receiver write failure: a client with
/gamesconstrained to a32mtmpfs emitteddownload-failed;/games/alienswarm/version.iniwas absent inside the container and active operations were empty. - S21-S23 live mutation propagation: a connected peer observed
cod5added,cod5removed, andcnc4bumped from20250101to20260501without reconnecting or dropping the peer. - S24-S25 concurrency: two clients downloaded
alienswarmfrom one source at the same time and both diffed cleanly; one client downloadedbfbc2andcnctwconcurrently and both roots diffed cleanly. - S26 duplicate same-game download: the second
alienswarmdownload command returnedoperation already in progress for game alienswarm; the first download still finished and diffed cleanly. - S27 self-connect rejection: connecting a peer to its own listener returned
cannot connect peer to itself ...;list-peersstayed empty and the peer stayed responsive. - S28 address-change invariant:
just testpassed and includedpeer_db::tests::address_update_preserves_peer_identity_and_library. - S29 empty-library peer: an observer first saw the empty peer with zero games;
after that peer downloaded
alienswarm, the downloaded root diffed cleanly and the observer's peer snapshot for that same peer containedalienswarm. - S30 5-peer aggregation: a sixth client connected to five peers and aggregated
six game IDs with expected
peer_countand latest versions, with no duplicate game rows and no self-peer entry. - S31 bootstrapped source: after the original source was killed, a third peer
downloaded
alienswarmfrom the bootstrapped client and diffed cleanly against the original fixture. - S32 reinstall: reinstall after uninstall recreated
local/, reportedinstalled=true, and produced no transfer chunk events during reinstall. - S33 external root mutation: after mutating the downloaded
bfbc2.etiinside the client container,installwrotelocal/fixture-payload.txtthat matched the mutated archive exactly bycmp. - S34 many-small-files transfer: a
bf1942fixture with 20 small regular files and no.etidownloaded withinstall=false; 21 file chunks were observed includingversion.ini, and the receiver diffed cleanly against the source. - S35 unknown game ID: a source advertised
mystery-gamevia--fixture; the receiver filtered it out oflist-games,download mystery-gamereturnedgame mystery-game is not in the local catalog, and no local files were created. - S36 latest singleton: with one peer on
20260501and four peers on20250101, the client reportedpeer_count=5and latest20260501; only the singleton latest peer sent chunks and the final root diffed cleanly.
2026-05-18 - Full Matrix Manual Docker Pass
- Build/setup:
just peer-cli-imagepassed. Localjust peer-cli-buildneededRUSTC_WRAPPER=because the hostkachewrapper failed with a read-only filesystem error;RUSTC_WRAPPER= just peer-cli-buildpassed. - Temporary skew/conflict fixtures were created under the ignored
.lanspread-peer-cli/full-fixtures/tree usingrar a -idq -m0against/dev/urandompayloads and then renaming the archives to.eti.find .lanspread-peer-cli/full-fixtures -name '*.eti' -exec unrar t -idq {} \;passed. - S1 startup scan:
just peer-cli-alphaemittedcli-started,local-library-changed, andlocal-peer-ready;alienswarm,bf1942, andggooweredownloaded=true,installed=false,availability=Ready. - S2 clean direct connect: with only alpha and bravo running, alpha connected to
bravo at
10.66.0.3:42776;wait-peersreturnedpeer_count=1, andlist-peersshowed exactly one bravo peer with four games. - S3 clean remote aggregation: an empty
clean-s3-clientsaw exactly alpha and bravo.list-gamesshowedggoo peer_count=2;alienswarm,bf1942,bfbc2,cnc4, andcnctweach hadpeer_count=1. - S4 single-source no-install:
full-empty-clientdownloadedbfbc2from bravo withinstall=false. Events includedgot-game-files,download-begin,download-finished, and localinstalled=false. Host verification:diff -r crates/lanspread-peer-cli/fixtures/fixture-bravo/bfbc2 .lanspread-peer-cli/full-empty-client/games/bfbc2passed andlocal/was absent. - S5 auto-install:
full-empty-clientdownloadedcnctwwith default install. Events included download finish,install-begin, andinstall-finished;local/fixture-payload.txtexisted. Host verification diffed the downloaded files againstfixture-bravo/cnctwexcludinglocal/and.lanspread.json. - S6 manual install/uninstall: after S4,
install bfbc2createdlocal/and markedinstalled=true;uninstall bfbc2removedlocal/and preserved the downloaded root files. Host verification diffed the preserved files againstfixture-bravo/bfbc2excluding.lanspread.json. - S7 duplicate-source download:
full-empty-clientdownloaded sharedggoofrom alpha/bravo withinstall=false. Chunk events used alpha forversion.iniand bravo forggoo.eti; hostdiff -rmatched bothfixture-alpha/ggooandfixture-bravo/ggoo. - S8 ambiguous metadata rejection:
full-s8-aandfull-s8-bboth advertisedggooversion20260101but with different.etisizes (1,048,746and2,097,323bytes). The client sawpeer_count=2, thendownload ggooemitteddownload-failed; no targetggoo/version.iniwas committed. - S9 missing game:
download does-not-existemittedno-peers-have-gameand returned a command error;.lanspread-peer-cli/full-empty-client/gameshad nodoes-not-existdirectory. - S10 shutdown cleanup: alpha saw bravo before shutdown with one remote peer and
bravo-only remote games. After bravo
shutdown, alpha emittedpeer-lost;list-peersreturned[]andlist-gamesreturned an empty remote list. - S11 same identity reconnect: restarting bravo reused peer ID
019e347d901e70c19adf5b9fd313fce4at new address10.66.0.3:41764. Alphalist-peersshowed exactly one bravo entry at the new address. - S12 transfer serving gates: this remains covered by unit tests because the
CLI cannot stably race raw transfer requests against non-catalog, missing
sentinel, active-operation, and
local/path states.RUSTC_WRAPPER= just testpassed, includinglocal_download_available_gates_on_catalog_operation_and_sentinel,get_game_response_respects_serve_gates,file_transfer_dispatch_respects_serve_gates, andlocal_relative_paths_are_never_transferable. - S13 exact transferred-file equality: the S4 small transfer and S14 large
transfer both passed host
diff -ragainst the original source game directories, proving exact file equality beyond event flow. - S14 large multi-peer chunked download:
full-empty-clientfirst downloadedalienswarmfrom alpha and diffed cleanly againstfixture-alpha/alienswarm. A freshfull-s14-clientthen sawalienswarm peer_count=2and downloaded from both alpha andfull-empty-client. Large.etichunk totals were67,108,864bytes from alpha and58,721,049bytes from the staged peer, balanced within one32 MiBchunk. Final hostdiff -ragainstfixture-alpha/alienswarmpassed. - S15 three-way version skew: peers A/B/C advertised
cnc4versions20250101,20250201, and20250301. The client saw one row withpeer_count=3andeti_game_version=20250301; all chunks came only from C at10.66.0.4:60290. Hostdiff -ragainst C passed. - S16 latest-version fanout with stale peer present: A advertised stale
20250101; B/C both advertised latest20250301with a134,217,906byte.eti. The client sawpeer_count=3; chunks came only from B/C (67,108,873and67,109,042bytes respectively), with stale A contributing zero. Hostdiff -rmatched both B and C. - S17 latest-version conflict rejection: A advertised stale
20250101; B/C both advertised latest20250301but with conflicting.etisizes (1,048,748and2,097,325bytes). The client sawpeer_count=3and latest20250301, thendownload cnc4emitteddownload-failed; no targetcnc4/version.iniwas committed. - Gates after manual runs:
just fmt,RUSTC_WRAPPER= just test, andRUSTC_WRAPPER= just clippypassed.
2026-05-17 - Exact Transfer And Large Multi-Peer Chunking
- Fixture update:
fixture-alpha/alienswarm/alienswarm.etiwas rebuilt withrar a -idq -m0from three random 40 MiB payload files, then renamed to.eti. Final archive size:125,829,913bytes.unrar t -idqpassed. - Gates before manual runs:
just fmt,just test,just peer-cli-build,just clippy, andjust peer-cli-imagepassed. - S13 small exact transfer:
deep-small-clientdownloadedbfbc2fromfixture-bravowithinstall=false. SHA-256 manifests matched exactly:bfbc2/bfbc2.etif7accef0833f29481acdeaac58261bc4fc23ebb58b7197049024d354f60daabc;bfbc2/version.inif3d94f70edcebbbc7d8ce38fdf076412fb95114ce1ecf071b26c9c2f93586372. - S13 large exact transfer:
deep-stage-bdownloadedalienswarmfromfixture-alphawithinstall=false. SHA-256 manifests matched exactly:alienswarm/alienswarm.eti8a4fb1fd458e731affb175134b7b99efc8d8a5eda80e978ba81f721d01aecc43;alienswarm/notes.txt3832bcb7057a4453981e975d2d2d528bfd9a26671423352f4a8527362d5b9810;alienswarm/version.ini8dfdc51d4dbfb06015b41a85a5f5d47f44144139e4a12db2b17eb040773082a3. - S14 multi-peer setup:
deep-stage-cconnected to alpha (10.66.0.3:53514) anddeep-stage-b(10.66.0.2:58491).list-gamesshowedalienswarmwithpeer_count=2before the download. - S14 chunk-source evidence for
alienswarm/alienswarm.eti:deep-stage-creceived chunks fromdeep-stage-bat offsets0and67,108,864(67,108,864bytes total) and from alpha at offsets33,554,432and100,663,296(58,721,049bytes total). The source-byte difference was8,387,815bytes, below one32 MiBchunk. - S14 final exactness:
deep-stage-c'salienswarmSHA-256 manifest matchedfixture-alphaexactly foralienswarm.eti,notes.txt, andversion.ini.