Status: PREDECLARED 2026-10-02 ~19:00 CDT, before any code. Ruling R128 (~/ProjectAmp2/REVISION_REGISTER.md): fix what receipts/R0.9-GPT-REVIEW-2.md requires before R0.9 can be accepted, agenda items E-01..E-05, up to and including bundle r3, which is built and NOT sent. Builder: a Claude subagent on the Mac, over SSH to the PX13. The live record is receipts/R0.9-CLOSURE.md, section "R128 build (review 2 fixes): live record". House rules, as R118: no claim without a measurement; every law gets a planted bug only it catches, or the overlap is declared with its reason; references are written and hashed before their first run and never edited to agree with a run; the four frozen crates (cd-core, cd-wire, cd-node, cd-micro) stay byte-identical; every kernel-leg change is a deviation D10+.
Baseline before any edit (18:47:59 CDT, HEAD bceea0d, clean): tools/guards.sh ALL GUARDS PASS; tools/verify_r08_freeze.sh R0.8 FREEZE VERIFIED.
Design (fixed by the brief; deviations, if any, recorded with their reason).
cd_connect::AttemptNonce: a u64 in a private field. Its only constructor is AttemptNonce::fresh(), which reads
8 bytes from /dev/urandom with std::fs::File (std only, no new crate) and returns None if it cannot. It derives Debug, PartialEq, Eq and nothing else (not Clone, Copy or Default), and nothing implements a conversion into it. value() is what a transport encodes in the HELLO.
connect(expectation, candidate, transport) keeps its three arguments and makes the nonce ITSELF, once per call. If
the OS gives none, it fails with the new ConnectError::NoFreshNonce and nothing is retargeted. (Mechanism: the body is a private fn road(…, nonce_source) and connect passes AttemptNonce::fresh; the private seam exists only so a unit test can show the no-nonce path. road is not public, so no caller outside the crate can choose a source.)
The trait: TransportPeer::retarget(&mut self, cand, attempt: &AttemptNonce). Contract: it begins ONE attempt; any
HELLO the transport sends for this attempt carries attempt.value(); an implementation must either discard every piece of state from a previous attempt, or refuse.
The road, in order: (1) the handle is the expected peer; (2) a fresh nonce, or NoFreshNonce; (3) retarget;
(4) transport authentication; (4a) exact-datagram parse; (4b) correlation, NEW: the frame must be Frame::Ack with echo_nonce == attempt.value() — a Frame::Hello is UnexpectedFrame, an ACK with any other echo is the new AnswerNotForThisAttempt; (5) the expected-NodeId comparison, over decoded bytes, only after correlation; (6) the session. A wrong NodeId in a correctly correlated ACK still REACHES step 5 and fails there.
The trace (GPT's nc36 point). A new trait method road_step(&mut self, step: RoadStep), default empty, returns
nothing: the road reports each step as it RUNS (auth, parse, correlate, node). ScriptedPeer records retarget in retarget() and the rest through road_step, so its trace is retarget,auth,parse,correlate,node for a session and stops where the road stopped otherwise. The method returns nothing, so a transport cannot steer the road through it. Consequence, declared: nc42's gate nc42_R_road_trait_has_no_route_method lists the trait's methods literally; its expected list gains road_step. Its plant (m42_trait_route, a method returning the route) still trips it.
ScriptedPeer records each nonce it is given (nonces()) and builds its answer at retarget from that attempt's
nonce, discarding the previous attempt's: by ScriptedAnswer mode — Ack (default: the honest ACK), AckWrongEcho, UnsolicitedHello (it carries this attempt's nonce and the right NodeId, so only the frame kind can refuse it), and Fixed(bytes) (e.g. an earlier attempt's correct ACK, replayed). with_trailing is kept.
The lab probes that implement TransportPeer (lab/r09close/n1_probe.rs, n5_probe.rs, d6_probe.rs,
d2_probe.rs) answer a correlated ACK built from the nonce they are given. Their output lines and the gates of nc42, nc43, nc44 and nc46 are unchanged.
The kernel probe's PID-derived nonce (lab/nc35k/probe.rs:58) is removed: the nonce comes from connect().
Observation, not changed: frozen cd-node/src/main.rs:46 derives its hello nonce from the PID the same way. cd-node is frozen, and the listen responder the kernel leg uses (cd-node/src/lib.rs:74-86) echoes whatever nonce the HELLO carries, so the kernel cases are expected to keep their outcomes. That is measured by running them (E-02), not assumed.
Laws (named AC to keep them apart from the accounting gate's C1–C5), their gates and their planted bugs.
| law | gates | planted bug (r128 reference) | predicted RED set |
|---|---|---|---|
AC1 correlation: an ACK whose echo is not this attempt's nonce is refused, AnswerNotForThisAttempt | nc35_N_wrong_echo_is_not_this_attempts_answer; nc35_R_replayed_answer_is_not_this_attempts_answer (window nc35_R_window_the_replayed_bytes_answered_their_own_attempt); unit tests | r128_no_correlation: the echo check removed | N, R, NW and T_N. Declared: N and R are one law with two sources of a wrong echo (forged, replayed); NW's subject is the ORDER of two checks, and a road without the first has none; T_N reads where N stopped |
AC2 frame kind: a HELLO in place of the answer is refused, UnexpectedFrame | nc35_H_unsolicited_hello_refused; unit test | r128_hello_as_ack: a HELLO is taken as the ACK | H alone |
| AC3 order: correlation before the NodeId comparison | nc35_NW_correlation_precedes_the_node_comparison (wrong NodeId AND wrong echo → AnswerNotForThisAttempt); nc35_B_reached_the_semantic_step keeps B (right echo, wrong NodeId → NodeIdMismatch) | r128_node_before_correlation: the NodeId compared first, the trace reports untouched | NW and T_B. Declared: B now stops at the correlation step's position, and the trace sees it |
| AC4 a fresh nonce per attempt | nc35_Q_two_attempts_two_sessions, nc35_Q_two_attempts_two_different_nonces; unit test | r128_stale_nonce: fresh() memoized for the process | Q's nonce gate and R. Declared: a replay is accepted exactly when the nonce repeats |
AC5 nonce provenance (structural): built only by fresh(), which reads the OS and nothing else; not Clone/Copy/Default, no conversion into it | nc35_S_nonce_built_only_by_fresh, nc35_S_fresh_reads_the_os_and_nothing_else | r128_nonce_from_caller (a pub fn from_value); r128_counter_nonce (PID and a counter, no /dev/urandom: distinct per attempt, so AC1 and AC4 cannot see it) | the first gate alone; the second gate alone. NoFreshNonce is covered by this law and a unit test: any fallback has to mint a nonce outside fresh(), which the first gate refuses |
| AC6 the trace is the road's | nc35_T_E_trace_is_the_whole_road, nc35_T_B_trace_reached_the_node_comparison, nc35_T_N_trace_stopped_at_correlation, nc35_T_A_trace_stopped_at_authentication; nc36_C_window_the_trace_is_the_whole_road | r128_trace_skips_parse: the parse step is not reported | T_E, T_B, T_N and nc36's window (T_A stops before parse). Declared: two legs read one recorder |
R118's rows re-predicted because of E-01's new gates (declared in the r128 reference, before it runs): m42_claim_trusted (an unauthenticated answer claiming the right NodeId is accepted) now also turns nc35_T_A_trace_stopped_at_authentication red, because A no longer stops at authentication; m_bind_mints now also turns nc34's B and F authority gates red (E-03). R118's rows whose anchor E-01 moves (m42_retarget_first and five m43_* rows, all anchored on the retarget call) are re-derived on the new call with their expected sets unchanged.
Unit tests in cd-connect (run by cargo test --workspace in tools/guards.sh, battery leg 1): success; wrong nonce; a replayed previous answer; an unsolicited HELLO; a wrong NodeId with a correct nonce reaching the semantic step; two connects on one scripted handle with two different nonces; no fresh nonce → NoFreshNonce and nothing retargeted; correlation before the NodeId comparison. The existing tests stay.
KernelWgPeer: one attempt per handle, and no secret-printing queryChoice: a handle serves exactly ONE attempt. The first retarget begins it, whatever its outcome; a second retarget is refused at the top, before any kernel call, as RetargetError::Refused with its own note (retarget: refused: this handle already served an attempt; one attempt per handle (D10)). Why not reset: the per-attempt state is not only fields (target, baseline, started, outcome, answer) but the UDP socket's receive queue and datagrams still in flight from the earlier attempt, which can arrive AFTER any reset; a reset that is correct today is one forgotten field from being wrong tomorrow, while a refusal is one line a gate can watch. The probe already makes one handle per call. E-01's correlation is the second line: even if a stale answer were handed over, it would be refused as AnswerNotForThisAttempt (the plant below measures this).
KernelWgPeer::open() no longer takes a nonce; retarget keeps attempt.value() and the HELLO carries it.
The private key is no longer read. dump() becomes kernel_peers(), built from wg show <if> latest-handshakes,
wg show <if> endpoints and wg show <if> transfer — each one line per peer keyed by its PUBLIC key; none prints the private key or a preshared key. The doc comment says so, and says what the R0.9 code did. The leg's own in-container kdump helper is NOT changed: it reads wg show wg0 dump inside the container and drops line 1 there, which its comment already states truthfully; changing it would change the evidence lines of the four cases. Recorded in D11.
The control, in the battery: nc35k gains a section on a FAKE kernel (a wg and an ip shim on PATH that log
every invocation, and a responder on loopback that is the frozen cd-node's own serve_once), BEFORE the lab is deployed: lab/nc35k/reuse_control.rs opens one handle and calls the real connect() twice. It is not kernel evidence and says so. Two pseudo-cases with verdict lines the mutant harness reads (nc35k_mutants.sh learns Q and P):
Q, one attempt per handle (law KQ1): W nc35k_Q_W_first_attempt_on_the_handle_is_a_session; R
nc35k_Q_R_a_second_attempt_was_made; K nc35k_Q_K_no_kernel_call_during_the_second_attempt; C nc35k_Q_C_second_attempt_refused (Retarget(Refused)), nc35k_Q_C_the_refusal_names_the_rule.
P, no query that prints a secret (law KP1): W nc35k_P_W_the_handle_consulted_the_kernel; K
nc35k_P_K_no_query_that_prints_a_secret (every wg invocation is show <if> with latest-handshakes, endpoints or transfer, or set <if> peer <k> endpoint <a>).
Law KQ2, the HELLO carries this attempt's nonce: measured on the real kernel by E and B (the frozen responder
echoes the HELLO's nonce, so E is a session and B reaches the NodeId step only if it is the attempt's) and on the fake kernel by Q's window.
The four kernel cases must keep their outcomes (A TransportNotAuthenticated, B NodeIdMismatch with K1's
handshake advanced, E session, U TransportUnreachable). Before: the leg at bceea0d (run 18:48 CDT today, log nc35k-before.log). After: the leg on the R128 tree. Any change of outcome stops the work (R128 brief).
Deviations (in docs/r09-nc35-kernel-leg-DEVIATIONS.md): D10 one attempt per handle, open() without a nonce,
the nonce from connect(), and the fake-kernel section with Q; D11 no secret-printing query, with P, and the leg's kdump left as it is; D12 nc35's pinned SHA-256 moves again (E-01's new cases), the old value kept beside it.
Mutants (lab/nc35k/mutants-r128.json, mutants-r128.expected; R108's m0–m8 verbatim, their anchors do not
move): m9 the one-attempt guard removed → Q:K,C; m10 the R0.9 dump query restored → P:K; m11 the HELLO carries a PID-derived nonce instead of the attempt's → E:C B:C Q:W (declared: E, B and Q's window all need a correlated answer). The R108 rows gain Q only where a mutant breaks the handle's first attempt on the fake kernel: m2 (the handshake is never found) and m5 (the NodeId comparison always refuses) → Q:W; every other R108 row is unchanged.
auth() returns the whole authority line (got, worker, worker_id, locus, evidence_len), and every
comparison requires it non-empty on both sides (nc35 F2's form). nc34 B records authority w1 before and after its eight facts (new gate nc34_B_authority_identical); F's mint and widen gates compare the whole line; a new nc34_F_evidence_count_unchanged names the evidence count read from those lines.
Law N34, its plants: R118's m_bind_mints (every bind adds grant evidence), which nc34 missed, must now turn
nc34 B and F red; and a new r128_bind_mints_on_d_hit (only a fact labelled d_hit, which only nc34 records, adds evidence) → nc34's four gates and nothing else. Declared overlap inside N34: the evidence gate is a projection of the line that mint and widen compare; it exists to name what moved.
Wording narrowed: nc34 does not have six separate legitimate movers. It shows the snapshot moving under attach and
finalize and the authority line moving under grant, and reads the six verbs from those measurements (mint and widen from the authority line; bind, restore, transfer, close from snapshot fields). The header and the PASS line say that, and no longer say "every one of the six verbs was measured moving".
(a) The boundary gate matches identifier SEGMENTS, case-insensitively (tools/rendezvous_boundary_gate.py, law
B3). Every identifier-like token in code (string literals included, // comments removed) is split at _, at non-alphanumerics and at lower→upper and acronym boundaries, lower-cased, and each segment compared with each name. Exemptions, reviewed and listed in the gate with a reason each: UNIX_EPOCH (std's clock origin). An exemption is an exact token, never a substring rule. nc38's reference is revised before its first run under R128 (dated note; R118's header pre-declared the flip): the four b_ rows (CarrierId, locus_id, Generation, fencing_epoch) now expect B3; new rows p_seg_screaming_locus_id (LOCUS_ID) → B3, p_seg_label_with_hyphen ("fencing-epoch") → B3, p_exemption_is_exact (unix_epoch_fence) → B3, n_unix_epoch (std's UNIX_EPOCH used) → nothing; and two declared blind spots, re-measured every battery: b_plural_anchors (anchors) and b_abbrev_gen (gen).
(b) docs/r09-deferred-threat-backlog.md: "so it cannot be missed" removed; the representations and scopes the
guard does NOT cover listed; torn writes, symlink effects and identity substitution stated to matter even without a location map.
(c) Coverage wording: N1 (n1-matrix.tsv notes for R>I, R>C, R>E: their strength is principally the structural
absence of a route input; L>I/L>C/L>E structural only, kept; the N1 catalogue row: accounted dispositions, not twenty independently exercised attack channels); nc36 (equal five-step traces on identical normalized inputs, not a proof about every road); nc46 (isolation evidence plus a real fence baseline, not an integrated attack: the scripted episode's session is never delivered to cd-durable). nc46's window gate is renamed from nc46_B_window_the_episode_reached_the_fenced_node to nc46_B_window_the_scripted_episode_returned_a_session, and the catalogue's D2 row names the new gate (a declared reference change, recorded with this reason; no other cell moves).
(d) `tools/verify_battery.py` enforces the REQUIRED inventory itself: the 27 legs in battery order, each with an
exact verdict line (a whole line, anchored at its start: ALL GUARDS PASS; NCnn PASS alone or followed by — …; R0.8 FREEZE VERIFIED: …; run_r08's receipt: receipts-lab/R0.8-<stamp>.json result: PASS), exactly one, and no FAIL verdict for that leg anywhere in its log; the N1 and catalogue checks always on. Laws: V1 leg_inventory_complete, V2 leg_inventory_has_no_extra_or_repeat, V3 leg_<x>_reached_its_subject (exact), V4 the table checks always on. Plants (~/build/r128/vlaw_plants_r128.sh, reference in its header, hashed before it runs, run on the R128 battery's receipt, and each also against the R118 verifier from git for contrast): R118's four rows kept (v0, v_n1_only, v_cat_only, v_unnamed); v_missing_leg (nc33 removed) → V1 only; v_extra_leg (an nc99 with a log saying NC99 PASS) → V2 only; v_repeated_leg (nc33 twice) → V2 only; v_marker_in_prose (nc33's verdict line demoted into a longer line) → V3 only; v_fail_after_pass (NC33 FAIL (1). after the PASS line) → V3 only; v_no_nc41 → V1 and the catalogue check (declared: V4 matters only when nc41 is absent, which V1 also refuses; the catalogue names nc41's gates). Expected under the R118 verifier: every new row passes unseen.
(e) The private-key claim: done under E-02. S5's chronology: lab/r09close/s5-reference.txt's header and the
record say the three declared additions were written retrospectively, on 2026-10-02, from the implementation as it stood at 7c11fc7 — a historical reconstruction, not an enumeration performed at the R0.9 open.
(f) Review 1's wording, corrected where it is cited, with the old words kept visible: disposition 3's laws are
C3/C4 (the catalogue's), not A3/A4 (N1's); disposition 4's guard claim and its "inert" reading; disposition 1's "no other deletion".
The mutant sets a change could affect, each against a reference written and hashed before it runs:
lab/r09close/mutants-r128.{json,expected} (R118's 23 rows re-derived — anchors moved by E-01 revised and declared, m_bind_mints now predicting nc34 — plus the R128 rows above; legs nc34 nc35 nc36 nc42 nc43 nc44 nc46), run by lab/scripts/r09close_mutants.sh on the host; lab/nc35k/mutants-r128.{json,expected} by run-r09-nc35k.sh mutants in the runner; the vlaw plants; and the plant-reference legs (nc38–nc41, nc45) inside the battery.
The FULL battery (27 legs; no leg is added, so the runner's four places are unchanged) on the final committed tree,
verified by the new tools/verify_battery.py.
The bundle generator's README_FOR_GPT.md lists review 2's items with each disposition (fixed, with commit and
control; amended; or Travis's) and keeps the still-open ones. Bundle r3 is built with OUT=$HOME/Downloads/COMPUTEDRIVEN_R09_CLOSURE_FOR_GPT-r3-<sha>.tar.gz; r2 and the 13:50 bundle are not touched. Its path, size and sha256 go in the record. It is not sent.
Written and hashed before their first run; the hashes are appended below, dated, as each is written. lab/r09close/nc38.expected and nc38.plants.json (revised, E-04a); lab/r09close/mutants-r128.json and .expected (new); lab/nc35k/mutants-r128.json and .expected (new); ~/build/r128/vlaw_plants_r128.sh (new, outside the tree); lab/r09close/catalogue.tsv (D2's renamed gate, N1 wording), lab/r09close/n1-matrix.tsv (notes only), lab/r09close/s5-reference.txt (header only). R118's and R108's references (mutants.json/.expected in both places) are kept as they were: they are the record of the trees they were written for.
nc35: N, R (+window), H, NW, Q ×2, S ×2, T ×4 (13 gates); nc36: one window; nc34: B authority, F evidence (2 gates) and every authority comparison made complete; nc35k: Q (5 gates) and P (2 gates) on a fake kernel; nc38: 6 new rows and 4 flipped; verify_battery: V1–V4; 8 unit tests in cd-connect.
A frozen crate would have to change; the kernel leg cannot run; any of A, B, E, U changes outcome; a law cannot get a plant only it catches. Any of these stops the work and is reported, not improvised around.
2026-10-02 19:07:44 CDT — lab/r09close/nc38.expected a074d71ba9c6036f5b279e13d7683f46e2033d37bcf9608e331dca8ef61adca5,
lab/r09close/nc38.plants.json e9cedc2781c73e905fecfc8aaa34bb37016793eac426945c78f6769435ee33bb (R118's: 0a111e05242d…, 3867a650f501…, in git).
2026-10-02 19:10:13 CDT — ~/build/r128/vlaw_plants_r128.sh (outside the tree; its reference is its header)
e24fa32fbfc1febbdb8566cc96c709b4e3829262295b2439d6e1e08723c6f77a.
2026-10-02 19:15:30 CDT — lab/r09close/mutants-r128.json 0cc903557ab4ac60c51e2b98c8a1277aefcbb69d4d8579c8cfcb837f903ea5b7,
lab/r09close/mutants-r128.expected e8fe8d090ab4bd803798e96daf33dfb51d6b3278a5b706032bacc308e3947c2f, lab/nc35k/mutants-r128.json 11cfdb060fab74aa71f1c62e68580c1e2e1fd4c42ed6308475c859d5a715f47a, lab/nc35k/mutants-r128.expected 40e1da29542e72e88376bacb9c5f32a1ae36052f8725be047d6469b2b9f8b06d.