feat(peer): validate manifests before download mutation

Why:
- Remote and UI-echoed file descriptions could reach transaction and storage
  code one entry at a time, so a hostile late path could mutate earlier files.
- Per-file consensus also accepted malformed peer lists and let duplicate rows
  inflate a source's vote.

What:
- Add a complete protocol-7 manifest adapter with catalog-root confinement,
  portable path and alias rules, reserved-path protection, shape and size caps,
  symlink/reparse inspection, and zero-mutation tests.
- Keep download selection in the peer core, validate every peer manifest before
  consensus, and pass only the validated manifest into storage/orchestration.
- Canonicalize locally advertised paths, cap exact chunk receives, and preserve
  the local-only install fast path.
- Record the chosen safety limits and follow-up ownership/catalog decisions.

Test Plan:
- just clippy
- just test
- just frontend-test
- just build
- just fmt (Rust/TOML/Prettier completed; rumdl reports 39 pre-existing issues)
- git diff --cached --check
This commit is contained in:
2026-08-09 17:51:11 +02:00
parent 9268de2371
commit a6ed60a538
12 changed files with 1376 additions and 137 deletions
@@ -0,0 +1,71 @@
# Peer authentication refactor decisions
This file records implementation choices that are not fully determined by
`PEER_AUTH_PLAN.md`, especially choices where every available option has a
meaningful downside.
## 2026-08-09 — Bound protocol-7 download descriptions generously
**TL;DR:** Until catalog manifests replace remote descriptions, accept at most
100,000 entries, 1 TiB per ordinary file, 64 KiB for the in-memory `version.ini`
sentinel, 255 bytes per path component, 900 bytes per relative path, and 16 TiB
in aggregate for one game. Resolved destinations are capped at 1,000 platform
path units.
The plan requires count, per-file, and aggregate limits but intentionally does
not prescribe values. These limits are far above the expected game and CLI
fixture sizes while bounding allocation, planning work, and arithmetic.
Alternatives:
- Lower product-sized limits would reject abusive inputs earlier, but risk
inventing constraints that real large games or mod packs exceed.
- Limits near `usize`/`u64` maxima avoid practical compatibility concerns, but
do not meaningfully bound memory or work.
- Configurable limits add settings and test combinations without a current user
need; protocol safety limits should be consistent between installations.
## 2026-08-09 — Track download ownership; never infer it from unknown files
**TL;DR:** Persist an atomic per-game set of successfully downloaded paths under
application state. On replacement, remove only paths in the previous owned set
that are absent from the new authoritative manifest. If old or corrupt state has
no trustworthy set, preserve unknown root files.
The existing game directory has no provenance information. Deleting every
non-reserved path would remove stale downloads, but could also delete a user's
own files and contradict the plan's preservation rule. The first update after
upgrading may therefore leave legacy stale files; subsequent successful
downloads have exact ownership and can clean safely.
Alternatives:
- Bootstrap ownership from the current directory. This is seamless, but can
permanently misclassify user files as Lanspread-owned and later delete them.
- Delete every non-reserved file missing from the new manifest. This is simple
and cleans legacy state completely, but is unsafe for user files.
- Delete only familiar archive suffixes such as `.eti`. This avoids most user
files, but bakes package naming guesses into the security boundary and cannot
clean general catalog payloads.
- Never clean stale files. This is safest for user data, but fails the explicit
successful-replacement requirement and can keep serving obsolete payload.
## 2026-08-09 — Generate production manifests outside the source checkout
**TL;DR:** Provide a small deterministic generator binary that runs next to the
canonical production packages and writes the JSON manifest artifacts that are
then stored and packaged with Lanspread.
This checkout has catalog metadata for 186 games but canonical package bytes
only for a small CLI fixture set. Hashes cannot be derived honestly without the
bytes. Keeping the generator here makes the format reproducible without
requiring production game packages in Git.
Alternatives:
- Store production packages in this repository. That would make generation
self-contained, but is impractical for size and distribution reasons.
- Generate hashes at application runtime. This would make local peer bytes the
authority, defeating catalog-owned verification and adding startup work.
- Accept hashes announced by peers. This is easy to deploy, but provides no
protection against a peer that supplies both the bytes and their claimed hash.