diff --git a/NEXT_STEPS.md b/NEXT_STEPS.md deleted file mode 100644 index 8d734ed..0000000 --- a/NEXT_STEPS.md +++ /dev/null @@ -1,66 +0,0 @@ -# Streamed Install Next Steps - -I’d treat the prototype as proof of the hard part: “can we stream -archive-derived install bytes into `local/` without making the receiver a -source?” Yes. Next I’d harden the pieces that decide whether this is -product-ready. - -1. **Done — Move from CLI-only to real app integration** - - The GUI now has an explicit “Low disk install” action in the game detail - modal for remote-only games. The Tauri backend queues that path through - `stream_install_game`, injects the shared external `unrar` stream provider, - and hands fetched file details to `StreamInstallGame` instead of the normal - download command. - -2. **Done — Replace per-file `unrar p` with a final archive provider** - - The shared external `unrar` stream provider now runs `unrar lt` once for the - archive metadata and one sequential `unrar p` pass per archive for payload - bytes. It frames directories, file starts, file chunks, and file ends from - the technical listing, so CLI and GUI callers use one purpose-built provider - instead of a per-file extraction loop. - -3. **Done — Handle solid archives deliberately** - - The provider exposes the RAR `solid` flag in `ArchiveBegin` and always uses - one sequential payload pass per archive, which is the safe path for solid - archives. S41 now verifies a real solid RAR fixture through the Docker - peer-cli flow, including local-only final state, absent root archive/sentinel, - byte count, and extracted payload SHA-256 hashes. - -4. **Done — Decide the integrity model** - - Streamed installs intentionally verify against sender archive metadata for - now: each file must match the RAR-advertised size and CRC32. That catches - transport corruption, truncation, and provider bugs, but does not claim - malicious-peer protection. Trusted content remains a separate catalog schema - step: add catalog-owned archive or extracted-file SHA-256 hashes, then verify - those at the receiver before commit. - -5. **Done — Upgrade retry/resume semantics** - - Streamed install attempts now use the same majority-validated peer set as - normal downloads, and each failed attempt rolls back its staging transaction - before trying the next peer. S42 pins the policy: retry the whole stream from - another validated peer, keep no partial files across attempts, and do not add - byte-offset resume until there is a strong reason. - -6. **Done — Expand scenario coverage** - - S43-S47 cover the remaining streamed-install edges: already-installed - rejection, corrupt archive rollback, sender disconnect mid-stream, receiver - cancel mid-stream, and multi-archive `.eti` roots streamed in sorted order. - The peer-cli harness now exposes `cancel-download` so cancellation scenarios - exercise the same runtime path as the GUI. - -7. **Done — Clean product semantics** - - The UI now keeps streamed installs in the installed visual state while making - the sharing limitation explicit: cards show `Not shareable`, and the detail - modal status shows `Installed, not shareable`. Downloaded-and-installed games - keep the normal `Installed` label. - -The remaining production-readiness step is additive: move from sender-owned RAR -metadata to catalog-owned archive or extracted-file hashes, then verify those -at the receiver before committing the streamed install. diff --git a/IMPL_DECISIONS.md b/organize/decision-tracking/IMPL_DECISIONS.md similarity index 100% rename from IMPL_DECISIONS.md rename to organize/decision-tracking/IMPL_DECISIONS.md diff --git a/PEER_AUTH_PLAN.md b/organize/planning/PEER_AUTH_PLAN.md similarity index 92% rename from PEER_AUTH_PLAN.md rename to organize/planning/PEER_AUTH_PLAN.md index 09b53aa..e1bdbef 100644 --- a/PEER_AUTH_PLAN.md +++ b/organize/planning/PEER_AUTH_PLAN.md @@ -34,7 +34,7 @@ not three partially compatible protocol generations. | Content authority | Ship BLAKE3 file and chunk hashes from the same bundled catalog authority as `game.db`. | Every eligible nearby peer is usable automatically; wrong bytes are rejected and retried elsewhere. | | Peer identity | Use one installation-local TLS key and derive `PeerId` from that TLS public key. | Identity works silently and survives ordinary restarts when possible; users do not manage it. | | Transport | Pin every outbound QUIC connection to the expected `PeerId`. | An address spoof or MITM cannot impersonate the peer selected as a source. | -| Control messages | Use ordinary bounded protocol messages inside TLS. Treat unauthenticated inbound change notifications only as hints that trigger a pinned pull. | No signed-envelope layer, nonce ledger, or message-signing overhead. | +| Control messages | Use ordinary bounded protocol messages inside TLS. Treat unauthenticated inbound change notifications only as hints that trigger a pinned pull, and carry current revisions on the liveness ping that already runs so a lost hint self-heals. | No signed-envelope layer, nonce ledger, or message-signing overhead. | | Call to Play | Exchange only each peer's own session state by direct pinned pulls; do not relay third-party histories. | Calls are live LAN-party state and disappear naturally as their authors leave. | | Privacy | Provide one global Local network sharing switch. | Participation is easy to understand; no per-peer policy matrix. | | Protocol rollout | Make one cutover to the new current protocol. | Mixed versions are explained clearly, without maintaining legacy paths. | @@ -50,6 +50,9 @@ bundled content manifest -> validated local download plan local Call to Play change -> cheap invalidation hint to known peers -> each peer pulls the author's current state over pinned TLS + +pinned liveness ping -> responder's own current revisions + -> pull that one responder only on mismatch ``` ## 2. Threat model and guarantees @@ -316,6 +319,31 @@ This extra pull is one small LAN round trip and removes general signed envelopes, canonical opaque payloads, nonce caches, replay semantics, inbound client-certificate plumbing, and connect-back authority state. +Hints are a latency optimization, never a correctness requirement. The liveness +ping that already runs is the reconciliation channel: `Pong` carries the +responder's own `(runtime_session_id, library_revision, call_to_play_revision)`. +The initiator compares them against what it has cached for that endpoint +generation and, on any mismatch or a new session ID, schedules exactly the +coalesced pinned pull a hint would have scheduled. A hint that was dropped, +never sent, or discarded by rate limiting therefore converges within one ping +interval, with no new timer, no mDNS payload growth, and no periodic full-state +polling. + +Freshness for that check must be measured from the local peer's own last pinned +exchange with the remote peer, never from inbound activity. The current +`ping_idle_peers` skips any peer whose `last_seen` was refreshed by traffic +arriving from it (`update_last_seen_by_addr`), so a peer that keeps talking to +us would suppress the very check that detects our staleness about it. Track the +last completed outbound pinned exchange separately from `last_seen`, which +continues to serve stale-peer pruning. Inbound traffic is not evidence of +freshness, for the same reason it is not evidence of identity. + +Revisions on `Pong` are a staleness signal, not content authority; the pull +remains the authoritative step. A responder that inflates its revision only +causes pulls of its own state, bounded by the same coalescing and rate limits. A +responder that understates it leaves the initiator stale about that responder +alone, which it could already achieve by changing nothing. + An unproven address collision never evicts an authenticated peer. If a pinned dial later proves that a different ID now owns the same address, atomically replace address ownership and retire the old record only if it still names @@ -437,9 +465,9 @@ The cutover includes: - version-bound ALPN and per-installation server certificates instead of the repository-wide `cert.pem`/`key.pem`; - mDNS candidate-only semantics and useful incompatible-version telemetry; -- responder-owned pull snapshots plus bounded change hints instead of inbound - state-bearing `Hello`, pushed `LibraryDelta`, and pushed/relayed - `CallToPlayEvents`; +- responder-owned pull snapshots, revision-bearing `Pong`, and bounded change + hints instead of inbound state-bearing `Hello`, pushed `LibraryDelta`, and + pushed/relayed `CallToPlayEvents`; - cryptographic `content_id` in game availability and catalog-driven chunk requests; - canonical forward-slash catalog paths; @@ -462,7 +490,7 @@ real reusable boundary; a new identity crate is not required by the design. | `lanspread-proto` | `PeerId`, `PeerEndpoint`, `content_id`, pull snapshots, change hints, author-owned Call-to-Play wire types, and the one protocol version. No crypto or storage logic. | | `lanspread-peer::identity` | Simple key/certificate load-or-generate, SPKI-derived ID, and test identity injection. | | `lanspread-peer::network` | Per-endpoint rustls client config, full responder verification, ALPN, and no address-only connect. | -| discovery/handshake/liveness | Candidate-only mDNS, pinned pulls, hint coalescing, endpoint generations, and version-mismatch reporting. | +| discovery/handshake/liveness | Candidate-only mDNS, pinned pulls, hint coalescing, revision reconciliation on ping, endpoint generations, and version-mismatch reporting. | | `peer_db` | Authenticated endpoint/state records and exact `content_id` source lookup. | | download/storage/stream install | Validated catalog plan, hash-as-received, source quarantine/retry, sentinel commit, and protected staging. | | `call_to_play` | Local author slice, per-peer replacement snapshots, simple authority checks, timers, and bounds. | @@ -529,6 +557,9 @@ This phase is not complete until: CLI dial rejects the wrong key at the expected address; - a forged mDNS record or inbound hint cannot create/rebind/remove peer state, inject a library/Call-to-Play update, or bypass a pinned pull; +- a change hint that is dropped, never sent, or rate-limited away still + converges within one ping interval, and inbound traffic from a peer does not + defer that peer's revision check; - an honest multi-source download swarms automatically and commits only the catalog bytes; - one bad source is quarantined and another source completes the chunk; diff --git a/PEER_CLI_SCENARIOS.md b/organize/testing/PEER_CLI_SCENARIOS.md similarity index 100% rename from PEER_CLI_SCENARIOS.md rename to organize/testing/PEER_CLI_SCENARIOS.md diff --git a/BACKLOG.md b/organize/unsorted/BACKLOG.md similarity index 100% rename from BACKLOG.md rename to organize/unsorted/BACKLOG.md diff --git a/CALL_TO_PLAY_FIXES_PLAN.md b/organize/unsorted/CALL_TO_PLAY_FIXES_PLAN.md similarity index 100% rename from CALL_TO_PLAY_FIXES_PLAN.md rename to organize/unsorted/CALL_TO_PLAY_FIXES_PLAN.md diff --git a/CALL_TO_PLAY_REVIEW_FABLE_5_XHIGH.md b/organize/unsorted/CALL_TO_PLAY_REVIEW_FABLE_5_XHIGH.md similarity index 100% rename from CALL_TO_PLAY_REVIEW_FABLE_5_XHIGH.md rename to organize/unsorted/CALL_TO_PLAY_REVIEW_FABLE_5_XHIGH.md diff --git a/CALL_TO_PLAY_REVIEW_GEMINI_3.6_FLASH.md b/organize/unsorted/CALL_TO_PLAY_REVIEW_GEMINI_3.6_FLASH.md similarity index 100% rename from CALL_TO_PLAY_REVIEW_GEMINI_3.6_FLASH.md rename to organize/unsorted/CALL_TO_PLAY_REVIEW_GEMINI_3.6_FLASH.md diff --git a/CALL_TO_PLAY_REVIEW_KIMI_K3.md b/organize/unsorted/CALL_TO_PLAY_REVIEW_KIMI_K3.md similarity index 100% rename from CALL_TO_PLAY_REVIEW_KIMI_K3.md rename to organize/unsorted/CALL_TO_PLAY_REVIEW_KIMI_K3.md diff --git a/CLEAN_CODE.md b/organize/unsorted/CLEAN_CODE.md similarity index 100% rename from CLEAN_CODE.md rename to organize/unsorted/CLEAN_CODE.md diff --git a/CLEAN_CODE_PLAN_1.md b/organize/unsorted/CLEAN_CODE_PLAN_1.md similarity index 100% rename from CLEAN_CODE_PLAN_1.md rename to organize/unsorted/CLEAN_CODE_PLAN_1.md diff --git a/FABLE_5_FINDINGS.md b/organize/unsorted/FABLE_5_FINDINGS.md similarity index 100% rename from FABLE_5_FINDINGS.md rename to organize/unsorted/FABLE_5_FINDINGS.md diff --git a/FINDINGS.md b/organize/unsorted/FINDINGS.md similarity index 100% rename from FINDINGS.md rename to organize/unsorted/FINDINGS.md diff --git a/FINDINGS_AUSWERTUNG_SOL.md b/organize/unsorted/FINDINGS_AUSWERTUNG_SOL.md similarity index 100% rename from FINDINGS_AUSWERTUNG_SOL.md rename to organize/unsorted/FINDINGS_AUSWERTUNG_SOL.md