Files
lanspread/PEER_CLI_SCENARIOS.md
T
ddidderr 373def6d44 feat(peer): prototype streamed installs
Add a streamed-install prototype that can receive archive-derived install bytes
straight into local/ without first storing the peer-owned root archive payload.
This is intended for low-disk clients that want to install a game but opt out of
becoming a downloadable peer source for that game.

The protocol gains a current-version-only StreamInstall request and framed
StreamInstallFrame responses. The peer core owns the generic transport,
transaction, path validation, size checks, CRC32 verification, and lifecycle
state. The archive-specific work is hidden behind StreamInstallProvider so the
prototype can use unrar while the final implementation can swap in a better
provider without rewriting the peer command path.

The receiver writes into .local.installing and only promotes to local/ after the
full stream verifies. It deliberately does not write the root version.ini or
archive files, so the settled local state is installed=true, downloaded=false,
and availability=LocalOnly. That preserves the existing rule that local/ is not
served to peers and makes streamed receivers non-sources by construction.

The CLI is the only caller for now. It exposes stream-install and provides the
prototype unrar implementation with unrar lt for entry metadata and unrar p for
file bytes. This is simple and good enough to prove non-solid archive streaming,
but it is not the production provider shape for solid archives because per-file
unrar p would repeatedly decompress prefixes. The Tauri app explicitly passes
stream_install_provider: None, so the GUI behavior stays unchanged until a real
product path is designed.

Document the production-readiness work in NEXT_STEPS.md. The main follow-up is
to make the provider abstraction final-ish and replace the per-file CLI unrar
provider with a one-pass archive provider, then wire a deliberate GUI low-disk
mode, retry semantics, and broader failure scenarios.

Test Plan:
- just fmt
- RUSTC_WRAPPER= CARGO_BUILD_RUSTC_WRAPPER= just test
- python3 crates/lanspread-peer-cli/scripts/run_extended_scenarios.py \
  S39 S40 --build-image
- RUSTC_WRAPPER= CARGO_BUILD_RUSTC_WRAPPER= just clippy
- git diff --check
- git diff --cached --check

Follow-up: NEXT_STEPS.md
2026-06-07 20:32:05 +02:00

32 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 but a new port, then alpha connects to the new address. Alpha has one bravo peer entry with the updated address, not duplicate identities.
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 fixture-alpha/alienswarm contains a renamed RAR .eti larger than 100 MB. A second peer downloads it, then a third peer downloads alienswarm from both peers. The third peer's downloaded files match the source by SHA-256; download-chunk-finished events show the large .eti chunks coming from both peers with byte counts balanced within one chunk.
S15 Three-way version skew Three peers advertise the same catalog game ID. Peer A has version.ini=20250101, peer B has version.ini=20250201, and peer C has version.ini=20250301; each version has distinguishable file contents. 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=3 and eti_game_version=20250301. The got-game-files descriptor set and transfer source are peer C's newest version only; no chunks come from A or B. The receiver's version.ini and SHA-256 manifest match C exactly.
S16 Latest-version fanout with stale peers present Peer A has an older version of a game. Peers B and C both advertise the same newest version with matching file manifests; use a large file when proving chunk split. The aggregated row still counts all ready peers, but eligible transfer peers are only B and C. Large-file chunks may split between B and C; peer A contributes no manifest majority vote and no file chunks.
S17 Latest-version conflict rejection Peer A has an older version. Peers B and C both advertise the newest version, but their latest-version file sizes conflict. Validation considers only the latest-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 game from two ready peers, then one source is killed after the download has begun. Failed chunks are retried against the surviving source; the download finishes, no download-failed is emitted, and the receiver's files match the source by diff or SHA-256.
S19 Mid-download sole-source drop Client downloads a large game from one source, then that source is killed after the download has begun. The download emits download-failed; 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 gets a newer version.ini. The other peer receives a library update without reconnecting, and the aggregated row reflects the newer eti_game_version.
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 command fails cleanly, no self-peer entry is created, and the peer remains responsive.
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 games, shared games, and differing versions; a sixth client connects to all five. The client shows one row per game ID, correct ready-source peer_count, latest 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 Latest singleton beats stale majority Five peers advertise one game; one peer has 20260501, four peers have 20250101. list-games reports eti_game_version=20260501; all descriptors and chunks come from the singleton latest peer; stale peers 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 got-game-files, download-begin, streamed download-chunk-finished, download-finished, install-begin, and install-finished. 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.
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.

Version-Skew Contract

Use S15-S17 to pin down what "newer" means when several peers have the same game ID:

  • Version comparison uses the eight-digit version.ini string, so use sortable YYYYMMDD values in manual fixtures.
  • list-games aggregates by game ID. The game appears once; peer_count counts all ready peers with that ID, including peers that only have older versions.
  • The aggregated eti_game_version must be the newest ready version.
  • The descriptor set emitted to the download path, file-size validation, and transfer planning are latest-only. Older-version peers may be queried by a generic detail request, but their descriptors must not supply download descriptors, majority votes, or chunks once a newer version exists.
  • If exactly one peer has the latest version, that peer is the only transfer source. If several peers tie on the latest version, validation and chunk fanout happen among that latest-version set only.
  • Capture proof with the list-games row, got-game-files descriptors, download-chunk-finished source 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.ini sentinel. 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 latest-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 with different version.ini contents. The existing alpha/bravo/charlie fixtures cover duplicate-source and shared-game cases, but not the three-version skew until a dedicated fixture or temporary games root is prepared.

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 the games/<id>/launch_settings_applied marker so the next play reapplies settings to a freshly (re)created local/.
  • The first play stamps the username into the first account_name.txt and the first SmartSteamEmu.ini PersonaName line, and the language into the first language.txt, searching the whole local/ tree. The matched PersonaName line keeps its existing line ending (\n or \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's launch_settings unit tests cover the rewrite, line-ending, and marker logic deterministically.

Run Log

2026-06-07 - Streamed Install Prototype (S39-S40)

  • Code under test added stream-install to lanspread-peer-cli, a peer StreamInstallGame command, streamed install frames over QUIC, and an injected unrar lt/unrar p provider for archive-derived bytes.
  • Gates before Docker: just fmt and RUSTC_WRAPPER= CARGO_BUILD_RUSTC_WRAPPER= just test passed for the workspace.
  • Runner: python3 crates/lanspread-peer-cli/scripts/run_extended_scenarios.py S39 S40 --build-image passed against the rebuilt lanspread-peer-cli:dev image.
  • S39 streamed a catalog-version-adjusted cnctw fixture from a real RAR .eti into the receiver's local/ only. The receiver had downloaded=false, installed=true, availability=LocalOnly, no root version.ini, no root .eti, and payload SHA-256 hashes 82f4da22dc042166def2a5ee2eca19fc9e52785f99838e86c32167cb342e2588 (bin/cnctw-payload.bin) and abf833a06c74ea9f17d505c2684186491898ce906405e0f098f0deac19476b06 (data/cnctw-assets.dat) matching unrar p.
  • S40 connected an observer only to that streamed-install receiver. The observer saw the receiver's cnctw summary as local-only, remote aggregation hid it as a downloadable source, and download cnctw failed with no peers have game cnctw.

2026-05-28 - First-Play Launch-Setting Stamping (S38)

  • Code under test moved the account_name.txt/language.txt overwrite out of the install transaction and into a single first-play step (shared with the new SmartSteamEmu.ini PersonaName rewrite) gated by the games/<id>/launch_settings_applied marker.
  • just test passed the whole workspace, including the new lanspread_peer::launch_settings unit tests and install::transaction::install_resets_launch_settings_marker.
  • S38 host run: built crates/lanspread-peer-cli/fixtures/fixture-persona/css with a stored RAR .eti (verified by unrar t) burying a CRLF SmartSteamEmu.ini plus stub account_name.txt/language.txt. A host peer installed css with --unrar /usr/bin/unrar, then play css stamped the username into the deep PersonaName line (CRLF preserved, sibling lines intact) and account_name.txt, the language into language.txt, and created the marker. A second play css returned already_applied=true and 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) and 6651f02 (fix(ui): derive operation status from snapshots).
  • Gates before the matrix: just fmt, just test, just frontend-test, and just build passed. The peer harness image was rebuilt with just peer-cli-image.
  • Runner: python3 crates/lanspread-peer-cli/scripts/run_extended_scenarios.py passed S1-S36 against the rebuilt lanspread-peer-cli:dev image.
  • Auto-install coverage remained good: S5 downloaded and installed cnctw, saw the fixture payload under local/, and the downloaded root diffed cleanly against fixture-bravo/cnctw excluding local metadata.
  • Large/exact transfer coverage remained good: S13 small and large downloads diffed cleanly; S14 split alienswarm between two sources with chunk totals 67,108,864 and 58,721,049 bytes 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.py passed S1-S36 against the current lanspread-peer-cli:dev image.
  • 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 were 58,721,049 and 67,108,864 bytes, balanced within one 32 MiB chunk.
  • 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 cmp for the mutated install payload.

2026-05-18 - Extended Scenario Docker Pass

  • Runner: python3 crates/lanspread-peer-cli/scripts/run_extended_scenarios.py passed for S18-S36 after rebuilding lanspread-peer-cli:dev with just peer-cli-image.
  • S18 redundant source drop: one alienswarm source was killed after download-begin; the client emitted download-finished, no download-failed, and diff -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-begin emitted download-failed; the receiver had no committed alienswarm/version.ini, no ready local row, and no active operation left.
  • S20 receiver write failure: a client with /games constrained to a 32m tmpfs emitted download-failed; /games/alienswarm/version.ini was absent inside the container and active operations were empty.
  • S21-S23 live mutation propagation: a connected peer observed cod5 added, cod5 removed, and cnc4 bumped from 20250101 to 20260501 without reconnecting or dropping the peer.
  • S24-S25 concurrency: two clients downloaded alienswarm from one source at the same time and both diffed cleanly; one client downloaded bfbc2 and cnctw concurrently and both roots diffed cleanly.
  • S26 duplicate same-game download: the second alienswarm download command returned operation 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-peers stayed empty and the peer stayed responsive.
  • S28 address-change invariant: just test passed and included peer_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 contained alienswarm.
  • S30 5-peer aggregation: a sixth client connected to five peers and aggregated six game IDs with expected peer_count and 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 alienswarm from the bootstrapped client and diffed cleanly against the original fixture.
  • S32 reinstall: reinstall after uninstall recreated local/, reported installed=true, and produced no transfer chunk events during reinstall.
  • S33 external root mutation: after mutating the downloaded bfbc2.eti inside the client container, install wrote local/fixture-payload.txt that matched the mutated archive exactly by cmp.
  • S34 many-small-files transfer: a bf1942 fixture with 20 small regular files and no .eti downloaded with install=false; 21 file chunks were observed including version.ini, and the receiver diffed cleanly against the source.
  • S35 unknown game ID: a source advertised mystery-game via --fixture; the receiver filtered it out of list-games, download mystery-game returned game mystery-game is not in the local catalog, and no local files were created.
  • S36 latest singleton: with one peer on 20260501 and four peers on 20250101, the client reported peer_count=5 and latest 20260501; 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-image passed. Local just peer-cli-build needed RUSTC_WRAPPER= because the host kache wrapper failed with a read-only filesystem error; RUSTC_WRAPPER= just peer-cli-build passed.
  • Temporary skew/conflict fixtures were created under the ignored .lanspread-peer-cli/full-fixtures/ tree using rar a -idq -m0 against /dev/urandom payloads 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-alpha emitted cli-started, local-library-changed, and local-peer-ready; alienswarm, bf1942, and ggoo were downloaded=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-peers returned peer_count=1, and list-peers showed exactly one bravo peer with four games.
  • S3 clean remote aggregation: an empty clean-s3-client saw exactly alpha and bravo. list-games showed ggoo peer_count=2; alienswarm, bf1942, bfbc2, cnc4, and cnctw each had peer_count=1.
  • S4 single-source no-install: full-empty-client downloaded bfbc2 from bravo with install=false. Events included got-game-files, download-begin, download-finished, and local installed=false. Host verification: diff -r crates/lanspread-peer-cli/fixtures/fixture-bravo/bfbc2 .lanspread-peer-cli/full-empty-client/games/bfbc2 passed and local/ was absent.
  • S5 auto-install: full-empty-client downloaded cnctw with default install. Events included download finish, install-begin, and install-finished; local/fixture-payload.txt existed. Host verification diffed the downloaded files against fixture-bravo/cnctw excluding local/ and .lanspread.json.
  • S6 manual install/uninstall: after S4, install bfbc2 created local/ and marked installed=true; uninstall bfbc2 removed local/ and preserved the downloaded root files. Host verification diffed the preserved files against fixture-bravo/bfbc2 excluding .lanspread.json.
  • S7 duplicate-source download: full-empty-client downloaded shared ggoo from alpha/bravo with install=false. Chunk events used alpha for version.ini and bravo for ggoo.eti; host diff -r matched both fixture-alpha/ggoo and fixture-bravo/ggoo.
  • S8 ambiguous metadata rejection: full-s8-a and full-s8-b both advertised ggoo version 20260101 but with different .eti sizes (1,048,746 and 2,097,323 bytes). The client saw peer_count=2, then download ggoo emitted download-failed; no target ggoo/version.ini was committed.
  • S9 missing game: download does-not-exist emitted no-peers-have-game and returned a command error; .lanspread-peer-cli/full-empty-client/games had no does-not-exist directory.
  • S10 shutdown cleanup: alpha saw bravo before shutdown with one remote peer and bravo-only remote games. After bravo shutdown, alpha emitted peer-lost; list-peers returned [] and list-games returned an empty remote list.
  • S11 same identity reconnect: restarting bravo reused peer ID 019e347d901e70c19adf5b9fd313fce4 at new address 10.66.0.3:41764. Alpha list-peers showed 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 test passed, including local_download_available_gates_on_catalog_operation_and_sentinel, get_game_response_respects_serve_gates, file_transfer_dispatch_respects_serve_gates, and local_relative_paths_are_never_transferable.
  • S13 exact transferred-file equality: the S4 small transfer and S14 large transfer both passed host diff -r against the original source game directories, proving exact file equality beyond event flow.
  • S14 large multi-peer chunked download: full-empty-client first downloaded alienswarm from alpha and diffed cleanly against fixture-alpha/alienswarm. A fresh full-s14-client then saw alienswarm peer_count=2 and downloaded from both alpha and full-empty-client. Large .eti chunk totals were 67,108,864 bytes from alpha and 58,721,049 bytes from the staged peer, balanced within one 32 MiB chunk. Final host diff -r against fixture-alpha/alienswarm passed.
  • S15 three-way version skew: peers A/B/C advertised cnc4 versions 20250101, 20250201, and 20250301. The client saw one row with peer_count=3 and eti_game_version=20250301; all chunks came only from C at 10.66.0.4:60290. Host diff -r against C passed.
  • S16 latest-version fanout with stale peer present: A advertised stale 20250101; B/C both advertised latest 20250301 with a 134,217,906 byte .eti. The client saw peer_count=3; chunks came only from B/C (67,108,873 and 67,109,042 bytes respectively), with stale A contributing zero. Host diff -r matched both B and C.
  • S17 latest-version conflict rejection: A advertised stale 20250101; B/C both advertised latest 20250301 but with conflicting .eti sizes (1,048,748 and 2,097,325 bytes). The client saw peer_count=3 and latest 20250301, then download cnc4 emitted download-failed; no target cnc4/version.ini was committed.
  • Gates after manual runs: just fmt, RUSTC_WRAPPER= just test, and RUSTC_WRAPPER= just clippy passed.

2026-05-17 - Exact Transfer And Large Multi-Peer Chunking

  • Fixture update: fixture-alpha/alienswarm/alienswarm.eti was rebuilt with rar a -idq -m0 from three random 40 MiB payload files, then renamed to .eti. Final archive size: 125,829,913 bytes. unrar t -idq passed.
  • Gates before manual runs: just fmt, just test, just peer-cli-build, just clippy, and just peer-cli-image passed.
  • S13 small exact transfer: deep-small-client downloaded bfbc2 from fixture-bravo with install=false. SHA-256 manifests matched exactly: bfbc2/bfbc2.eti f7accef0833f29481acdeaac58261bc4fc23ebb58b7197049024d354f60daabc; bfbc2/version.ini f3d94f70edcebbbc7d8ce38fdf076412fb95114ce1ecf071b26c9c2f93586372.
  • S13 large exact transfer: deep-stage-b downloaded alienswarm from fixture-alpha with install=false. SHA-256 manifests matched exactly: alienswarm/alienswarm.eti 8a4fb1fd458e731affb175134b7b99efc8d8a5eda80e978ba81f721d01aecc43; alienswarm/notes.txt 3832bcb7057a4453981e975d2d2d528bfd9a26671423352f4a8527362d5b9810; alienswarm/version.ini 8dfdc51d4dbfb06015b41a85a5f5d47f44144139e4a12db2b17eb040773082a3.
  • S14 multi-peer setup: deep-stage-c connected to alpha (10.66.0.3:53514) and deep-stage-b (10.66.0.2:58491). list-games showed alienswarm with peer_count=2 before the download.
  • S14 chunk-source evidence for alienswarm/alienswarm.eti: deep-stage-c received chunks from deep-stage-b at offsets 0 and 67,108,864 (67,108,864 bytes total) and from alpha at offsets 33,554,432 and 100,663,296 (58,721,049 bytes total). The source-byte difference was 8,387,815 bytes, below one 32 MiB chunk.
  • S14 final exactness: deep-stage-c's alienswarm SHA-256 manifest matched fixture-alpha exactly for alienswarm.eti, notes.txt, and version.ini.