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
This commit is contained in:
+30
-4
@@ -46,6 +46,8 @@ for deterministic local runs; mDNS/macvlan remains an environment smoke path.
|
||||
| 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
|
||||
|
||||
@@ -105,13 +107,37 @@ Use S38 to pin down how launcher settings are stamped into an installed game:
|
||||
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`, so it runs against the host
|
||||
`lanspread-peer-cli` binary rather than the Docker matrix image (which omits
|
||||
`unrar`). The peer crate's `launch_settings` unit tests cover the rewrite,
|
||||
line-ending, and marker logic deterministically.
|
||||
- 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
|
||||
|
||||
Reference in New Issue
Block a user