This file is the single canonical detail for every R0.9 threat family the round deliberately did not build. receipts/R0.9-OPEN.md keeps a compact summary and links here; it does not duplicate what is below. Two copies of a truth is how the first one goes stale.
Nothing here is a completion requirement for the first known-identity rendezvous theorem. Each entry says what would make it open: a family opens when the implementation actually introduces its surface, not when someone remembers it exists. A control whose subject does not exist is the vacuity this branch has shipped eight times, and the ninth was this round's own.
The families are ordered by how likely their surface is to arrive.
Opens when: the implementation introduces a relay — anything that forwards on another party's behalf.
No relay exists in the first mechanism. Kept as backlog so the surface arrives to a contract rather than the reverse.
R1 — the relay as confused deputy. [honest] A relay's own position,
credential or reachability must not count as the relayed party's.
R2 — the relay as amplifier. [raw] From an unvalidated source, measure
bytes-out / bytes-in for every response type. Assert ≤ 3.0 and assert traffic stops at the budget until a challenge completes. Precedent: RFC 9000 §8; SSDP at ~30×; WS-Discovery observed at 100–500×.
R3 — the relay is a wire. [honest] Delay, drop, reorder, observe. Not
mint, not bind.
R4 — reservation-scoped relaying. [honest] Refuse to forward without a
reservation; run to the duration and byte caps and assert the cut. Precedent: libp2p circuit relay v2, which replaced v1 precisely because v1 had no reservation and was defended by heuristics.
R5 — bounded state under challenge flood. [raw] Flood path challenges
while suppressing progress; assert bounded memory. Precedent: quic-go, quiche and quicly all queued `PATH_RESPONSE` unboundedly.
Opens when: a peer's address is allowed to change during a session, or the plane gains a mobility protocol of its own.
Same reason.
M1 — a move confers nothing. [honest] Complete a legitimate migration;
diff the capability set before and after and assert it is byte-identical. Precedent: MPTCP `ADD_ADDR` adds a path, never a right.
M2 — the off-path claim. [raw] An off-path party claims a peer's move.
Assert refusal without a round trip to the claimed address, and prefer the stronger form: prove it on the old identity path and the new location path both. Precedent: Mobile IPv6 return routability, RFC 3775.
Opens with the R-family, of which it is the sharpest single control.
P4 — relay reach-set, deny by default. [honest] **DEFERRED — the first
mechanism has no relay, and a control whose subject does not exist is the vacuity this branch has shipped eight times.** Kept because the surface will arrive.
Ask the relay to forward to 127.0.0.1, ::1, ::, 0.0.0.0, ::ffff:127.0.0.1, 169.254.169.254, RFC1918 space, and its own control port. Assert refusal — then delete the denylist and assert it still refuses. A reach-set defended by a list is defended until the next notation; coturn's loopback denial was bypassed first by 0.0.0.0 and then again by IPv4-mapped ::ffff:0:0/96.
Opens when: the plane gains a persistent peer table that survives a restart. The first mechanism has none.
D10 — eclipse survives restart. [honest] **DEFERRED — needs a persistent
peer table, which the first mechanism does not have.** Poison the peer table from k identities across j distinct /16s, restart, measure the adversarial fraction of outbound peers. Assert a live entry is probed before a new claim can displace it. Precedent: Bitcoin `test-before-evict`, feelers, anchors.
Opens when availability against routing hijack becomes a goal. It is not, and must not be presented as, what prevents authority minting.
C1 — vantage-point corroboration. [honest] **DEFERRED, and reclassified:
this is availability and hijack hardening, NOT the core safety property.** If a single malicious endpoint hint is already harmless because the subsequent handshake must authenticate a pre-existing expected peer, then corroboration is not what prevents authority minting — and requiring it for core safety would misattribute where the safety comes from. Keep it for the round that cares about being reachable, not the round that cares about being unforgeable. Precedent: CA/B SC-067 (MPIC), after Birge-Lee et al. obtained real certificates via BGP hijack and the 2018 Route 53 / MyEtherWallet hijack.
Opens when an unknown-peer plane with a routing metric exists to place identities in.
C4 — identity placement cost. [compute] **DEFERRED — there is no
routing metric to place identities in until an unknown-peer plane exists.** Measure the cost of placing m identities adjacent to a target in whatever routing metric R0.9 adopts. Assert either superlinear cost or d-disjoint-path lookup coverage. Precedent: S/Kademlia; IPFS content eclipse from one small instance.
Opens when the round decides a caller may learn of an identity it did not already expect. That is a different trust contract, not a bigger version of this one: the first mechanism's entire safety argument is that a PeerExpectation exists before any query, so a malicious answer can only misdirect. Remove that premise and every control in R0.9-OPEN.md §6 has to be re-argued.
DHT bootstrapping, peer sampling, Sybil-resistant placement and rendezvous introduction all live here. receipts/R0.9-OPEN.md §4 keeps the prior art — discv5's signed sequence-numbered records, S/Kademlia's disjoint-path lookups, the Bitcoin and Ethereum eclipse results — because whoever opens this should start from what has already been learned rather than from the catalogue.
Opens when: anything keys a map — or any table, in any shape — by NodeId. It opens BEFORE that change, not with it: the first such change must first open an R0.8-profile repair round.
cd-node/src/lib.rs:43 creates the identity file with fs::write(path, id): File::create semantics, which follow a planted symlink, with no O_EXCL, no fsync and no atomic rename — the primitive F22 removed from cd-durable. R0.9-OPEN §8 recorded it at the open as inert while a NodeId is authority-inert, and consequential "the moment R0.9 makes the NodeId the key of a location map": then a NodeId that can be swapped or torn on disk becomes a key that can be made to point at someone else's entry.
Ruled by Travis, R118 (2026-10-02): it stays as it is until something keys a map by NodeId. The first change that does must first open an R0.8-profile repair round — write to a temporary file, fsync it, rename it over the target (and fsync the directory), with the round's own falsifier first, as F22 was. cd-node is one of the four frozen crates (R0.8-FROZEN; the declared exception is R4's additions to cd-core only, see lab/r09close/frozen-exception.json), so the repair is its own round and cannot ride along with the map.
The trigger has a guard for its commonest shape; the ruling covers every shape. (R128: until then this paragraph said the guard meant the trigger "cannot be missed". It can: GPT's review 2 was right that the guard covers fewer representations and scopes than the opening condition above, which is ANY NodeId-keyed table.) tools/nodeid_map_guard.py, run by tools/guards.sh, fails the build when a map keyed by NodeId appears in cd-rendezvous, cd-connect, lab/**/*.rs or the Rust embedded in lab/scripts/*.sh: any identifier ending in Map or Cache, in type position or by turbofish, whose first type argument is NodeId — path-qualified, by reference, or through a type / use … as alias in the same file. lab/scripts/nc40_nodeid_map_guard.sh (battery leg) plants each scope and each form and checks the guard fires on exactly that one.
What the guard does NOT cover, so a reviewer must:
representations: a HashSet/BTreeSet of NodeIds; an association list Vec<(NodeId, _)>; a map keyed by the raw
[u8; 32], by a newtype around NodeId, by its hex or base64 text, or by a hash of it; a key type that is a tuple or struct containing a NodeId; a map type whose name does not end in Map or Cache (IndexMap-like names do, a Table, Registry or Directory does not); a sorted Vec searched by NodeId; a NodeId used as a file or directory name, or as a database or sqlite key; Python dicts in lab tooling (nc40 re-measures the first four every battery as declared non-matches);
scopes: every crate other than the two planes — cd-node, cd-durable, cd-lifecycle, cd-home, cd-floor,
cd-kernellet and any new crate — and every tool, script and configuration outside lab/. A table in any of those shapes or scopes, keyed by NodeId, triggers the repair round by the ruling all the same, and the review of any change that introduces one must say so; the guard checks the commonest shape, nothing more.
Why it matters even before a location map exists (R128). "Inert while the NodeId keys nothing" is about AUTHORITY: a NodeId holds no lifecycle authority, so a swapped one cannot widen what a party may do. It is not about the node itself. The write's own defects have consequences today: a torn or interrupted write (no fsync, no temp-and-rename) can leave a node with a partial identity file that load_or_create_identity then refuses, or with none, so it mints a NEW identity at restart; a planted symlink at the identity path is followed, so the write lands where the link points; and a substituted identity file makes the node announce someone else's NodeId — an identity substitution that peers holding a PeerExpectation for the real one refuse, but that a local operator or a later NodeId-keyed table would not see. These are availability and integrity consequences on the node's own filesystem; the deferral accepts them for now by ruling, and does not claim they are absent.
Opens when: the first plane state that holds a route or a location against anything — a peer table, a location cache, gleaning (P3's raw half) or mobility (the M-family). Item 7's guard (tools/nodeid_map_guard.py) trips on the NodeId-keyed form of such a table.
N1 asks, for each ordered pair of identity, route, credential, endpoint and lifecycle authority, that an attacker-chosen source establish nothing about an honest target. Sixteen of the twenty cells are measured by named gates (lab/r09close/n1-matrix.tsv, accounted by tools/r09_accounting_gate.py, leg nc41; the cells nothing measured before R118 are leg nc42). The four whose TARGET is a route are deferred because no plane holds route state for any source to establish: cd-rendezvous keeps nothing (a hint is one address, not stored); cd-connect keeps nothing between calls and outputs no route at all (nc42_R_road_trait_has_no_route_method, nc42_R_road_trait_returns_no_address); the only route object in the tree, cd-node's ObservedRoute, is a per-datagram return value R0.4 keeps apart from NodeId and nothing stores. A control here would measure a subject that does not exist. The ROUTE as a SOURCE (R>I, R>C, R>E, R>L) is measured — R>L by nc34's reachability bases; R>I, R>C and R>E by nc42, whose dynamic cases vary a field the connector cannot receive (the trait has no method to hand it over), so their strength is principally that structural absence, which nc42's R gates check (R128 wording, GPT review 2).
Five catalogue IDs are executed in part and deferred in part (lab/r09close/catalogue.tsv, status split). The executed half is named there by its gates; the deferred half is recorded here, where every R0.9 deferral lives.
Opens when: mobility (the M-family) arrives, or the first location table. The scripted half is executed (nc42's I>E gates: an unauthenticated datagram claiming N from an attacker's route gets no session, moves only the candidate, and the road outputs no route and keeps nothing; nc40: no NodeId-keyed table exists to glean into). The raw half — spoofed packets in a netns against the kernel lab — has no location state to poison, and WireGuard itself updates an endpoint only from a packet that authenticated. It joins M2's off-path claim when that opens.
Opens with: the R-family — the first relay or proxy; P4 is its sharpest control. The address-class half is executed (nc43: sixteen classes, none turns a refusal into a session, the planes hold no class predicate). The hop-limit analogue — a Locus fact must not be fetchable through a relay or proxy — needs a relay, and the first mechanism has none.
Opens when: the plane gains registration — a directory that records which key claims a NodeId. The client half is measured (nc35k A: a working peer holding K2 whose cd-node claims N1 is refused at transport). "The second is refused" by a registry has no subject: the plane has no registration and cannot carry a key at all (nc35 D).
Opens when: a rendezvous SERVICE exists that an operator could run with its own signing key. The structural half is measured (the plane cannot name a Locus — the boundary gate, shown to fire by nc38 — cannot carry a credential, and nothing builds a PeerExpectation from it).
Opens when: the road holds a secret of its own — a token, a pre-shared key, a credential — that it could emit. No secret material exists in the R0.9 planes (the private key stays in the kernel, §7a), and the probe's HELLO travels inside the tunnel, so a wrong peer's application hears nothing (nc35k A). WireGuard's own handshake initiation is the remaining pre-authentication emission; it is WireGuard's contract, not this round's.