fix(peer): confine download mutations to game root handles
Remote manifests were validated before mutation, but preparation, chunk writes, sentinel transactions, and ownership recovery later reopened ambient paths. A link or reparse-point swap between those steps could redirect a mutation outside the validated game root. Introduce a retained ConfinedGameRoot capability backed by cap-primitives. Carry typed validated destinations into chunk plans, walk every component without following links, and perform payload, sentinel, stale-file, abort, and recovery mutations relative to the retained handle. File writes and verification use the same opened handle, while final durability syncs payload files and unique parent directories before committing version.ini. Make ownership-record publication phase-aware as well. A directory-sync failure after record rename now stops before payload mutation without performing an unsafe old-sentinel rollback. Record the capability-root, bounded-handle, hard-link, and unproven Windows durability tradeoffs in the decision log. Test Plan: - `just clippy` -- passed - `just test` -- passed; 185 peer tests and the full workspace are green - `just fmt` -- Rust, TOML, and Prettier completed; command remains nonzero on 39 pre-existing rumdl issues outside this change - `git diff --cached --check` -- passed
This commit is contained in:
@@ -159,8 +159,9 @@ Downloaded and installed are independent predicates:
|
||||
|
||||
- `downloaded` is true only when `<game_root>/version.ini` exists as a regular
|
||||
file. The sentinel is written last through `.version.ini.tmp` and atomic
|
||||
rename. An interrupted replacement leaves no restored old sentinel because
|
||||
archive bytes may already have changed.
|
||||
rename. The old sentinel is parked before a pending ownership set is
|
||||
published; recovery restores it only when a valid baseline proves payload
|
||||
mutation never started.
|
||||
- `installed` is true when `<game_root>/local/` is a directory. The contents of
|
||||
`local/` are user-owned and are skipped by manifests, fingerprints, and file
|
||||
serving.
|
||||
@@ -209,6 +210,14 @@ Most scans become O(number of game dirs), with full recursion only when needed.
|
||||
|
||||
- Keep `GetGame`/manifest requests, but keyed by `manifest_hash` so repeated
|
||||
calls can be skipped when unchanged.
|
||||
- The complete remote description is converted into a
|
||||
`ValidatedDownloadManifest` before any destination mutation. It contains
|
||||
canonical game-root-relative paths and rejects aliases, reserved state, shape
|
||||
conflicts, and bounded-size violations as one unit.
|
||||
- Download mutation holds a capability handle for the direct catalog game root.
|
||||
Directory components and final files are reopened relative to that handle
|
||||
without following links or Windows reparse points; chunk writes and checks use
|
||||
the same opened file handle.
|
||||
- Downloads remain chunked QUIC streams with the existing integrity checks.
|
||||
- A game is transferable only when its ID is in the catalog, no operation is
|
||||
active for that ID, and the root-level `version.ini` sentinel exists.
|
||||
|
||||
Reference in New Issue
Block a user