computedriven computedriven/docs/r09-nc35-kernel-leg-plan.md
Status: DRAFT, prespecified 2026-09-30 by the Claude session on the Mac, at Travis's go. Not reviewed by GPT. Nothing is built or run. On review: freeze it, then build in the or…

R0.9 — nc35's kernel leg: cases A and B on real kernel WireGuard (plan)

Status: DRAFT, prespecified 2026-09-30 by the Claude session on the Mac, at Travis's go. Not reviewed by GPT. Nothing is built or run. On review: freeze it, then build in the order in §5. This is GPT's execution-order item 9 (R0.9-OPEN §8a). lab/scripts/nc35_layer_substitution.sh says it itself: "The scripted transport is NOT the claim -- it is the harness … The kernel-WireGuard execution of A and B is a separate leg." This file is that leg's contract, written before its code.

1. What the leg must show, and nothing more

The cases keep nc35's letters and nc35's expected values (nc35_layer_substitution.sh lines 145–156).

CaseSetupKernel evidence requiredConnector result required
ARight PeerExpectation (N1, K1). The endpoint is held by a DIFFERENT WireGuard peer (K2).K1's latest handshake stays 0 for the whole bound TTransportNotAuthenticated, and no session
BRight transport peer (K1). Its responder answers with NodeId N2 ≠ N1.K1's latest handshake ADVANCES after the probe starts, within TNodeIdMismatch (ConnectError::NodeIdMismatch), and no session
E (positive control)Everything correctK1's handshake advancessession: a ValidatedPeerSession with N1, K1 and the probed address
U (control)The endpoint's interface is down, or no routenoneTransportUnreachable, not TransportNotAuthenticated
  • B passes only with BOTH halves. The kernel must show K1 authenticated, AND the connector must refuse at the

NodeId comparison. A B that fails at transport proved the transport check twice and the semantic check never (nc35's header).

  • E and U exist so A and B cannot pass vacuously. A leg where nothing ever authenticates would "pass" A and fail

E. A leg that folds "down" into "unauthenticated" would pass A and fail U.

  • Not claimed: key rotation (R0.9 does not own it, §7a), paths between separate hosts, relays, mobility, or

anything the scripted nc35 already covers (C, D, P, F stay scripted).

2. Where it runs

In the existing R0.x lab on the PX13, exactly where R0.6–R0.8's WireGuard legs ran. Verified read-only on 2026-09-30:

  • The privileged Docker runner is ~/computedriven-r05-qualification/runner/run-r09.sh, "the same privilege model as

the accepted R0.5-R0.8 runner mechanism". The user travis is in the docker group, so no sudo is needed.

  • containerlab topologies from lab/clab-cd-r0x/.

  • The node image is lab/Dockerfile.cd-node-wg (Ubuntu, wireguard-tools, the static cd-node).

  • Kernel WireGuard interfaces come from the tree's own lab/scripts/wg_overlay*.sh (ip link add … type wireguard).

It needs no locuchest, and no Super host.script.run (F1). cd-floor-01 stays bench-only (Travis 2026-09-30), and the leg never runs there. Any battery-grade timing belongs under docs/battery-host-profile.md; this leg makes no timing claim.

3. The mechanism to build (after the falsification commit)

  • `KernelWgPeer`: an implementation of cd-connect's TransportPeer trait (cd-connect/src/lib.rs), living in

lab tooling. It never goes in a frozen crate and never in cd-rendezvous: the boundary guard "cd-connect has exactly one non-test call site" must still hold. It reads the kernel's view with wg show <if> dump (or netlink) and compares decoded 32-byte keys, never strings. R0.6's gate compared 64 hex characters against 44 base64 characters and could never fire.

  • The outcome, derived from kernel evidence only:

  • Authenticated if the expected peer's latest handshake advances after the probe starts, within T;

  • NotAuthenticated if the interface is up and the endpoint is routable, but no handshake completes within T;

  • Unreachable if the interface is down or there is no route.

  • T is fixed in advance at 10 s. WireGuard's REKEY_TIMEOUT is 5 s, so one retry fits.

  • The responder is the unmodified frozen cd-node HELLO/ACK in the endpoint's container, configured with N1 (for

E) or N2 (for B). The frozen crates are not touched (R0.8-FROZEN, tools/verify_r08_freeze.sh).

  • Keys: generated per run with wg genkey inside the containers, loaded with wg set … private-key, and never

written to the host or to any receipt. Receipts carry only public keys' SHA-256 prefixes. The local private key stays in the kernel (§7a).

4. Pass criteria for the leg

  1. A, B, E and U each produce their required kernel evidence AND connector result (§1). Anything else fails.

  2. The scripted nc35_layer_substitution.sh still passes, unchanged.

  3. tools/guards.sh is ALL GUARDS PASS, including the five R0.9 boundary assertions.

  4. tools/verify_r08_freeze.sh is unchanged.

  5. Teardown leaves no lab containers and no WireGuard interfaces, and the run records the host's module state before

and after.

5. Build order (falsifiers first, mechanism second)

  1. Falsification commit: lab/scripts/nc35k_kernel_leg.sh exists and is wired as battery leg 18 of run-r09.sh,

with the four-places check (rc_ init, run_leg, FINAL, jq args; §7 H-family). It FAILS with "no kernel transport — the kernel leg has no subject yet".

  1. Mechanism: KernelWgPeer and the leg's topology, until §4 holds.

  2. Mutants, each caught by exactly its case, or with the overlap declared:

  • (m1) KernelWgPeer reports Authenticated without reading the kernel → A goes red;

  • (m2) keys compared in two different encodings → E and B go red. This overlap is declared: both need the

expected peer to match, and a key that never matches makes neither authenticate;

  • (m3) the responder's NodeId is not compared → B goes red at the semantic step;

  • (m4) Unreachable is folded into NotAuthenticated → U goes red.

  1. Execution under run-r09.sh, with a receipt receipts/R0.9-NC35K-EXECUTED.md: the raw wg show … dump

lines (public-key prefixes only), every exit code, and the tree's HEAD.

  1. Then R0.9-OPEN §9's closure list, and GPT's freeze review.

6. Open for GPT and Travis

  1. T = 10 s: accepted?

  2. R97 says every stage is built WITH Super. This leg runs in the lab runner, which Super cannot drive today:

repository-python-gate@1's table for this repository names only tools/succession_laws_gate.py. Accept the lab runner for this leg, or route the code change through a Super review first?

  1. The root-owned `target/release` (since 2026-09-03) makes the guards refuse the default build directory. Fix

its ownership (that needs root), or keep using CARGO_TARGET_DIR?

Open in the interactive atlas