markdown formatting

This commit is contained in:
2026-08-09 16:16:45 +02:00
parent f4a6259cf3
commit 18dd3b7e07
21 changed files with 1630 additions and 893 deletions
+148 -150
View File
@@ -5,10 +5,9 @@
Revised implementation plan; not yet implemented.
This plan deliberately treats Lanspread as what it is: a desktop utility for
friends and other attendees at a LAN party to discover each other, share a
known game catalog at LAN speed, and coordinate a match. It is not an account
system, a global untrusted file-sharing network, or a device-administration
product.
friends and other attendees at a LAN party to discover each other, share a known
game catalog at LAN speed, and coordinate a match. It is not an account system,
a global untrusted file-sharing network, or a device-administration product.
The normal user journey must remain:
@@ -18,9 +17,9 @@ The normal user journey must remain:
4. Let Lanspread swarm from matching peers and verify the result itself.
5. Use Call to Play while those people are present.
Security mechanisms in this plan are automatic. There are no key backup
dialogs, trust ceremonies, fingerprint prompts, or per-device download
permissions in the normal UI.
Security mechanisms in this plan are automatic. There are no key backup dialogs,
trust ceremonies, fingerprint prompts, or per-device download permissions in the
normal UI.
The project still has one current wire version and no compatibility shims. The
wire changes below are developed together and activated with one protocol bump,
@@ -28,16 +27,16 @@ not three partially compatible protocol generations.
## 1. Product and architecture decisions
| Area | Decision | User-visible result |
|---|---|---|
| Filesystem safety | Validate the complete destination manifest before any mutation and confine it to one catalog game root. | A hostile peer cannot overwrite another game, `local/`, saves, or transaction state. |
| 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, 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. |
| Area | Decision | User-visible result |
| ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- |
| Filesystem safety | Validate the complete destination manifest before any mutation and confine it to one catalog game root. | A hostile peer cannot overwrite another game, `local/`, saves, or transaction state. |
| 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, 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. |
The resulting data flow is intentionally small:
@@ -58,31 +57,31 @@ pinned liveness ping -> responder's own current revisions
## 2. Threat model and guarantees
Assume a hostile device can join the same LAN, advertise arbitrary mDNS data,
send arbitrary protocol messages, occupy reused IP addresses, and run a
modified Lanspread build. The attacker does not control the victim's OS, the
installed Lanspread application, or its bundled catalog files.
send arbitrary protocol messages, occupy reused IP addresses, and run a modified
Lanspread build. The attacker does not control the victim's OS, the installed
Lanspread application, or its bundled catalog files.
After this plan:
- a remote description cannot make Lanspread create, truncate, or delete a
path outside the requested catalog game's download-owned area;
- a selected responder must prove possession of the TLS private key whose
public key derives the expected `PeerId`;
- a remote description cannot make Lanspread create, truncate, or delete a path
outside the requested catalog game's download-owned area;
- a selected responder must prove possession of the TLS private key whose public
key derives the expected `PeerId`;
- mDNS, IP addresses, display names, and inbound notification bodies never
become identity authority by themselves;
- a source cannot make a download commit bytes that differ from the hashes in
the victim's bundled catalog, even if that source is the only peer present;
- corrupt sources are removed from the current transfer automatically rather
than presented to the user as a trust decision; and
- one peer cannot publish Call-to-Play actions as another peer or mutate
another peer's author-owned state.
- one peer cannot publish Call-to-Play actions as another peer or mutate another
peer's author-owned state.
The following are explicit non-goals:
- A `PeerId` does not prove a human name. Display names remain friendly labels.
- The installation key is not a user account and has no promised continuity
across OS reinstall, application-data deletion, or copying the application
to another computer.
across OS reinstall, application-data deletion, or copying the application to
another computer.
- Content hashes prove that bytes match the bundled catalog. They do not prove
that the catalog publisher's game is benign, licensed, or malware-free.
- A hash advertised by the same peer that sends the bytes is not trusted. The
@@ -113,11 +112,11 @@ Validation is for the complete list and fails without any mutation. It must:
- require a known catalog `game_id` and resolve every destination relative to
exactly `<games_folder>/<game_id>`;
- use one canonical forward-slash relative-path form and reject empty,
absolute, drive-qualified, UNC, NUL, `.`, `..`, mixed-separator, and
non-normalized paths;
- reject duplicate paths, file/directory conflicts, and platform aliases such
as Windows case, trailing-dot/space, device-name, and alternate-data-stream
- use one canonical forward-slash relative-path form and reject empty, absolute,
drive-qualified, UNC, NUL, `.`, `..`, mixed-separator, and non-normalized
paths;
- reject duplicate paths, file/directory conflicts, and platform aliases such as
Windows case, trailing-dot/space, device-name, and alternate-data-stream
collisions;
- reject `local/`, `.local.*`, download/install intent state, legacy state,
scratch sentinels, and every other path owned by installation or recovery,
@@ -133,17 +132,17 @@ The Tauri command supplies only the selected `game_id`. The peer core chooses
the complete authoritative plan. A UI-echoed file list is never authority.
After a successful complete transfer, remove download-owned files absent from
the authoritative manifest before committing `version.ini`. Preserve
`local/`, install staging/backup state, and user-owned files in all success,
failure, cancellation, and recovery paths.
the authoritative manifest before committing `version.ini`. Preserve `local/`,
install staging/backup state, and user-owned files in all success, failure,
cancellation, and recovery paths.
For the current protocol, this validator safely contains the existing remote
descriptions. A narrow protocol-7 adapter requires and removes exactly one
matching leading `game_id/` component (and discards only the current exact
redundant game-root directory entry) before constructing root-relative paths;
it rejects a missing/different/doubled prefix. After the protocol cutover, the
same validated type is constructed directly from the bundled content manifest
and remote descriptions cease to define local paths at all.
redundant game-root directory entry) before constructing root-relative paths; it
rejects a missing/different/doubled prefix. After the protocol cutover, the same
validated type is constructed directly from the bundled content manifest and
remote descriptions cease to define local paths at all.
Required proof includes hostile descriptors placed after valid descriptors,
cross-game paths, both requested and other-game `local/` sentinels, reserved
@@ -153,10 +152,10 @@ mutation.
### 3.2 Make the bundled catalog the content authority
`game.db` is already the application's authority for game identity and
version. Add reproducibly generated per-game companion manifest artifacts,
located at `manifests/<game_id>.json` (loaded on-demand when downloading or serving a game),
and package them with both the desktop application and peer-CLI fixtures.
`game.db` is already the application's authority for game identity and version.
Add reproducibly generated per-game companion manifest artifacts, located at
`manifests/<game_id>.json` (loaded on-demand when downloading or serving a
game), and package them with both the desktop application and peer-CLI fixtures.
For each supported `(game_id, game_version)`, the manifest artifact contains:
@@ -176,11 +175,11 @@ CatalogContentManifest {
}
```
Entries are sorted by canonical path. `content_id` is BLAKE3 over a
versioned, length-delimited encoding of all preceding manifest fields and
hashes, excluding the `content_id` field itself; it is not the current
noncryptographic `u64 manifest_hash`. Golden tests freeze that encoding. The
ordinary chunk size matches Lanspread's 128 MiB transfer chunk.
Entries are sorted by canonical path. `content_id` is BLAKE3 over a versioned,
length-delimited encoding of all preceding manifest fields and hashes, excluding
the `content_id` field itself; it is not the current noncryptographic
`u64 manifest_hash`. Golden tests freeze that encoding. The ordinary chunk size
matches Lanspread's 128 MiB transfer chunk.
The catalog publishing workflow must generate these per-game manifests from the
canonical game packages, verify them by rereading the packages, and fail the
@@ -193,22 +192,22 @@ games.
Peers advertise only that they can serve a catalog `content_id`. A peer counts
as a source for the local catalog game only when its advertised ID exactly
matches the receiver's expected ID. The receiver builds paths, sizes, chunks,
and expected hashes entirely from its local catalog manifest. This replaces remote
manifest selection and majority-by-file-size consensus.
and expected hashes entirely from its local catalog manifest. This replaces
remote manifest selection and majority-by-file-size consensus.
For ordinary downloads:
1. Select every currently reachable peer advertising the expected
`content_id`; there is no approval prompt.
1. Select every currently reachable peer advertising the expected `content_id`;
there is no approval prompt.
2. Carry `PeerEndpoint { peer_id, addr }` and `content_id` through planning,
swarming, progress, and retry.
3. The sender serves only an exact catalog file/range for the requested
`(game_id, content_id)` and applies the same canonical/reserved-path policy
before opening a local file. A caller-supplied path can never expose
`local/` or another local file.
4. Hash each chunk with BLAKE3 while receiving it and compare it before marking that chunk
complete. Exact length, offset coverage, and the catalog file shape are also
mandatory.
before opening a local file. A caller-supplied path can never expose `local/`
or another local file.
4. Hash each chunk with BLAKE3 while receiving it and compare it before marking
that chunk complete. Exact length, offset coverage, and the catalog file
shape are also mandatory.
5. On mismatch, invalidate that write, quarantine that `(PeerId, content_id)`
for the current runtime/transfer, and retry the chunk from another matching
peer. Do not create durable “trust” state.
@@ -216,42 +215,43 @@ For ordinary downloads:
successfully. Failure leaves the game non-downloadable/non-installable and
preserves `local/`.
No background disk-scanning or pre-hashing of existing files is required. Chunks are verified strictly as they stream in during an active transfer.
No background disk-scanning or pre-hashing of existing files is required. Chunks
are verified strictly as they stream in during an active transfer.
Streamed install needs a catalog-owned extracted-file manifest because the
sender controls both today's RAR CRC32 metadata and extracted bytes. The
receiver accepts exactly the expected path set, sizes, and BLAKE3 values in
isolated staging, then applies the documented local account/language rewrite
and promotes the transaction. CRC32 may remain as an early corruption check,
but it is not the security boundary. A game without a verified extracted
manifest does not offer Stream Install; there is no unverified fallback or
warning-through button.
isolated staging, then applies the documented local account/language rewrite and
promotes the transaction. CRC32 may remain as an early corruption check, but it
is not the security boundary. A game without a verified extracted manifest does
not offer Stream Install; there is no unverified fallback or warning-through
button.
Hashing is performed in the existing streaming I/O path. The acceptance gate
measures end-to-end throughput on the standard LAN workload and avoids a
second full read when complete chunk coverage already proves the file bytes.
measures end-to-end throughput on the standard LAN workload and avoids a second
full read when complete chunk coverage already proves the file bytes.
### 3.3 Use a simple installation-local TLS identity
The identity exists to bind a live peer and its changing address to TLS. It is
not exposed as a user credential.
- Generate one self-issued TLS certificate/key pair in Tauri's
`app_data_dir()` and store it in one versioned application file with
restrictive permissions where the platform supports them.
- Generate one self-issued TLS certificate/key pair in Tauri's `app_data_dir()`
and store it in one versioned application file with restrictive permissions
where the platform supports them.
- Prefer Ed25519 if the selected s2n-quic rustls provider supports the complete
responder-verification path. Otherwise use one supported P-256 TLS key. Do not
add a second signing identity or a custom certificate-extension binding.
- Define `PeerId` as lowercase unpadded base32 of
`BLAKE3(canonical DER SubjectPublicKeyInfo)` from the actual TLS key. The
same key is therefore both the identity and the TLS proof-of-possession key.
- Validate on load that the private key, certificate SPKI, and derived ID
agree. Never log private material.
- A valid file is reused. A missing or corrupt file is regenerated
automatically (quarantining corrupt bytes best-effort) and produces at most
a diagnostic log entry. If persistence is unavailable, use a fresh in-memory
identity for that run and show a non-blocking diagnostic; LAN functionality
should not become a repair wizard.
`BLAKE3(canonical DER SubjectPublicKeyInfo)` from the actual TLS key. The same
key is therefore both the identity and the TLS proof-of-possession key.
- Validate on load that the private key, certificate SPKI, and derived ID agree.
Never log private material.
- A valid file is reused. A missing or corrupt file is regenerated automatically
(quarantining corrupt bytes best-effort) and produces at most a diagnostic log
entry. If persistence is unavailable, use a fresh in-memory identity for that
run and show a non-blocking diagnostic; LAN functionality should not become a
repair wizard.
- The peer CLI may accept an explicit deterministic identity file/seed for
repeatable tests. It does not probe keyrings or share a default container
identity accidentally.
@@ -278,11 +278,11 @@ Call-to-Play refresh, metadata/content requests, chunk plans, retries, streamed
install, healing, liveness, and direct peer-CLI operations. Delete
address-derived IDs, unique-IP identity fallbacks, and address-only connects.
mDNS supplies bounded candidates containing `(peer_id, addr, protocol,
revision hints)`. It may cause a dial, but it never directly creates or
updates authenticated peer/library/Call-to-Play state. A candidate becomes a
peer only after a successful outgoing TLS connection to its advertised address
proves the expected `PeerId`.
mDNS supplies bounded candidates containing
`(peer_id, addr, protocol, revision hints)`. It may cause a dial, but it never
directly creates or updates authenticated peer/library/Call-to-Play state. A
candidate becomes a peer only after a successful outgoing TLS connection to its
advertised address proves the expected `PeerId`.
Use TLS 1.3 with a self-issued certificate. The custom client verifier must:
@@ -291,17 +291,17 @@ Use TLS 1.3 with a self-issued certificate. The custom client verifier must:
endpoint's expected ID; and
3. perform real TLS 1.3 CertificateVerify validation under the presented key.
The load-bearing negative test presents peer A's certificate/SPKI with peer
B's private key and requires the handshake to fail. Also reject a different
valid peer at a reused address. Use one version-bound ALPN, disable 0-RTT, and
start without TLS session resumption so every short-lived connection performs
the simple full proof.
The load-bearing negative test presents peer A's certificate/SPKI with peer B's
private key and requires the handshake to fail. Also reject a different valid
peer at a reused address. Use one version-bound ALPN, disable 0-RTT, and start
without TLS session resumption so every short-lived connection performs the
simple full proof.
The protocol is deliberately responder-authenticated rather than wrapping
every message in a signature:
The protocol is deliberately responder-authenticated rather than wrapping every
message in a signature:
- Requests that read public library/content state may be made by any LAN
client while Local network sharing is enabled.
- Requests that read public library/content state may be made by any LAN client
while Local network sharing is enabled.
- A response is authoritative only to the initiator that connected using the
expected `PeerEndpoint`; the TLS channel supplies integrity and request/
response correlation.
@@ -311,9 +311,9 @@ every message in a signature:
For a known claimed ID they schedule one coalesced, rate-limited pull from
that ID's already known endpoint. Their payload never merges directly. Hints
for unknown IDs are ignored and mDNS remains the discovery path.
- `Hello` becomes a pull-oriented exchange: the initiator sends no
authoritative identity or replicated state, and the pinned responder returns
its own current snapshot.
- `Hello` becomes a pull-oriented exchange: the initiator sends no authoritative
identity or replicated state, and the pinned responder returns its own current
snapshot.
This extra pull is one small LAN round trip and removes general signed
envelopes, canonical opaque payloads, nonce caches, replay semantics, inbound
@@ -346,11 +346,11 @@ 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
that address/generation. A same-ID address move is likewise committed only
after pinning the new endpoint. Pings are also pinned, and a late ping result
may update/remove only the same endpoint generation it probed so it cannot
delete a peer that has already moved or reconnected.
replace address ownership and retire the old record only if it still names that
address/generation. A same-ID address move is likewise committed only after
pinning the new endpoint. Pings are also pinned, and a late ping result may
update/remove only the same endpoint generation it probed so it cannot delete a
peer that has already moved or reconnected.
Remove `Goodbye`. It is unnecessary for correctness and an unauthenticated
removal hint is unsafe. mDNS expiry plus responder-pinned liveness handles
@@ -392,18 +392,18 @@ Authority rules remain simple:
- `Create`, `Start`, `Cancel`, and `AddTime` are effective only when the pinned
author equals `CallId.creator`.
- RSVP, ready/leave, and chat actions are attributed to the pinned author. They
become effective only while the referenced creator root is directly
present; an author slice pulled before its creator is retained within its
ordinary bound but remains hidden until that creator's direct pull arrives.
- A snapshot contains only events authored by its responder. Third-party
events are rejected rather than relayed.
become effective only while the referenced creator root is directly present;
an author slice pulled before its creator is retained within its ordinary
bound but remains hidden until that creator's direct pull arrives.
- A snapshot contains only events authored by its responder. Third-party events
are rejected rather than relayed.
- Display names never grant authority.
A newly arriving peer discovers and pulls directly from every live peer, so it
reconstructs calls from the people still present. If an author's peer goes
away, remove that author's slice. If the creator goes away, the call disappears
from the derived view. A participant who leaves naturally drops out. This is
the intended session model, not data loss.
reconstructs calls from the people still present. If an author's peer goes away,
remove that author's slice. If the creator goes away, the call disappears from
the derived view. A participant who leaves naturally drops out. This is the
intended session model, not data loss.
Keep the useful human-scale timers: active calls expire, unresolved expired
calls may remain visible for five minutes, and Start/Cancel results may remain
@@ -412,13 +412,12 @@ snapshot. There are no session-long tombstones, rootless terminal records,
three-day history horizons, verification caches, or permanent anti-resurrection
state because no third party can replay an old author's history as authority.
Retain straightforward schema and resource limits: bounded strings/chat,
bounded events and encoded bytes per author, bounded total live peers, and a
named control-frame maximum. Validate one author's snapshot off to the side and
accept or reject it as a unit; a bad/oversized peer cannot consume another
author's slice or the local author's capacity. Exact limits are set from the
existing three-peer and stress fixtures, not from an internet-scale adversary
model.
Retain straightforward schema and resource limits: bounded strings/chat, bounded
events and encoded bytes per author, bounded total live peers, and a named
control-frame maximum. Validate one author's snapshot off to the side and accept
or reject it as a unit; a bad/oversized peer cannot consume another author's
slice or the local author's capacity. Exact limits are set from the existing
three-peer and stress fixtures, not from an internet-scale adversary model.
A malicious creator can show inconsistent versions of its own noncritical call
to different peers. This plan accepts that limit rather than adding signatures,
@@ -428,8 +427,8 @@ gossip, consensus, or permanent storage to a party invitation feature.
Add one visible `Local network sharing` setting, on by default for this
LAN-sharing application. When off, stop mDNS advertisement/discovery, the QUIC
listener, outbound refresh, and serving. The setting is durable and its state
is obvious in the main UI/settings.
listener, outbound refresh, and serving. The setting is durable and its state is
obvious in the main UI/settings.
Do not add per-peer source prompts. Every peer with the locally expected
`content_id` is an eligible swarm source; verification is automatic.
@@ -441,10 +440,10 @@ Trusted, key-changed, backup, repair, or fingerprint-confirmation workflows.
User-facing exceptional states are concrete:
- `Verifying downloaded chunks` while newly received content is checked;
- `A source sent invalid data; retrying another nearby peer` when recovery is
in progress;
- `No nearby peer could provide the verified catalog version` after all
matching sources fail;
- `A source sent invalid data; retrying another nearby peer` when recovery is in
progress;
- `No nearby peer could provide the verified catalog version` after all matching
sources fail;
- `Nearby devices are running a different Lanspread version` when mDNS sees an
incompatible protocol; and
- a non-blocking networking diagnostic if the installation identity cannot be
@@ -484,18 +483,18 @@ version mismatch rather than failing silently.
Keep the change inside existing crates unless implementation pressure proves a
real reusable boundary; a new identity crate is not required by the design.
| Area | Responsibility |
|---|---|
| `lanspread-db` / `lanspread-compat` | Catalog content-manifest types and loading beside `game.db`. |
| `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, 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. |
| Tauri/frontend | Global sharing switch, verification/progress failures, incompatible-version notice, and replacement of the full derived Call-to-Play view. |
| peer CLI | Distinct deterministic identities, hostile TLS/content modes, and zero-prompt multi-peer scenarios. |
| Area | Responsibility |
| ----------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `lanspread-db` / `lanspread-compat` | Catalog content-manifest types and loading beside `game.db`. |
| `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, 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. |
| Tauri/frontend | Global sharing switch, verification/progress failures, incompatible-version notice, and replacement of the full derived Call-to-Play view. |
| peer CLI | Distinct deterministic identities, hostile TLS/content modes, and zero-prompt multi-peer scenarios. |
## 6. Implementation phases and gates
@@ -521,8 +520,8 @@ reported as Windows proof.
Add the reproducible content-manifest generator and fixture manifests. Freeze
the versioned manifest/content-ID encoding with golden tests. Extend local
catalog state, build download plans only from that state, implement streaming
BLAKE3 checks and source quarantine, and implement verified extracted
manifests for Stream Install.
BLAKE3 checks and source quarantine, and implement verified extracted manifests
for Stream Install.
Do not claim completion from test fixtures alone: production catalog packages
must have independently generated manifests, and the release/build path must
@@ -542,10 +541,10 @@ generation-conditional. No trust database or identity UI is introduced.
### Phase 4 — make the single wire cutover
Bump the current protocol once and activate all coupled wire behavior from
§4: pinned transport, catalog `content_id`, catalog-driven downloads, pull-only
library synchronization, bounded invalidation hints, author-owned
Call-to-Play snapshots, and no `Goodbye`.
Bump the current protocol once and activate all coupled wire behavior from §4:
pinned transport, catalog `content_id`, catalog-driven downloads, pull-only
library synchronization, bounded invalidation hints, author-owned Call-to-Play
snapshots, and no `Goodbye`.
This phase is not complete until:
@@ -576,11 +575,11 @@ phase; do not leave the shared-certificate or relayed-event description behind.
### Phase 5 — finish the small user-facing surface and audit
Add the global sharing switch and the concrete progress/error states from
§3.6. Run a first-run test with an empty app-data directory, a normal restart,
a corrupt identity file, and unwritable identity persistence; none may produce
a key-management workflow or prevent the ephemeral fallback from participating
for that run.
Add the global sharing switch and the concrete progress/error states from §3.6.
Run a first-run test with an empty app-data directory, a normal restart, a
corrupt identity file, and unwritable identity persistence; none may produce a
key-management workflow or prevent the ephemeral fallback from participating for
that run.
Run all standard checks, the complete peer-CLI suite, fresh three-peer manual
scenarios, production builds/bundles on supported platforms, and a final audit
@@ -600,8 +599,8 @@ for:
## 7. Success criteria
The plan is complete when the following statement is true from a user's point
of view:
The plan is complete when the following statement is true from a user's point of
view:
> I opened Lanspread at a LAN party, immediately saw the people and games
> nearby, downloaded from all matching peers without approving devices, and
@@ -610,6 +609,5 @@ of view:
From the implementation point of view, that experience rests on only three
security boundaries: confined local paths, catalog-owned content hashes, and
responder-pinned TLS. Call to Play deliberately reuses the pinned-pull model
and remains ephemeral instead of becoming a second distributed security
protocol.
responder-pinned TLS. Call to Play deliberately reuses the pinned-pull model and
remains ephemeral instead of becoming a second distributed security protocol.