fix(peer): preserve untracked download files
Reject exact manifest destinations that are not covered by the last committed ownership set before creating a baseline or parking version.ini. Align Windows device-name validation with the confined filesystem backend and keep cleanup capability-relative. Replace recursive downloaded-game removal with an empty ownership generation. The operation now removes only proven-owned files and the sentinel, preserves unknown files and directories, and remains recoverable and idempotent across crashes. Test Plan: - just clippy - just test - just fmt (Rust, TOML, and Prettier completed; rumdl still reports 39 pre-existing issues)
This commit is contained in:
@@ -114,27 +114,31 @@ Alternatives:
|
||||
promotion point, but can require another full game's worth of disk and a large
|
||||
multi-file transaction mechanism.
|
||||
|
||||
## 2026-08-09 — Manifest target paths become download-owned
|
||||
## 2026-08-09 — Reject untracked exact manifest targets
|
||||
|
||||
**TL;DR:** Once a validated download transaction starts, every regular file path
|
||||
in that manifest is treated as downloader-owned for abort and recovery. Unknown
|
||||
paths outside the manifest remain untouched.
|
||||
**TL;DR:** Before parking `version.ini` or preparing storage, reject a download
|
||||
when a regular-file destination already exists but is absent from the last
|
||||
committed ownership set. Previously owned paths may be replaced; every unknown
|
||||
path remains untouched.
|
||||
|
||||
Without prior ownership state, an existing file at an exact manifest target is
|
||||
ambiguous. Treating the authoritative target namespace as download-owned keeps
|
||||
legacy upgrades working and makes partial-transfer cleanup deterministic. A
|
||||
user-created extra file is preserved, but a user file placed at an exact package
|
||||
path may be replaced or removed by the download.
|
||||
ambiguous. Automatically claiming it would make a failed first or post-upgrade
|
||||
download truncate and then delete potentially user-owned bytes. Failing closed
|
||||
preserves the file, but a legacy tree without an ownership journal may require
|
||||
the user to move or remove conflicting package files before its first update.
|
||||
Phase 2 can later recognize intact catalog bytes by their trusted local hashes
|
||||
without weakening this boundary.
|
||||
|
||||
Alternatives:
|
||||
|
||||
- Reject every untracked existing target. This preserves ambiguous files, but
|
||||
prevents the first post-upgrade update of existing legacy downloads.
|
||||
- Treat every validated manifest target as download-owned. This keeps legacy
|
||||
upgrades seamless, but can destroy an unrelated user file on failure or
|
||||
cancellation.
|
||||
- Snapshot and restore untracked targets. This preserves their bytes, but adds
|
||||
unbounded backup space and another crash-consistent transaction.
|
||||
- Leave pre-existing untracked targets after abort. This avoids deletion, but
|
||||
the transfer may already have truncated or partially overwritten them, so it
|
||||
does not actually preserve their contents.
|
||||
- Stage every payload in a second game tree and promote it atomically. This
|
||||
avoids touching collisions during transfer, but can require another full
|
||||
game's worth of disk and a cross-platform multi-file promotion protocol.
|
||||
|
||||
## 2026-08-09 — Ownership-record rename is its publication point
|
||||
|
||||
@@ -201,6 +205,66 @@ Alternatives:
|
||||
but expands the crash protocol and still needs a policy for pre-existing
|
||||
manifest directories.
|
||||
|
||||
## 2026-08-09 — Remove downloads as an empty ownership generation
|
||||
|
||||
**TL;DR:** “Remove downloaded files” parks `version.ini`, durably journals an
|
||||
empty pending generation, removes only the last committed file set, discards the
|
||||
sentinel, and keeps a valid empty ownership record. It never recursively deletes
|
||||
the game root.
|
||||
|
||||
The existing pending-generation recovery already gives this operation a clean
|
||||
crash protocol. Before the empty generation is durable, recovery restores the
|
||||
sentinel and no payload has been touched. Afterwards, recovery idempotently
|
||||
finishes deleting only proven-owned files and finalizes the empty record.
|
||||
Unknown files and directories remain. A legacy, corrupt, or wrongly bound record
|
||||
fails closed because paths and sizes alone cannot distinguish package bytes from
|
||||
user bytes; Phase 2 can later adopt an intact legacy tree by checking it against
|
||||
trusted catalog hashes.
|
||||
|
||||
Alternatives:
|
||||
|
||||
- Recursively delete the game root, then clear the ledger. This frees every
|
||||
byte, but destroys unknown files and leaves stale deletion authority if the
|
||||
process crashes between those operations.
|
||||
- Delete the sentinel and leave every ambiguous payload file. This preserves
|
||||
user bytes, but reports a misleading successful removal while reclaiming
|
||||
almost no space.
|
||||
- Infer ownership from the current remote manifest or filename extensions. This
|
||||
is convenient for legacy trees, but lets untrusted or incomplete metadata
|
||||
authorize deletion.
|
||||
|
||||
## 2026-08-09 — Ownership follows committed paths, not inode generations
|
||||
|
||||
**TL;DR:** A successfully committed download path remains downloader-owned until
|
||||
a later download or explicit removal releases it. The ledger does not persist
|
||||
platform-specific inode or file-ID generations.
|
||||
|
||||
This is enough for the stated remote-peer threat model: a peer cannot replace a
|
||||
victim filesystem object except through the one admitted download operation, and
|
||||
untracked exact targets are rejected before that operation starts. A local actor
|
||||
who replaces an already owned path makes the replacement subject to later
|
||||
owned-path cleanup. That local-filesystem race is outside the plan's threat
|
||||
model, but the consequence is recorded because the provenance boundary is path
|
||||
based rather than object based.
|
||||
|
||||
The same rule applies if someone deletes and recreates the entire game root at
|
||||
the same configured path outside Lanspread: the last committed relative paths
|
||||
remain owned. The application cannot distinguish that replacement without a
|
||||
separate root-generation marker or platform file identity.
|
||||
|
||||
Alternatives:
|
||||
|
||||
- Persist inode/file-ID generations and delete only the exact recorded object.
|
||||
This detects replacement, but creates a platform-specific schema and does not
|
||||
survive ordinary copy/restore workflows consistently.
|
||||
- Hash every owned file before cleanup. Phase 2 catalog hashes can identify
|
||||
intact package content, but always rereading multi-gigabyte payloads solely
|
||||
for deletion adds significant latency and still needs a policy for modified
|
||||
downloader-owned files.
|
||||
- Never delete a previously owned path automatically. This preserves every local
|
||||
replacement but makes stale cleanup and “Remove downloaded files” unable to
|
||||
reclaim ordinary package data.
|
||||
|
||||
## 2026-08-09 — A baseline record distinguishes new and legacy scratch
|
||||
|
||||
**TL;DR:** Before parking an existing `version.ini`, the new downloader durably
|
||||
@@ -299,15 +363,14 @@ Alternatives:
|
||||
**TL;DR:** Reject links and reparse points that can redirect path resolution,
|
||||
but do not reject an otherwise regular manifest target merely because it has
|
||||
multiple hard links. The threat model excludes an attacker controlling the
|
||||
victim filesystem, and exact manifest target paths already become download-owned
|
||||
when a transaction starts.
|
||||
victim filesystem, and only previously recorded download-owned targets may be
|
||||
replaced by a transaction.
|
||||
|
||||
A hard link cannot be selected or created by a remote description outside the
|
||||
validated game-relative namespace. A local user can make the same inode visible
|
||||
under another name, but that is local filesystem manipulation rather than a
|
||||
peer-controlled path escape. This choice inherits the documented consequence
|
||||
that replacing an exact ambiguous manifest target may affect another local name
|
||||
for those bytes.
|
||||
validated game-relative namespace. A local user can make an already owned inode
|
||||
visible under another name, but that is local filesystem manipulation rather
|
||||
than a peer-controlled path escape. New untracked manifest targets are rejected
|
||||
before mutation instead of being claimed automatically.
|
||||
|
||||
Alternatives:
|
||||
|
||||
|
||||
Reference in New Issue
Block a user