Four things in this system are routinely called "the address of that node", and they are four different things. They are listed here in the order they were separated by evidence, and the separations are executed results, not design intentions.
1 NodeId semantic identity of a Fabric node
─────────────────────────────────────────────────────────────────
2 ObservedRoute application-visible route to that endpoint
─────────────────────────────────────────────────────────────────
3 WireGuard public key transport authentication credential
─────────────────────────────────────────────────────────────────
4 WireGuard endpoint current underlay network location
Nothing derives any of these from any other. A NodeId comes from /dev/urandom into an identity file and cd-node has no knowledge that WireGuard exists; a WireGuard keypair comes from wg genkey and has no knowledge that a NodeId exists; an ObservedRoute is whatever recv_from reported as the source of the datagram that just arrived; an underlay endpoint is whatever the peer is currently dialling.
R0.4 separated 1 from 2. hello_once returns (NodeId, ObservedRoute) as two values and there is no conversion between them. Two exchanges from two different sockets, same responder: same NodeId, different ObservedRoute.
R0.6B separated 1 from 3. The server's WireGuard keypair was replaced under a responder that was never restarted, and the exchange still returned the same NodeId.
R0.6C separated 1 from 4 — and this is the layer that was mislabelled. R0.6C moved the server's UNDERLAY endpoint, 192.168.2.10 -> 192.168.2.20, vacated the old address, and repointed the peers. What it did not do is change the application-visible route: across all 21 R0.6 runs the transcripts report
observed_route=10.100.0.2:47001
both before and after the move, because the responder stayed bound to the same overlay address throughout. The supported claim is therefore:
The WireGuard underlay endpoint location changed while both the semantic NodeId and the application-visible ObservedRoute remained stable.
Do not read the R0.6 receipts as demonstrating an ObservedRoute change. Their DOCTRINE block collapses layers 2 and 4 into one line — IP endpoint observed reachability (ObservedRoute) — which named the right experiment under the wrong label. Those receipts are historical and are left exactly as executed; this file supersedes their terminology, and R0.7 onwards records all four layers separately, per row, in its identity table.
R0.6 gated "no NodeId equals any WireGuard public key" by comparing the two as printed strings. That gate could never have fired: a NodeId prints as 64 hexadecimal characters and a WireGuard public key as 44 base64 characters, so string equality is structurally impossible even if the underlying 32 bytes were identical. It was not load-bearing — the real evidence for 1 ≠ 3 is R0.6B's key rotation under an unchanged NodeId, plus the source-level independence above — but it was not evidence either.
R0.7 keeps the falsifier and makes it mean something: both sides are decoded to their 32 raw bytes before comparison. An equality falsifier over encoded forms is worth nothing; over canonical bytes it is worth having.
No discovery and no rendezvous. In both R0.6 and R0.7 the runner
is the out-of-band configuration channel: an operator moved a host, or started a successor, and told the peers. Nothing learns a new endpoint by itself.
No durable identity across a lab lifetime. Each run mints fresh
NodeIds. Identity survives the network manipulations within one running responder; a fresh lab lifetime is a fresh identity.
No relation between a NodeId and a Carrier, Worker, or Locus.
R0.7 records LocusId, WorkerId, CarrierId and NodeId side by side precisely so that the question of which of them a persistent NodeId belongs to stays open and answerable by a later experiment, rather than being settled silently by a convenient equality.