Mesh: NAT traversal and relay for mesh peers #220

Open
opened 2026-10-15 08:17:20 +00:00 by kayg · 129 comments
Owner

Owner 2026-10-15: build NAT traversal and the relay for mesh peers first (survey #219: PathKind::Relayed is only a type today). D163 (relay fallback, back to direct), D131 (relay is one command and a "Run a relay here" box in the UI), D185, D197 M5 (paths), D265. Merges with or after mesh-autoswitch #201.

Owner 2026-10-15: build NAT traversal and the relay for mesh peers first (survey #219: `PathKind::Relayed` is only a type today). D163 (relay fallback, back to direct), D131 (relay is one command and a "Run a relay here" box in the UI), D185, D197 M5 (paths), D265. Merges with or after mesh-autoswitch #201.
Author
Owner

Starting mesh-relay on branch mesh-relay at base/head c87fc06314 (origin/idle-cpu-199). Read #220 (no prior comments or linked parent), #219 body/comments and survey attachment; reading #201 history and existing shared interfaces before edits. Switch-over marker is absent: only single-crate checks/tests until it appears; no workspace/static build started. The work covers D131/D163/D196/D197/D198: discovery, peer ciphertext relay with TCP/TLS fallback, direct recovery, removable relay component, namespace proofs and perf-VM evidence. No deploy.

Starting mesh-relay on branch mesh-relay at base/head c87fc063141d33658715eea42687330352e8bd5c (origin/idle-cpu-199). Read #220 (no prior comments or linked parent), #219 body/comments and survey attachment; reading #201 history and existing shared interfaces before edits. Switch-over marker is absent: only single-crate checks/tests until it appears; no workspace/static build started. The work covers D131/D163/D196/D197/D198: discovery, peer ciphertext relay with TCP/TLS fallback, direct recovery, removable relay component, namespace proofs and perf-VM evidence. No deploy.
Author
Owner

Contract proposal before implementation (D163/D196/D197/D198): keep endpoint candidates separate from reachability and path selection. Discovery records must refer to the actual peer-traffic socket and a current network generation; bound candidates, reject unusable addresses, expire observations, and distinguish observed stable mappings from destination-dependent mappings without claiming a NAT filtering type from two samples. A mapping observed toward the control connection is not a WireGuard reflexive candidate. Add public kernel endpoint parsing for IPv4/IPv6 without exposing device private fields. First independent slice: pure discovery state and strict address validation in infraternal-mesh, with no listener, timer, new dependency, wire-format change or #201 selector change. Relay forwarding will remain ciphertext-only and use existing selected QUIC/TLS stacks; no alternative library/engine is being selected. Switch-over marker remains absent; workspace/static builds are still prohibited by this brief.

Contract proposal before implementation (D163/D196/D197/D198): keep endpoint candidates separate from reachability and path selection. Discovery records must refer to the actual peer-traffic socket and a current network generation; bound candidates, reject unusable addresses, expire observations, and distinguish observed stable mappings from destination-dependent mappings without claiming a NAT filtering type from two samples. A mapping observed toward the control connection is not a WireGuard reflexive candidate. Add public kernel endpoint parsing for IPv4/IPv6 without exposing device private fields. First independent slice: pure discovery state and strict address validation in infraternal-mesh, with no listener, timer, new dependency, wire-format change or #201 selector change. Relay forwarding will remain ciphertext-only and use existing selected QUIC/TLS stacks; no alternative library/engine is being selected. Switch-over marker remains absent; workspace/static builds are still prohibited by this brief.
Author
Owner

Starting research-nat on branch research-nat at base/head e972f91dad (merge base with origin/dev is identical). Read #220 body/all 2 comments and #219 body/all comments; neither identifies a parent issue. Continuing the mesh-relay discovery contract rather than duplicating implementation. Deliverable: primary-sourced docs only, early findings here followed by research/nat-traversal/README.md; foundational engines remain owner choices. D196 stage-one both-port-changing relay boundary and D197 M5 hysteresis will remain explicit. No deploy; final task instruction authorizes branch pushes.

Starting research-nat on branch research-nat at base/head e972f91dad67ac1f1274a4e4cd70df24ee052bc1 (merge base with origin/dev is identical). Read #220 body/all 2 comments and #219 body/all comments; neither identifies a parent issue. Continuing the mesh-relay discovery contract rather than duplicating implementation. Deliverable: primary-sourced docs only, early findings here followed by research/nat-traversal/README.md; foundational engines remain owner choices. D196 stage-one both-port-changing relay boundary and D197 M5 hysteresis will remain explicit. No deploy; final task instruction authorizes branch pushes.
Author
Owner

Early research-nat design input for mesh-relay (#220); full primary-source document follows.

  1. Keep authenticated relay usable while gathering/punching; direct discovery is an upgrade, never a prerequisite for first traffic. UDP-blocked peers need TLS/TCP 443; an HTTP-compatible option may also be needed behind restrictive proxies. Never send plaintext mesh traffic to a relay.
  2. Preserve the actual peer-traffic socket + network generation invariant from the contract proposal. Existing Linux kernel WireGuard owns that UDP socket: a separate userspace STUN socket cannot measure its mapping. The integration seam must explicitly carry/observe WireGuard ciphertext without selecting a replacement engine in this research.
  3. Mapping and filtering are independent. Same mapping at two destinations is only an observation, not proof of endpoint-independent behavior or inbound reachability. RFC 4787 §§4.1,5: https://www.rfc-editor.org/rfc/rfc4787.html#section-4.1 . CGNAT alone does not imply failure.
  4. D196 stage 1 relays both-port-changing pairs. The birthday technique is useful to evaluate later for one hard / one stable side, not a guarantee for two hard sides. The 2020 article describes 256 hard-side sockets + random easy-side probes; do not claim it as current shipped behavior. Pinned Tailscale b83f28ebfbd9f86c2543e30e9dcf035a417dce1e has no such scheduler in inspected magicsock/portmapper/disco/netcheck. https://tailscale.com/blog/how-nat-traversal-works ; github.com/tailscale/tailscale@b83f28ebfb/wgengine/magicsock/magicsock.go (L1343-L1351) .
  5. Auto-relay: Linux + stable reachability + resource caps + same-mesh authorization. ASN/cloud hints prioritize checks, never authorize or establish reachability. Public interface==reflexive is sufficient only as a hint, not necessary: AWS public IPv4 is gateway NAT, with private interface IPv4. https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-instance-addressing.html#concepts-public-addresses . Probe IPv4 and IPv6 separately from outside.
  6. Share one D197 M5 selector with #201: prove replacement identity/path, require a sustained measurable win, direct on equivalent quality, bounded make-before-break handoff. WireGuard datagrams alone cannot promise lossless arbitrary UDP during outages; preserve reliable application offsets/ACKs and report the distinction.
  7. Do not inherit legacy research performance claims as acceptance: #219 records 1,000 configured peers with failed health guards in #201. The new DCUtR paper reports 70% ±7.1% conditional on successful relay reservation/address discovery, not 70% universal connectivity. https://arxiv.org/abs/2604.12484 .
  8. D278: no NAT controls or path-change notices. Only add-host/loading steps and the Mesh direct/relayed label. Namespace acceptance must cover independent mapping/filtering, both hard NATs, UDP denied, stale endpoints, suspend/roam, low MTU/PTB denied and relay failure, with real encrypted numbered traffic.

These are research recommendations, not changed decisions or implemented features. No foundational library selected; no runtime measurements performed.

Early research-nat design input for mesh-relay (#220); full primary-source document follows. 1. Keep authenticated relay usable while gathering/punching; direct discovery is an upgrade, never a prerequisite for first traffic. UDP-blocked peers need TLS/TCP 443; an HTTP-compatible option may also be needed behind restrictive proxies. Never send plaintext mesh traffic to a relay. 2. Preserve the actual peer-traffic socket + network generation invariant from the contract proposal. Existing Linux kernel WireGuard owns that UDP socket: a separate userspace STUN socket cannot measure its mapping. The integration seam must explicitly carry/observe WireGuard ciphertext without selecting a replacement engine in this research. 3. Mapping and filtering are independent. Same mapping at two destinations is only an observation, not proof of endpoint-independent behavior or inbound reachability. RFC 4787 §§4.1,5: https://www.rfc-editor.org/rfc/rfc4787.html#section-4.1 . CGNAT alone does not imply failure. 4. D196 stage 1 relays both-port-changing pairs. The birthday technique is useful to evaluate later for one hard / one stable side, not a guarantee for two hard sides. The 2020 article describes 256 hard-side sockets + random easy-side probes; do not claim it as current shipped behavior. Pinned Tailscale b83f28ebfbd9f86c2543e30e9dcf035a417dce1e has no such scheduler in inspected magicsock/portmapper/disco/netcheck. https://tailscale.com/blog/how-nat-traversal-works ; https://github.com/tailscale/tailscale/blob/b83f28ebfbd9f86c2543e30e9dcf035a417dce1e/wgengine/magicsock/magicsock.go#L1343-L1351 . 5. Auto-relay: Linux + stable reachability + resource caps + same-mesh authorization. ASN/cloud hints prioritize checks, never authorize or establish reachability. Public interface==reflexive is sufficient only as a hint, not necessary: AWS public IPv4 is gateway NAT, with private interface IPv4. https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-instance-addressing.html#concepts-public-addresses . Probe IPv4 and IPv6 separately from outside. 6. Share one D197 M5 selector with #201: prove replacement identity/path, require a sustained measurable win, direct on equivalent quality, bounded make-before-break handoff. WireGuard datagrams alone cannot promise lossless arbitrary UDP during outages; preserve reliable application offsets/ACKs and report the distinction. 7. Do not inherit legacy research performance claims as acceptance: #219 records 1,000 configured peers with failed health guards in #201. The new DCUtR paper reports 70% ±7.1% conditional on successful relay reservation/address discovery, not 70% universal connectivity. https://arxiv.org/abs/2604.12484 . 8. D278: no NAT controls or path-change notices. Only add-host/loading steps and the Mesh direct/relayed label. Namespace acceptance must cover independent mapping/filtering, both hard NATs, UDP denied, stale endpoints, suspend/roam, low MTU/PTB denied and relay failure, with real encrypted numbered traffic. These are research recommendations, not changed decisions or implemented features. No foundational library selected; no runtime measurements performed.
Author
Owner

Completed and pushed 1127aa19d: bounded peer-socket discovery model in crates/infraternal-mesh/src/discovery.rs. Six new tests cover mapping evidence, independent IPv4/IPv6, mapped IPv4 canonicalisation, generation retirement, exact expiry, monotonic clocks, the 16-observation cap and invalid addresses/lifetimes. Crate gate: 23 tests plus 1 compile-fail doc test passed; fmt and all-target clippy exited 0. No listener, wire change, runtime coordination or performance claim in this slice.

fmt, clippy, crate tests.

Finding: Device::counters currently discards public WGPEER_A_ENDPOINT (field 4), while node_coordination constructs an endpoint from the control socket address + local WireGuard port. Those coordinates are not a discovered NAT mapping. Next slice decodes/report kernel public endpoints with strict IPv4/IPv6 sockaddr lengths, preserving multipart metadata and existing private-buffer zeroisation. A kernel endpoint may still be configured/unproven, so it must not be labelled authenticated reflexive evidence merely because it exists. Planning an isolated functional source-port-translation/rebinding proof before coordinator use. The switch-over marker is absent; no workspace/static build, perf claim, size delta or completion claim.

Completed and pushed 1127aa19d: bounded peer-socket discovery model in crates/infraternal-mesh/src/discovery.rs. Six new tests cover mapping evidence, independent IPv4/IPv6, mapped IPv4 canonicalisation, generation retirement, exact expiry, monotonic clocks, the 16-observation cap and invalid addresses/lifetimes. Crate gate: 23 tests plus 1 compile-fail doc test passed; fmt and all-target clippy exited 0. No listener, wire change, runtime coordination or performance claim in this slice. [fmt](https://git.kayg.org/attachments/160c18f8-c090-4b8e-baa7-cd49ec87e8ef), [clippy](https://git.kayg.org/attachments/a7a1ec60-806e-488a-a00e-41da47399274), [crate tests](https://git.kayg.org/attachments/107689db-01d8-43b5-8449-cc1d9b2e856f). Finding: Device::counters currently discards public WGPEER_A_ENDPOINT (field 4), while node_coordination constructs an endpoint from the control socket address + local WireGuard port. Those coordinates are not a discovered NAT mapping. Next slice decodes/report kernel public endpoints with strict IPv4/IPv6 sockaddr lengths, preserving multipart metadata and existing private-buffer zeroisation. A kernel endpoint may still be configured/unproven, so it must not be labelled authenticated reflexive evidence merely because it exists. Planning an isolated functional source-port-translation/rebinding proof before coordinator use. The switch-over marker is absent; no workspace/static build, perf claim, size delta or completion claim.
Author
Owner

Step 1 committed/pushed: 0f54e86f64, docs-only design. 524 lines, 21 resolved source definitions; git diff --check and cargo fmt --all --check pass. No Rust/TypeScript code touched, so touched-crate tests/clippy and TypeScript checks are inapplicable. Final full gate remains pending.

Design includes exact-socket/generation discovery, bounded 50ms checklist pacing and retry budgets (proposals), concurrent ciphertext relay and direct proof, D197 M5 hysteresis, explicit D196 hard-NAT stage-one boundary, PCP renewal, verified family/transport-specific auto-relay eligibility and finite resource limits. Handoff preserves reliable application offsets; arbitrary UDP losslessness is not claimed. Follow-up source comparison, measurement denominators, library candidates and namespace acceptance matrix are being added.

Finding: PCP renewal is randomized at 1/2–5/8 returned lifetime (RFC6887 §11.2.1), not a universally fixed half-life. NetBird config says 4s keepalive, but its pinned ICE fork task-loop inspection implies ~2s; this is source inference, not a measured runtime rate. A build-slot-wrapped baseline static build is queued for same-recipe binary comparison; no benchmark host or metadata endpoint used.

Step 1 committed/pushed: 0f54e86f64eb167ef44815198627a846d56d9d14, docs-only [design](https://git.kayg.org/kayg/infraternal/src/branch/research-nat/research/nat-traversal/README.md). 524 lines, 21 resolved source definitions; git diff --check and cargo fmt --all --check pass. No Rust/TypeScript code touched, so touched-crate tests/clippy and TypeScript checks are inapplicable. Final full gate remains pending. Design includes exact-socket/generation discovery, bounded 50ms checklist pacing and retry budgets (proposals), concurrent ciphertext relay and direct proof, D197 M5 hysteresis, explicit D196 hard-NAT stage-one boundary, PCP renewal, verified family/transport-specific auto-relay eligibility and finite resource limits. Handoff preserves reliable application offsets; arbitrary UDP losslessness is not claimed. Follow-up source comparison, measurement denominators, library candidates and namespace acceptance matrix are being added. Finding: PCP renewal is randomized at 1/2–5/8 returned lifetime (RFC6887 §11.2.1), not a universally fixed half-life. NetBird config says 4s keepalive, but its pinned ICE fork task-loop inspection implies ~2s; this is source inference, not a measured runtime rate. A build-slot-wrapped baseline static build is queued for same-recipe binary comparison; no benchmark host or metadata endpoint used.
Author
Owner

Completed and pushed 176aca846: contracts/mesh-discovery.md records socket/generation binding, monotonic expiry, bounded observations, address-family isolation, NAT-evidence limits and configured-vs-authenticated kernel endpoints. Keeps #201 selection and observer/route leases authoritative; no shared selector interface changed. Diff/format checks passed.

Current uncommitted next slice: strict std-only Linux sockaddr decoder + optional public Counters.endpoint, the existing isolated mesh robustness harness extended with that exact decoder, and scripts/mesh-endpoint-proof.py using the existing namespace helpers plus a kernel_node rebind fixture command. Standalone decoder tests: 2 passed, including truncation and unknown family/size combinations. Disposable user/network namespace checks confirmed WireGuard creation, nftables and forwarding are available. These are functional capability checks, not traversal or performance acceptance.

Touched-plugin clippy/tests have been queued through build-slot since the earlier step; endpoint-clippy.log is still empty (no build started). After that gate: build the single-crate kernel_node fixture and run the real translated-port/rebinding proof. Full scripts/verify fast, static size comparison, relay runtime/TCP443, coordinator integration, the six NAT profiles and perf-VM measurements remain outstanding. The required switch-over marker is still absent; no workspace/static build started. #201 has been notified about the additive endpoint field.

Completed and pushed 176aca846: contracts/mesh-discovery.md records socket/generation binding, monotonic expiry, bounded observations, address-family isolation, NAT-evidence limits and configured-vs-authenticated kernel endpoints. Keeps #201 selection and observer/route leases authoritative; no shared selector interface changed. Diff/format checks passed. Current uncommitted next slice: strict std-only Linux sockaddr decoder + optional public Counters.endpoint, the existing isolated mesh robustness harness extended with that exact decoder, and scripts/mesh-endpoint-proof.py using the existing namespace helpers plus a kernel_node rebind fixture command. Standalone decoder tests: 2 passed, including truncation and unknown family/size combinations. Disposable user/network namespace checks confirmed WireGuard creation, nftables and forwarding are available. These are functional capability checks, not traversal or performance acceptance. Touched-plugin clippy/tests have been queued through build-slot since the earlier step; endpoint-clippy.log is still empty (no build started). After that gate: build the single-crate kernel_node fixture and run the real translated-port/rebinding proof. Full scripts/verify fast, static size comparison, relay runtime/TCP443, coordinator integration, the six NAT profiles and perf-VM measurements remain outstanding. The required switch-over marker is still absent; no workspace/static build started. #201 has been notified about the additive endpoint field.
Author
Owner

Step 2: source comparison and owner candidate inventory committed on research-nat; current head is recorded below. Document now 769 lines /69 resolved source definitions. cargo fmt --all --check and git diff --check pass. No product/dependency changes. Full gate pending behind build-slot baseline comparison.

Findings now documented with pinned commits/functions/sections: Tailscale heartbeat3s/trust6.5s/idle45s are distinct timers; direct scoring includes non-RTT preferences and DERP home hysteresis. NetBird uses a pinned ICE fork, native ICE/relay race, then QUIC DATAGRAM/WSS race; not direct ICE-TCP. Studies retain 2005 router-report, 2022 volunteer, December2022 DCUtR and May2026 ProbeLab cohorts separately. Proposed native checker is not an ICE/RFC7675 compliance claim. Engine choices stay open; no comparable native ICE/relay RSS or throughput was found, and unknown cost is explicit.
3d98fc4801

Step 2: source comparison and owner candidate inventory committed on research-nat; current head is recorded below. Document now 769 lines /69 resolved source definitions. cargo fmt --all --check and git diff --check pass. No product/dependency changes. Full gate pending behind build-slot baseline comparison. Findings now documented with pinned commits/functions/sections: Tailscale heartbeat3s/trust6.5s/idle45s are distinct timers; direct scoring includes non-RTT preferences and DERP home hysteresis. NetBird uses a pinned ICE fork, native ICE/relay race, then QUIC DATAGRAM/WSS race; not direct ICE-TCP. Studies retain 2005 router-report, 2022 volunteer, December2022 DCUtR and May2026 ProbeLab cohorts separately. Proposed native checker is not an ICE/RFC7675 compliance claim. Engine choices stay open; no comparable native ICE/relay RSS or throughput was found, and unknown cost is explicit. 3d98fc4801e9fef2c4bd2bd9edcdf5c883bbdd52
Author
Owner

Relay integration findings before transport wiring: production host leaves are clientAuth-only (crates/infraternal-server/src/ca.rs:239), while existing QUIC/TLS listeners correctly require server authentication/name verification (crates/plugins/remote/src/remote_quic.rs server_config/client_config; remote_tls.rs server_config/client_config). A standalone node relay therefore needs a properly issued server-purpose identity through the existing CA/role enrollment; reusing a host client leaf as a server or relaxing certificate purpose/name checks would be incorrect. The server already has its separate server-purpose leaf (ca_bootstrap.rs:239). No verification bypass or new crypto engine is being introduced.

The TCP/TLS 443 listener also has to respect an existing HTTPS binding: server HTTPS bind and remote transport listeners are separate owners today (runtime.rs:305,420). Relay registration should remain a generic removable plugin capability, rather than a relay-specific core listener. These are implementation requirements for the remaining runtime, not evidence that peer relay exists. Full static growth cannot be measured while the switch-over marker forbids workspace builds; it remains unmeasured, with the >16 KB justification and >64 KB owner limits unchanged.

Relay integration findings before transport wiring: production host leaves are clientAuth-only (crates/infraternal-server/src/ca.rs:239), while existing QUIC/TLS listeners correctly require server authentication/name verification (crates/plugins/remote/src/remote_quic.rs server_config/client_config; remote_tls.rs server_config/client_config). A standalone node relay therefore needs a properly issued server-purpose identity through the existing CA/role enrollment; reusing a host client leaf as a server or relaxing certificate purpose/name checks would be incorrect. The server already has its separate server-purpose leaf (ca_bootstrap.rs:239). No verification bypass or new crypto engine is being introduced. The TCP/TLS 443 listener also has to respect an existing HTTPS binding: server HTTPS bind and remote transport listeners are separate owners today (runtime.rs:305,420). Relay registration should remain a generic removable plugin capability, rather than a relay-specific core listener. These are implementation requirements for the remaining runtime, not evidence that peer relay exists. Full static growth cannot be measured while the switch-over marker forbids workspace builds; it remains unmeasured, with the >16 KB justification and >64 KB owner limits unchanged.
Author
Owner

Steps 3/4 committed and pushed: baa9e6c989 (matrix/acceptance) then 13b800213d (review clarifications). Full design at head: 1,054 lines, 71 sources, 81 ordered mapping/filtering combinations plus special cases, explicit proposed budgets, scale arithmetic, traps, ten implementation rules and owner questions.

Findings incorporated: D166 protects reconnect/new permitted channels among already-known members during coordinator outage; directional inner initiation still needs bidirectional encrypted replies; a proved direct path can win before relay. Hard quota exhaustion explicitly pauses/errors with retained offsets if no alternative exists. Four separated measurement segments are not called a continuous soak. All source/statistical/numerical claims separate inspected constants, published evidence and proposals.

Validation: cargo fmt --all --check, git diff --check and document checks pass; 43 GitHub blob references/28 pinned files fetched with valid line bounds, zero errors. Source checks. Author review follows mesh transport/data-plane checklist; independent source/eligibility review corrections applied. Full final gate and same-recipe static size comparison remain pending behind the host build-slot queue. No runtime measurements, new libraries, code or UI changes.

Steps 3/4 committed and pushed: baa9e6c98972295d46d9a81fd41ad16486bf1648 (matrix/acceptance) then 13b800213d5e430470bf02941db0b29f6f42f058 (review clarifications). Full [design at head](https://git.kayg.org/kayg/infraternal/src/commit/13b800213d5e430470bf02941db0b29f6f42f058/research/nat-traversal/README.md): 1,054 lines, 71 sources, 81 ordered mapping/filtering combinations plus special cases, explicit proposed budgets, scale arithmetic, traps, ten implementation rules and owner questions. Findings incorporated: D166 protects reconnect/new permitted channels among already-known members during coordinator outage; directional inner initiation still needs bidirectional encrypted replies; a proved direct path can win before relay. Hard quota exhaustion explicitly pauses/errors with retained offsets if no alternative exists. Four separated measurement segments are not called a continuous soak. All source/statistical/numerical claims separate inspected constants, published evidence and proposals. Validation: cargo fmt --all --check, git diff --check and document checks pass; 43 GitHub blob references/28 pinned files fetched with valid line bounds, zero errors. [Source checks](https://git.kayg.org/attachments/7039dbb0-52cd-48ae-adce-41e69a5d3f09). [Author review](https://git.kayg.org/attachments/b4999100-4b65-4ba5-9c45-f0ed1df77191) follows mesh transport/data-plane checklist; independent source/eligibility review corrections applied. Full final gate and same-recipe static size comparison remain pending behind the host build-slot queue. No runtime measurements, new libraries, code or UI changes.
Author
Owner

INCOMPLETE checkpoint at pushed head 176aca8461. Committed: 1127aa19d discovery model; 176aca846 discovery contract. Their crate gate passed 23 tests + 1 compile-fail doc test, fmt and all-target clippy (gate log).

Uncommitted endpoint slice is preserved in the worktree and this complete patch, SHA256 8c838d59a776099c003bbee8fbea2093dc8f10b5795e007779736f0f6c589259. Standalone production decoder tests passed 2/2; root and isolated-harness fmt checks passed; NAT rule installation/replacement passed in an owned disposable namespace. Full translated-port/rebinding proof is written but NOT RUN.

Blocked validation: all four build slots stayed occupied. The queued touched-plugin clippy never started; stopped only our queue wrapper 699977 and its wait child 977910. Python 699971 also exited; ps showed all three gone. No daemon/server or namespace process remains. Own target directory removed; evidence retained. Scheduling/cleanup receipt. Orchestrator/build capacity is needed for the next gate; the switch-over orchestrator must publish switchover-merged before rebase/full builds.

Resume: run slot-wrapped mesh-plugin all-target clippy and library tests with --no-default-features --features agent; build its kernel_node example and run unshare -Urn python3 scripts/mesh-endpoint-proof.py --fixture . Check/run the existing isolated crates/infraternal-mesh/fuzz harness; its new path module compiles the exact std-only production decoder. Split parser/counter, harness and proof commits after their gates; notify #201 of the committed optional Counters.endpoint field.

Still required: actual coordinator discovery/simultaneous open; removable relay runtime and proper relay identities; TCP/TLS443; sustained direct recovery with #201; all six NAT profiles; perf-VM measurements at scale; merge-base stripped-static size delta; final scripts/verify fast. The required switch-over marker remains absent. scripts/verify fast was NOT RUN (prohibited by brief), so there is no final fast output to quote and no completion/size/performance acceptance claim. Do not close this issue or treat this checkpoint as the complete #220 implementation.

INCOMPLETE checkpoint at pushed head 176aca846185db0eaea00c3ab84edf0ccda54803. Committed: 1127aa19d discovery model; 176aca846 discovery contract. Their crate gate passed 23 tests + 1 compile-fail doc test, fmt and all-target clippy ([gate log](https://git.kayg.org/attachments/107689db-01d8-43b5-8449-cc1d9b2e856f)). Uncommitted endpoint slice is preserved in the worktree and this [complete patch](https://git.kayg.org/attachments/cc3c255f-5f4a-4bf6-b096-a79ff388c4f8), SHA256 8c838d59a776099c003bbee8fbea2093dc8f10b5795e007779736f0f6c589259. [Standalone production decoder tests](https://git.kayg.org/attachments/d683ea9b-4e3d-4610-9699-c98fd465c89f) passed 2/2; root and isolated-harness fmt checks passed; NAT rule installation/replacement passed in an owned disposable namespace. Full translated-port/rebinding proof is written but NOT RUN. Blocked validation: all four build slots stayed occupied. The queued touched-plugin clippy never started; stopped only our queue wrapper 699977 and its wait child 977910. Python 699971 also exited; ps showed all three gone. No daemon/server or namespace process remains. Own target directory removed; evidence retained. [Scheduling/cleanup receipt](https://git.kayg.org/attachments/947f7b2e-1e3f-4f4e-9651-0dee9fddf52c). Orchestrator/build capacity is needed for the next gate; the switch-over orchestrator must publish switchover-merged before rebase/full builds. Resume: run slot-wrapped mesh-plugin all-target clippy and library tests with --no-default-features --features agent; build its kernel_node example and run unshare -Urn python3 scripts/mesh-endpoint-proof.py --fixture <absolute example>. Check/run the existing isolated crates/infraternal-mesh/fuzz harness; its new path module compiles the exact std-only production decoder. Split parser/counter, harness and proof commits after their gates; notify #201 of the committed optional Counters.endpoint field. Still required: actual coordinator discovery/simultaneous open; removable relay runtime and proper relay identities; TCP/TLS443; sustained direct recovery with #201; all six NAT profiles; perf-VM measurements at scale; merge-base stripped-static size delta; final scripts/verify fast. The required switch-over marker remains absent. scripts/verify fast was NOT RUN (prohibited by brief), so there is no final fast output to quote and no completion/size/performance acceptance claim. Do not close this issue or treat this checkpoint as the complete #220 implementation.
Author
Owner

Verification scheduling blocker at 2026-10-15 08:58 UTC: static comparison has waited since 08:28; all four host build slots are occupied by active wrappers (size-ratchet-r2-review since08:13, idle-cpu-199 since08:27, terminal-links since08:01, unnamed fast gate since08:00). Research, review and all four documentation commits are complete/pushed at13b800213d5e430470bf02941db0b29f6f42f058. Full design is available to mesh-relay now.

Stopped only this job's queued wrapper714456/sleep1074281 (no build child had started) and queued one combined reservation: merge-base-equivalent static recipe, then branch-head scripts/verify fast, then byte/hash comparison. Only README differs from merge base; build scripts do not stamp Git identity. This avoids a second reservation wait. Need a normal free build slot; continuing to wait, no scheduling limits bypassed and no other process changed. No benchmark or remote host used.

Verification scheduling blocker at 2026-10-15 08:58 UTC: static comparison has waited since 08:28; all four host build slots are occupied by active wrappers (size-ratchet-r2-review since08:13, idle-cpu-199 since08:27, terminal-links since08:01, unnamed fast gate since08:00). Research, review and all four documentation commits are complete/pushed at13b800213d5e430470bf02941db0b29f6f42f058. Full [design](https://git.kayg.org/kayg/infraternal/src/commit/13b800213d5e430470bf02941db0b29f6f42f058/research/nat-traversal/README.md) is available to mesh-relay now. Stopped only this job's queued wrapper714456/sleep1074281 (no build child had started) and queued one combined reservation: merge-base-equivalent static recipe, then branch-head scripts/verify fast, then byte/hash comparison. Only README differs from merge base; build scripts do not stamp Git identity. This avoids a second reservation wait. Need a normal free build slot; continuing to wait, no scheduling limits bypassed and no other process changed. No benchmark or remote host used.
Author
Owner

Step 5 committed/pushed bdb4e372b2: incorporated mesh-relay's identity/listener findings after verifying ca.rs:239 (node client-auth only), ca_bootstrap.rs:239 (server-purpose leaf), and runtime.rs:305/420 (separate HTTPS/remote listener owners) at our baseline. Proper relay-purpose issuance/name/renewal and generic TLS443 listener sharing are explicit prerequisites; no certificate-purpose/name bypass or relay-specific core wiring. Added wrong-purpose/name/expiry and occupied443 acceptance cases. No code/contract/decision change.

Current full design:1,081 lines/75 resolved sources. cargo fmt --all --check, git diff --check and document invariants pass. Final static comparison + scripts/verify fast reservation still waiting; it will read/run this head. This update also acknowledges mesh-relay's incomplete checkpoint; no relay runtime completion is implied.

Step 5 committed/pushed bdb4e372b2ecf6a89cea6da61c76ffb1677a69f1: incorporated mesh-relay's identity/listener findings after verifying ca.rs:239 (node client-auth only), ca_bootstrap.rs:239 (server-purpose leaf), and runtime.rs:305/420 (separate HTTPS/remote listener owners) at our baseline. Proper relay-purpose issuance/name/renewal and generic TLS443 listener sharing are explicit prerequisites; no certificate-purpose/name bypass or relay-specific core wiring. Added wrong-purpose/name/expiry and occupied443 acceptance cases. No code/contract/decision change. [Current full design](https://git.kayg.org/kayg/infraternal/src/commit/bdb4e372b2ecf6a89cea6da61c76ffb1677a69f1/research/nat-traversal/README.md):1,081 lines/75 resolved sources. cargo fmt --all --check, git diff --check and document invariants pass. Final static comparison + scripts/verify fast reservation still waiting; it will read/run this head. This update also acknowledges mesh-relay's incomplete checkpoint; no relay runtime completion is implied.
Author
Owner

2026-10-15 09:14 UTC scheduling update: original static request has waited about45min; combined baseline+final-gate reservation remains queued. Slot1 changed from size-ratchet review to another fast gate at09:04; slot4 changed to size-ratchet-r2-review-retry at09:07. Slots2/3 still show idle-cpu-199/terminal-links. No research-nat compile/test has started; both build transcript files remain empty. The normal wrapper polls slots every20s without a FIFO queue, so slot turnover does not imply this waiter acquires one. No scheduling code/limit/other process was changed. Research and source review are pushed atbdb4e372b2ecf6a89cea6da61c76ffb1677a69f1; continuing to wait for required final validation.

2026-10-15 09:14 UTC scheduling update: original static request has waited about45min; combined baseline+final-gate reservation remains queued. Slot1 changed from size-ratchet review to another fast gate at09:04; slot4 changed to size-ratchet-r2-review-retry at09:07. Slots2/3 still show idle-cpu-199/terminal-links. No research-nat compile/test has started; both build transcript files remain empty. The normal wrapper polls slots every20s without a FIFO queue, so slot turnover does not imply this waiter acquires one. No scheduling code/limit/other process was changed. Research and source review are pushed atbdb4e372b2ecf6a89cea6da61c76ffb1677a69f1; continuing to wait for required final validation.
Author
Owner

2026-10-15 09:28 UTC: original verification request has waited about1h; combined comparison+final-gate reservation still has not acquired a slot (empty verification-session.log and baseline-static-build.log). Current pushed research head bdb4e372b2. Document/source/author checks pass; full fast gate and static size remain unrun/unmeasured. Continuing normal build-slot wait; no capacity limit bypass or other process changes. Resume command/provenance script is /home/kayg/.cache/codex-scratch/research-nat/verify-sequence.sh under the same wrapper, CARGO_TARGET_DIR research-nat/target and TRACER_TEST_PORT4320. No product code changes or runtime acceptance claims.

2026-10-15 09:28 UTC: original verification request has waited about1h; combined comparison+final-gate reservation still has not acquired a slot (empty verification-session.log and baseline-static-build.log). Current pushed research head bdb4e372b2ecf6a89cea6da61c76ffb1677a69f1. Document/source/author checks pass; full fast gate and static size remain unrun/unmeasured. Continuing normal build-slot wait; no capacity limit bypass or other process changes. Resume command/provenance script is /home/kayg/.cache/codex-scratch/research-nat/verify-sequence.sh under the same wrapper, CARGO_TARGET_DIR research-nat/target and TRACER_TEST_PORT4320. No product code changes or runtime acceptance claims.
Author
Owner

Step6 committed/pushed41531643151c5fd1320016b222d717ebf28e4bef: clarified D278's invisible normal flow without deleting D196's per-node mapping override; no NAT-type choice/additional traversal step is introduced. Local/reflexive equality explicitly compares IP:port, not just IP. No decision or code change. Document checks pass at1,083lines/75source definitions; fmt and diff checks pass. Full design. Combined final verification remains queued and will use this head; no gate/size result yet.

Step6 committed/pushed41531643151c5fd1320016b222d717ebf28e4bef: clarified D278's invisible normal flow without deleting D196's per-node mapping override; no NAT-type choice/additional traversal step is introduced. Local/reflexive equality explicitly compares IP:port, not just IP. No decision or code change. Document checks pass at1,083lines/75source definitions; fmt and diff checks pass. Full [design](https://git.kayg.org/kayg/infraternal/src/commit/41531643151c5fd1320016b222d717ebf28e4bef/research/nat-traversal/README.md). Combined final verification remains queued and will use this head; no gate/size result yet.
Author
Owner

Verification reservation acquired; comparison build is now compiling. Script confirms head41531643151c5fd1320016b222d717ebf28e4bef, merge basee972f91dad67ac1f1274a4e4cd70df24ee052bc1 and only research/nat-traversal/README.md differs. Document invariants pass:1,083lines/75sources, all labels/local links/role/license/wording/ten rules/scope/arithmetic checked. Same reservation runs the baseline-equivalent static recipe, final branch-head scripts/verify fast, and binary byte/hash comparison sequentially. These are build/functional checks, not NAT performance measurements. Original request08:28UTC; actual slot time is in wrapper provenance. No code/dependency/decision changes.

Verification reservation acquired; comparison build is now compiling. Script confirms head41531643151c5fd1320016b222d717ebf28e4bef, merge basee972f91dad67ac1f1274a4e4cd70df24ee052bc1 and only research/nat-traversal/README.md differs. Document invariants pass:1,083lines/75sources, all labels/local links/role/license/wording/ten rules/scope/arithmetic checked. Same reservation runs the baseline-equivalent static recipe, final branch-head scripts/verify fast, and binary byte/hash comparison sequentially. These are build/functional checks, not NAT performance measurements. Original request08:28UTC; actual slot time is in wrapper provenance. No code/dependency/decision changes.
Author
Owner

Static comparison build completed at head 4153164315. Merge-base compiler inputs are identical (only the research Markdown differs; no Git identity stamping). Exact shipping recipe produces a stripped static x86_64 binary of 22,277,392 bytes; SHA256 fce0032db0b4f651b2c5055ab649182e53cbd6ad1bcec8f31f0dfdba626bcf77. Configured ceiling in scripts/verify.d/rust.sh is 22,204,304 bytes, so unchanged inputs already exceed it by 73,088 bytes. This is not a measured branch delta yet; the final-head rebuild/comparison follows.

scripts/verify fast is now running in the same build-slot reservation. The research job will not change the ceiling or product code to conceal the baseline mismatch. Full gate output and final byte/hash comparison will be attached after completion. No NAT runtime/performance measurement or new dependency is involved.

Static comparison build completed at head 41531643151c5fd1320016b222d717ebf28e4bef. Merge-base compiler inputs are identical (only the research Markdown differs; no Git identity stamping). Exact shipping recipe produces a stripped static x86_64 binary of 22,277,392 bytes; SHA256 fce0032db0b4f651b2c5055ab649182e53cbd6ad1bcec8f31f0dfdba626bcf77. Configured ceiling in scripts/verify.d/rust.sh is 22,204,304 bytes, so unchanged inputs already exceed it by 73,088 bytes. This is not a measured branch delta yet; the final-head rebuild/comparison follows. scripts/verify fast is now running in the same build-slot reservation. The research job will not change the ceiling or product code to conceal the baseline mismatch. Full gate output and final byte/hash comparison will be attached after completion. No NAT runtime/performance measurement or new dependency is involved.
Author
Owner

First fast attempt found a gate-launch configuration error, not a product assertion failure. The agent_daemon integration test stopped before starting its fixture because I set INFRATERNAL_TEST_STATE_ROOT to the 50-byte nested research-nat/state path. Verbatim failure:

ValueError: test state path is too long for an AF_UNIX socket: 111 bytes

scripts/agent_fixture.py:41–56 explicitly enforces <108 bytes including the longer upstream socket. Corrected setting is the 44-byte task scratch root /home/kayg/.cache/codex-scratch/research-nat itself; helper-only checks for tb/ts/vc/mw/r99 generate 105–106-byte paths and pass without launching a daemon. The original full gate is finishing static checks; after it exits I will rerun scripts/verify fast with this setting. No code, test threshold, host fixture or size ceiling changed. Head remains 4153164315. Both attempts will be preserved and the final result reported explicitly.

First fast attempt found a gate-launch configuration error, not a product assertion failure. The agent_daemon integration test stopped before starting its fixture because I set INFRATERNAL_TEST_STATE_ROOT to the 50-byte nested research-nat/state path. Verbatim failure: ``` ValueError: test state path is too long for an AF_UNIX socket: 111 bytes ``` scripts/agent_fixture.py:41–56 explicitly enforces <108 bytes including the longer upstream socket. Corrected setting is the 44-byte task scratch root /home/kayg/.cache/codex-scratch/research-nat itself; helper-only checks for tb/ts/vc/mw/r99 generate 105–106-byte paths and pass without launching a daemon. The original full gate is finishing static checks; after it exits I will rerun scripts/verify fast with this setting. No code, test threshold, host fixture or size ceiling changed. Head remains 41531643151c5fd1320016b222d717ebf28e4bef. Both attempts will be preserved and the final result reported explicitly.
Author
Owner

First gate attempt finished at head 4153164315. Stripped x86_64 comparison: 22,277,392 bytes on both observations, delta 0 bytes, identical SHA256 fce0032db0b4f651b2c5055ab649182e53cbd6ad1bcec8f31f0dfdba626bcf77. Baseline compiler-input receipt; head receipt. Only the research Markdown differs from the merge base.

Initial test row failed before fixture startup because of my too-long state root (111-byte socket path). The corrected root passes the harness guard at 105–106 bytes; a full corrected gate is queued. No source or test threshold was changed. Static checks also confirm unchanged absolute ceiling mismatches: x86_64 22,277,392 vs22,204,304 (+73,088); aarch64 19,705,112 vs19,638,744 (+66,368). No ceiling was raised. These are artifact sizes, not NAT performance measurements.

Initial Rust summary, verbatim (not the final corrected result):

+---------------------------+--------------+
| Rust gate                 | Result       |
+---------------------------+--------------+
| fmt                       | PASS         |
| VT native fmt             | PASS         |
| CLI plugin compile-out    | PASS         |
| clippy                    | PASS         |
| test                      | FAIL (101)   |
| cargo-deny                | PASS         |
| static builds             | FAIL (1)     |
+---------------------------+--------------+

Complete initial gate transcript. The first transcript and receipts are preserved. Corrected gate is pending normal build capacity; validation is not claimed complete.

First gate attempt finished at head 41531643151c5fd1320016b222d717ebf28e4bef. Stripped x86_64 comparison: 22,277,392 bytes on both observations, delta 0 bytes, identical SHA256 fce0032db0b4f651b2c5055ab649182e53cbd6ad1bcec8f31f0dfdba626bcf77. [Baseline compiler-input receipt](https://git.kayg.org/attachments/b0bfd002-cd16-4056-ae82-969f91d53682); [head receipt](https://git.kayg.org/attachments/97059513-dd1b-465f-bf69-08d5dce4caab). Only the research Markdown differs from the merge base. Initial test row failed before fixture startup because of my too-long state root (111-byte socket path). The corrected root passes the harness guard at 105–106 bytes; a full corrected gate is queued. No source or test threshold was changed. Static checks also confirm unchanged absolute ceiling mismatches: x86_64 22,277,392 vs22,204,304 (+73,088); aarch64 19,705,112 vs19,638,744 (+66,368). No ceiling was raised. These are artifact sizes, not NAT performance measurements. Initial Rust summary, verbatim (not the final corrected result): ``` +---------------------------+--------------+ | Rust gate | Result | +---------------------------+--------------+ | fmt | PASS | | VT native fmt | PASS | | CLI plugin compile-out | PASS | | clippy | PASS | | test | FAIL (101) | | cargo-deny | PASS | | static builds | FAIL (1) | +---------------------------+--------------+ ``` [Complete initial gate transcript](https://git.kayg.org/attachments/617b8f86-e54a-458f-824b-30768547a46e). The first transcript and receipts are preserved. Corrected gate is pending normal build capacity; validation is not claimed complete.
Author
Owner

Verification status: corrected final scripts/verify fast has been queued behind the host build-slot wrapper since 10:38:55 UTC (about 30 min). Head remains 41531643151c5fd1320016b222d717ebf28e4bef, pushed, docs-only. No second slot command started. Initial full-gate transcript and zero-byte x86_64 comparison are attached above; corrected fixture root is checked and waiting. No product change or ceiling change is proposed.

Verification status: corrected final `scripts/verify fast` has been queued behind the host build-slot wrapper since 10:38:55 UTC (about 30 min). Head remains `41531643151c5fd1320016b222d717ebf28e4bef`, pushed, docs-only. No second slot command started. Initial full-gate transcript and zero-byte x86_64 comparison are attached above; corrected fixture root is checked and waiting. No product change or ceiling change is proposed.
Author
Owner

Corrected final gate remains queued about 45 min since 10:38:55 UTC. No gate process has started for this retry; head 41531643151c5fd1320016b222d717ebf28e4bef remains pushed and unchanged. Current regular-slot owners at 2026-10-15T11:23:42.119842+00:00:

2026-10-15T13:03:39+02:00 slot 1: size-ratchet-r2-review-test-alt-root pid 1799465: cargo test --workspace --locked
2026-10-15T13:08:49+02:00 slot 2: terminal-links-review pid 2046126: scripts/verify fast
2026-10-15T13:11:24+02:00 slot 3: ? pid 2143340: env JOB=mesh-autoswitch CARGO_BUILD_JOBS=2 CARGO_TARGET_DIR=/home/kayg/.cache/codex-scratch/mesh-autoswitch/target INFRATERNAL_TEST_STATE_ROOT=/home/kayg/.cache/codex-scratch/m201r14 RUST_TEST_THREADS=1 sh /home/kayg/.cache/codex-scratch/mesh-autoswitch/r15/build-retire-f-continue.sh
2026-10-15T13:03:13+02:00 slot 4: review-process-per-user pid 2047091: systemd-run --user --scope -p CPUWeight=10000 bash /home/kayg/.cache/codex-scratch/process-per-user-review/combined-checks.sh

Continuing to wait through the required wrapper. Research handoff, first gate receipt, corrected fixture-root explanation and zero-byte size comparison are already above. No other job was interrupted.

Corrected final gate remains queued about 45 min since 10:38:55 UTC. No gate process has started for this retry; head `41531643151c5fd1320016b222d717ebf28e4bef` remains pushed and unchanged. Current regular-slot owners at 2026-10-15T11:23:42.119842+00:00: ```text 2026-10-15T13:03:39+02:00 slot 1: size-ratchet-r2-review-test-alt-root pid 1799465: cargo test --workspace --locked 2026-10-15T13:08:49+02:00 slot 2: terminal-links-review pid 2046126: scripts/verify fast 2026-10-15T13:11:24+02:00 slot 3: ? pid 2143340: env JOB=mesh-autoswitch CARGO_BUILD_JOBS=2 CARGO_TARGET_DIR=/home/kayg/.cache/codex-scratch/mesh-autoswitch/target INFRATERNAL_TEST_STATE_ROOT=/home/kayg/.cache/codex-scratch/m201r14 RUST_TEST_THREADS=1 sh /home/kayg/.cache/codex-scratch/mesh-autoswitch/r15/build-retire-f-continue.sh 2026-10-15T13:03:13+02:00 slot 4: review-process-per-user pid 2047091: systemd-run --user --scope -p CPUWeight=10000 bash /home/kayg/.cache/codex-scratch/process-per-user-review/combined-checks.sh ``` Continuing to wait through the required wrapper. Research handoff, first gate receipt, corrected fixture-root explanation and zero-byte size comparison are already above. No other job was interrupted.
Author
Owner

Corrected final gate has waited about one hour since 10:38:55 UTC and has not acquired a build slot. Research remains pushed at 41531643151c5fd1320016b222d717ebf28e4bef; no further changes needed. Current regular-slot records at 2026-10-15T11:38:56.191446+00:00:

2026-10-15T13:03:39+02:00 slot 1: size-ratchet-r2-review-test-alt-root pid 1799465: cargo test --workspace --locked
2026-10-15T13:08:49+02:00 slot 2: terminal-links-review pid 2046126: scripts/verify fast
2026-10-15T13:32:23+02:00 slot 3: ? pid 2317626: /home/kayg/.cache/codex-scratch/designer-version-pill/serve-loop.sh
2026-10-15T13:34:49+02:00 slot 4: ? pid 2216793: bash -o pipefail -c scripts/verify fast 2>&1 | tee /home/kayg/.cache/codex-scratch/research-vnc/evidence/verify-fast.log

Continuing to wait through the wrapper; no other processes touched and no parallel build from this job.

Corrected final gate has waited about one hour since 10:38:55 UTC and has not acquired a build slot. Research remains pushed at `41531643151c5fd1320016b222d717ebf28e4bef`; no further changes needed. Current regular-slot records at 2026-10-15T11:38:56.191446+00:00: ```text 2026-10-15T13:03:39+02:00 slot 1: size-ratchet-r2-review-test-alt-root pid 1799465: cargo test --workspace --locked 2026-10-15T13:08:49+02:00 slot 2: terminal-links-review pid 2046126: scripts/verify fast 2026-10-15T13:32:23+02:00 slot 3: ? pid 2317626: /home/kayg/.cache/codex-scratch/designer-version-pill/serve-loop.sh 2026-10-15T13:34:49+02:00 slot 4: ? pid 2216793: bash -o pipefail -c scripts/verify fast 2>&1 | tee /home/kayg/.cache/codex-scratch/research-vnc/evidence/verify-fast.log ``` Continuing to wait through the wrapper; no other processes touched and no parallel build from this job.
Author
Owner

Corrected final scripts/verify fast acquired a build slot at about 11:58 UTC after roughly 79 min queued. Head 41531643151c5fd1320016b222d717ebf28e4bef; validated scratch fixture root /home/kayg/.cache/codex-scratch/research-nat. Document checks passed (1,083 lines; 75/75 reference labels). CLI unit tests: 76 passed, 1 ignored; now running daemon integration with the corrected path. No code or test threshold changes.

Corrected final `scripts/verify fast` acquired a build slot at about 11:58 UTC after roughly 79 min queued. Head `41531643151c5fd1320016b222d717ebf28e4bef`; validated scratch fixture root `/home/kayg/.cache/codex-scratch/research-nat`. Document checks passed (1,083 lines; 75/75 reference labels). CLI unit tests: 76 passed, 1 ignored; now running daemon integration with the corrected path. No code or test threshold changes.
Author
Owner

Corrected state-root full gate completed at head 41531643151c5fd1320016b222d717ebf28e4bef, exit 1. The daemon integration and vt_credit passed. Rust tests then stopped at daemon::gone_tests::a_vanished_session_gets_a_final_close_and_the_channel_stays_up: daemon_gone_tests.rs:111 reports path must be shorter than SUN_LEN. This separate Rust fixture uses TMPDIR plus 25-character random directory plus sessions/32-hex.sock: 121 bytes under the normal job/tmp path, versus Linux maximum 107. A temporary short alias /home/kayg/.cache/codex-scratch/rn points only to this job's tmp directory and yields 107 bytes; it will be removed during cleanup. No test or product code changed. Single exact-test confirmation is running before the remaining workspace gate. Static size remains 22,277,392 bytes x86_64, SHA unchanged, delta 0; ceilings still reject x86_64 (+73,088) and ARM (+66,368).

Gate summary verbatim:

+---------------------------+--------------+
| Rust gate                 | Result       |
+---------------------------+--------------+
| fmt                       | PASS         |
| VT native fmt             | PASS         |
| CLI plugin compile-out    | PASS         |
| clippy                    | PASS         |
| test                      | FAIL (101)   |
| cargo-deny                | PASS         |
| static builds             | FAIL (1)     |
+---------------------------+--------------+
Corrected state-root full gate completed at head `41531643151c5fd1320016b222d717ebf28e4bef`, exit 1. The daemon integration and `vt_credit` passed. Rust tests then stopped at `daemon::gone_tests::a_vanished_session_gets_a_final_close_and_the_channel_stays_up`: `daemon_gone_tests.rs:111` reports `path must be shorter than SUN_LEN`. This separate Rust fixture uses TMPDIR plus 25-character random directory plus sessions/32-hex.sock: 121 bytes under the normal job/tmp path, versus Linux maximum 107. A temporary short alias `/home/kayg/.cache/codex-scratch/rn` points only to this job's tmp directory and yields 107 bytes; it will be removed during cleanup. No test or product code changed. Single exact-test confirmation is running before the remaining workspace gate. Static size remains 22,277,392 bytes x86_64, SHA unchanged, delta 0; ceilings still reject x86_64 (+73,088) and ARM (+66,368). Gate summary verbatim: ```text +---------------------------+--------------+ | Rust gate | Result | +---------------------------+--------------+ | fmt | PASS | | VT native fmt | PASS | | CLI plugin compile-out | PASS | | clippy | PASS | | test | FAIL (101) | | cargo-deny | PASS | | static builds | FAIL (1) | +---------------------------+--------------+ ```
Author
Owner

Exact existing workspace test binary rerun with short TMPDIR alias: daemon::gone_tests::a_vanished_session_gets_a_final_close_and_the_channel_stays_up PASS (1 passed, 110 filtered). Alias points into this job's scratch only. No repository/test changes. The separate Cargo invocation began a different feature-graph rebuild, so it was stopped by its verified PID 2620725; ps -p 2620725 and own scratch build-child scan show no remaining process. Full final gate is queued again through build-slot from about 12:05 UTC, same head 41531643151c5fd1320016b222d717ebf28e4bef, with both fixture roots bounded.

second-verify-fast-tmpdir-path.log
short-tmpdir-existing-test.log

Exact existing workspace test binary rerun with short TMPDIR alias: `daemon::gone_tests::a_vanished_session_gets_a_final_close_and_the_channel_stays_up` PASS (1 passed, 110 filtered). Alias points into this job's scratch only. No repository/test changes. The separate Cargo invocation began a different feature-graph rebuild, so it was stopped by its verified PID 2620725; `ps -p 2620725` and own scratch build-child scan show no remaining process. Full final gate is queued again through build-slot from about 12:05 UTC, same head `41531643151c5fd1320016b222d717ebf28e4bef`, with both fixture roots bounded. [second-verify-fast-tmpdir-path.log](https://git.kayg.org/attachments/3e0d01e6-8161-4dfb-a23a-8c7fb276e53a) [short-tmpdir-existing-test.log](https://git.kayg.org/attachments/036aa051-6dd1-44c4-8407-75f31ce4ee03)
Author
Owner

Final attempt with both fixture roots bounded stopped before Rust checks: latency-proxy self-check missed its existing timing tolerance on the shared build host. Diagnostic only, not NAT performance acceptance: latency proxy: configured 300 ms RTT did not arrive: p50=312.84 ms p99=366.10 ms. Head unchanged 41531643151c5fd1320016b222d717ebf28e4bef; exact previously failing socket test already passed. Preserving the transcript and queueing unchanged full gate again, with at most three immediate retries only when this preflight fails before Rust checks. No threshold, product code or size ceiling changed; no load generator started. All other failures stop the run for inspection.

Final attempt with both fixture roots bounded stopped before Rust checks: latency-proxy self-check missed its existing timing tolerance on the shared build host. Diagnostic only, not NAT performance acceptance: `latency proxy: configured 300 ms RTT did not arrive: p50=312.84 ms p99=366.10 ms`. Head unchanged `41531643151c5fd1320016b222d717ebf28e4bef`; exact previously failing socket test already passed. Preserving the transcript and queueing unchanged full gate again, with at most three immediate retries only when this preflight fails before Rust checks. No threshold, product code or size ceiling changed; no load generator started. All other failures stop the run for inspection.
Author
Owner

Documentation delivery / mesh-relay handoff: six atomic commits pushed on research-nat; remote head verified as 41531643151c5fd1320016b222d717ebf28e4bef. Only research/nat-traversal/README.md differs from base e972f91dad67ac1f1274a4e4cd70df24ee052bc1 (1,083 lines, 75 used source labels).

Full design at the pushed head.

Handoff priorities: exact real-data-socket/generation discovery; concurrent direct checks and authenticated ciphertext relay; D196 both-hard relay boundary; shared D197 M5 selection; sparse bounded state at D170 scale; outside-proved Linux relay eligibility, including cloud 1:1 translation; correct relay server-purpose identity plus generic listener sharing; D166 offline known-member continuity; real MTU/IPv6/roaming proof; D278 invisible ordinary setup. Timers, relay quotas and namespace budgets remain explicitly unmeasured proposals. No engine/library/data choice made. Full comparison, 22 traps, ten rules, source sections and owner questions are in the document.

Final scripts/verify fast on head: exit 1, red. Workspace test summaries: 1,013 passed, 0 failed, 138 ignored across 107 summaries. Both socket-fixture path issues were resolved through scratch environment paths; the existing tests then passed without code changes. The final timing preflight passed unchanged. TypeScript stage was not reached after Rust static failure; no TypeScript changed.

Final gate summary, verbatim:

+---------------------------+--------------+
| Rust gate                 | Result       |
+---------------------------+--------------+
| fmt                       | PASS         |
| VT native fmt             | PASS         |
| CLI plugin compile-out    | PASS         |
| clippy                    | PASS         |
| test                      | PASS         |
| cargo-deny                | PASS         |
| static builds             | FAIL (1)     |
+---------------------------+--------------+

Static gate size lines, verbatim:

PT_INTERP: absent; size: 22277392 bytes; cap: 22204304 bytes; delta: +73088 bytes
PT_INTERP: absent; size: 19705112 bytes; cap: 19638744 bytes; delta: +66368 bytes

D243 same-recipe x86_64 comparison: baseline compiler inputs equal merge base (the sole difference is Markdown; build scripts do not stamp Git identity). Baseline-input and final artifacts are both 22,277,392 bytes, delta 0 bytes, identical SHA-256 fce0032db0b4f651b2c5055ab649182e53cbd6ad1bcec8f31f0dfdba626bcf77. This is an identical-compiler-input comparison in the same checkout/path, not a separate detached baseline build. Existing ceilings reject those unchanged inputs: x86_64 +73,088 bytes; ARM +66,368 bytes. No ceiling or product-code change made by this research job. ARM key-memory runtime proof was explicitly skipped on the x86_64 host.

Review/validation: mesh transport and data-plane author checklists reviewed; independent source reviews and corrections incorporated. Document roles/license/links/wording/ten rules/scope/arithmetic validated; pinned-source check verified 43 GitHub references across 28 files with zero invalid line bounds. No NAT runtime, namespace or performance acceptance is claimed by this documentation job.

Cleanup: worktree clean; remote and local head match; no remaining own build/test processes; own target, temporary source clone/state and short scratch alias removed. Evidence retained outside the checkout. Issue remains open. The unchanged static ceilings remain a verification blocker for the orchestrator; the research is available to mesh-relay now.

Final receipts:

final-verify-fast.log
final-static-size.json
author-review-final.txt
final-document-validation.log
cleanup.json
third-verify-fast-preflight.log

Documentation delivery / mesh-relay handoff: six atomic commits pushed on `research-nat`; remote head verified as `41531643151c5fd1320016b222d717ebf28e4bef`. Only `research/nat-traversal/README.md` differs from base `e972f91dad67ac1f1274a4e4cd70df24ee052bc1` (1,083 lines, 75 used source labels). [Full design at the pushed head](https://git.kayg.org/kayg/infraternal/src/commit/41531643151c5fd1320016b222d717ebf28e4bef/research/nat-traversal/README.md). Handoff priorities: exact real-data-socket/generation discovery; concurrent direct checks and authenticated ciphertext relay; D196 both-hard relay boundary; shared D197 M5 selection; sparse bounded state at D170 scale; outside-proved Linux relay eligibility, including cloud 1:1 translation; correct relay server-purpose identity plus generic listener sharing; D166 offline known-member continuity; real MTU/IPv6/roaming proof; D278 invisible ordinary setup. Timers, relay quotas and namespace budgets remain explicitly unmeasured proposals. No engine/library/data choice made. Full comparison, 22 traps, ten rules, source sections and owner questions are in the document. Final `scripts/verify fast` on head: **exit 1, red**. Workspace test summaries: **1,013 passed, 0 failed, 138 ignored** across 107 summaries. Both socket-fixture path issues were resolved through scratch environment paths; the existing tests then passed without code changes. The final timing preflight passed unchanged. TypeScript stage was not reached after Rust static failure; no TypeScript changed. Final gate summary, verbatim: ```text +---------------------------+--------------+ | Rust gate | Result | +---------------------------+--------------+ | fmt | PASS | | VT native fmt | PASS | | CLI plugin compile-out | PASS | | clippy | PASS | | test | PASS | | cargo-deny | PASS | | static builds | FAIL (1) | +---------------------------+--------------+ ``` Static gate size lines, verbatim: ```text PT_INTERP: absent; size: 22277392 bytes; cap: 22204304 bytes; delta: +73088 bytes PT_INTERP: absent; size: 19705112 bytes; cap: 19638744 bytes; delta: +66368 bytes ``` D243 same-recipe x86_64 comparison: baseline compiler inputs equal merge base (the sole difference is Markdown; build scripts do not stamp Git identity). Baseline-input and final artifacts are both **22,277,392 bytes**, **delta 0 bytes**, identical SHA-256 `fce0032db0b4f651b2c5055ab649182e53cbd6ad1bcec8f31f0dfdba626bcf77`. This is an identical-compiler-input comparison in the same checkout/path, not a separate detached baseline build. Existing ceilings reject those unchanged inputs: x86_64 +73,088 bytes; ARM +66,368 bytes. No ceiling or product-code change made by this research job. ARM key-memory runtime proof was explicitly skipped on the x86_64 host. Review/validation: mesh transport and data-plane author checklists reviewed; independent source reviews and corrections incorporated. Document roles/license/links/wording/ten rules/scope/arithmetic validated; pinned-source check verified 43 GitHub references across 28 files with zero invalid line bounds. No NAT runtime, namespace or performance acceptance is claimed by this documentation job. Cleanup: worktree clean; remote and local head match; no remaining own build/test processes; own target, temporary source clone/state and short scratch alias removed. Evidence retained outside the checkout. Issue remains open. The unchanged static ceilings remain a verification blocker for the orchestrator; the research is available to mesh-relay now. Final receipts: [final-verify-fast.log](https://git.kayg.org/attachments/9dbb649f-3b76-421e-91eb-dc13c24fe578) [final-static-size.json](https://git.kayg.org/attachments/f6f64f90-d35d-4a21-807b-0b282ea82660) [author-review-final.txt](https://git.kayg.org/attachments/afc1d324-c463-4e24-a88a-aec5403baef2) [final-document-validation.log](https://git.kayg.org/attachments/e57c4c2c-e54d-4418-9c47-d4843e30e2ab) [cleanup.json](https://git.kayg.org/attachments/b516cf9c-9df0-4859-b5ab-96e51e8af3d0) [third-verify-fast-preflight.log](https://git.kayg.org/attachments/bb813bb6-458d-43fb-a640-5d19f5c4303b)
Author
Owner

Design proposal: direct-path NAT traversal + relay fallback (orchestrator synthesis, 2026-10-21)

Read-only design pass. No code, no decision changed, no library chosen. This builds on what is already on #220: the research-nat design at 41531643 (research/nat-traversal/README.md, 1,083 lines) and the mesh-relay checkpoint at 176aca846 (crates/infraternal-mesh/src/discovery.rs, contracts/mesh-discovery.md). This comment does not repeat that document. It turns it into one concrete design for the kernel-WireGuard data plane we already ship, adds the India↔Germany arithmetic, and lists the questions that still need the owner.

Already decided, not reopened: D163 (kernel WireGuard on Linux; the server always runs a relay; direct first, back to direct when it opens), D131/D132/D197 (any node can relay; add-host turns relay on; one command; "Run a relay here" box), D176 (quinn + rustls + ring), D185 (relay with the lowest measured RTT, switch mid-session without losing bytes), D196 (our own discovery + simultaneous open; hard+hard relays in stage 1; PCP and NAT-PMP first, then UPnP, on by default with a per-node switch; TCP/TLS 443), D197 M5 (switch only on a clear, sustained win; direct wins a tie), D278 (no NAT controls; the path shows only in add-host/loading steps and the Mesh label), "latency is the design case": the server stays on monitor01.


0. Where the code is today (dev aba6d85ce, branch mesh-relay 176aca846)

Piece File State
Path type crates/infraternal-mesh/src/backend.rs:10 PathKind { Unknown, Direct, Relayed { relay } } type only, nothing produces Relayed
Advertised endpoint crates/plugins/mesh/src/node_coordination.rs + dataplane.rs:38 Peer.endpoint: Option<SocketAddr> built from the control-socket IP + WG listen port. It is a guess, not an observed mapping (finding on #220)
Endpoint switch crates/plugins/mesh/src/wireguard.rs:661 Device::endpoint() sets WGPEER_A_ENDPOINT and keeps the handshake state. This is the seam for moving relay→direct.
Endpoint read-back wireguard.rs counters throw away WGPEER_A_ENDPOINT; the uncommitted patch on #220 fixes this
Discovery book discovery.rs (mesh-relay) pure and bounded: 16 observations per socket, tied to a generation, IPv4 and IPv6 kept apart. No socket, no timer
Relay component crates/plugins/relay/src/{lib,cli}.rs relay up/down are declared not_available_yet
Byte-preserving relay crates/infraternal-agent/src/relay.rs offsets and credit for terminal streams (D128), not datagrams
Server-route selector dataplane.rs:70 ServerPath { mesh, enrolled_fallback } + #201 the shared D197 M5 selector the mesh must reuse

Two blockers come from the code, not from network theory:

  1. The kernel owns the WG UDP socket. A STUN-style probe sent from any other socket learns a different NAT mapping. Every candidate must be observed from the WG listen port itself (see §1).
  2. Kernel WireGuard roams. It moves a peer's endpoint to the source of the latest authenticated packet. Any relay design that hands packets to the kernel from a local shim address can make the kernel "roam back" to the relay after a switch to direct (see §5).

1. Candidate discovery on the real socket

Proposal (hybrid "same 5-tuple side channel"): the kernel keeps the WG fast path. Discovery and connectivity checks run as side-channel packets that use the same source IP:port as the WG socket:

  • Send: a raw socket (AF_INET/AF_INET6, SOCK_RAW, IPPROTO_UDP, we run as root per D163) writes UDP datagrams with source port = WG listen port. To the NAT they are the same flow, so they create and refresh the same mapping.
  • Receive: an AF_PACKET socket with a classic-BPF filter: udp dport == wg_port, and the payload starts with our probe magic and does not start with a WireGuard message type byte (1–4 followed by three zero bytes). The kernel WG module also sees the probe and drops it as invalid. That is a few hundred nanoseconds for each probe packet; data packets never reach userspace.
  • Observers: the server (DE) and every reachable relay answer a probe with the source IP:port they saw. The answer is authenticated with the node's enrolled QUIC identity over the control channel (it carries the transaction ID), so a spoofed UDP reply cannot plant a candidate. The discovery.rs book stores it. Two observers on different IPs give MappingEvidence::Same/Different (endpoint-independent vs dependent mapping), as the contract already says.
  • Candidates per node: host (each non-link-local interface address + WG port), server-reflexive (each observer, per family), port-mapped (§4), relay (§3). These stay within the existing 16-observation cap.
Socket seam option Fast-path cost Correctness Notes
A. Userspace adapter owns the UDP port (kernel WG endpoint = loopback shim) One extra copy and syscall pair per packet in each direction. On the 12-core host a userspace UDP forward is about 5–20 µs and about 1 core per ~1–2 Gbit/s (unmeasured here: needs a perf-VM number) Simplest for demux and for many sockets Kernel WG becomes a crypto box behind a proxy. Gives up the kernel fast path that D163 chose
B. Raw-send + AF_PACKET receive on the WG port (proposed) Zero on data packets. Probes cost about 1 µs each Same NAT mapping as the data flow by construction. Must prove the conntrack entry is shared (nftables NAT on the node itself can rewrite) Needs a CAP_NET_RAW path (we are root). One BPF filter per node, not per peer
C. B for direct, A only for relayed or hard-NAT pairs Zero on direct paths; userspace cost only where a relay already costs more Best of both Recommended. The shim exists only while a pair is relayed

Network change (netlink RTM_NEWADDR/DELADDR/NEWROUTE, resume after suspend): bump the discovery generation (Discovery::restart), withdraw old candidates, re-probe at once. Relay stays usable the whole time.

2. Hole punching (simultaneous open)

With seam B, kernel WireGuard does the punching itself. A WG handshake initiation is a UDP packet from the right 5-tuple.

  1. A wants B. The coordinator (or the relay home, §6 Q2) swaps the candidate lists and a shared punch_at time. Both sides receive it inside one control round trip.
  2. Both sides send authenticated probes (raw socket, WG port) to every peer candidate, paced as in research-nat §3: one new check every 50 ms per node; retransmit at 250/750/1750 ms; at most 40 probes per pair per attempt. The first probes on both sides open each NAT's mapping and filter.
  3. The first probe pair that succeeds in both directions (nonce echoed, identity-bound) proves the path. Only then does Device::endpoint() set the WG endpoint to the proven address. A short persistent_keepalive (proposal: 20 s, only while the pair is active) holds the mapping. The current code has a fixed keepalive (#219).
  4. If nothing succeeds, the pair stays relayed. Retry at 5, 15, 30, 60 s, then every 60 s, with jitter. A network event triggers a retry earlier.

Expected outcome by NAT pair (RFC 4787 terms, mapping/filtering; success rates must be measured, D196):

A \ B public / EIM-EIF EIM + APDF (typical home) EDM ("hard", CGNAT, many mobiles)
public / EIM-EIF direct direct direct (B reaches A's stable address)
EIM + APDF direct direct (simultaneous open) usually relay; prediction case (§2a)
EDM direct usually relay relay in stage 1 (D196)

2a. Hard NAT and the birthday technique (later, only on evidence)

For one EDM side and one stable side: the hard side opens m mappings and the easy side tries k random ports out of a pool of N. P ≈ 1 − (1 − m/N)^k. With m=256, N=64,512 and k=1,024 this gives about 98% in the model. Real port pools, port-preserving allocators and conntrack limits change it. research-nat §3 explains why it is not measured.

Constraint from our code: the hard side's m sockets must be the WG socket for the winner to carry data. With kernel WG there is one listen port, so the birthday technique requires seam A (the userspace shim) for that pair, or a per-pair second WG interface. So it costs exactly what seam C already pays for relayed pairs: it fits C, and it does not fit a pure B.

Port prediction (sequential allocators): cheaper. One side probes last_seen_port + 1..+16 from the single WG port. It is worth measuring first because it needs no extra sockets.

Trigger for building either one (D196 says "only if measurements show many relayed pairs that could have been direct"): see Q4.

3. Relay: data path, selection, failover

Data path: the relay forwards WireGuard ciphertext only. It never has a mesh key (research-nat §4).

  • UDP-capable networks: node ↔ relay over the existing QUIC (D176) with QUIC DATAGRAM frames (RFC 9221). They are unreliable and unordered, which matches WG semantics. There is no head-of-line blocking, and one QUIC connection per node to each relay is shared by all pairs. The per-packet header is a 4-byte channel ID (pair + key generation) bound at channel setup. Cost: the QUIC packet adds about 25–40 bytes on top of WG's overhead. The relayed MTU must drop to about 1,380 − 40. We must prove the Packet Too Big path (#219 already flags the fixed 1,380).
  • UDP blocked: TLS over TCP 443 with length-delimited records. It shares 443 with the server's HTTPS through ALPN demux in a generic plugin listener (D198; research-nat notes the listener owners are separate today, runtime.rs:305/420). TCP-over-TCP meltdown does not apply: the inner flow is WG/UDP. Inner TCP still sees head-of-line stalls on loss, which is accepted for this fallback.
  • Relay identity: node leaves are client-auth only (ca.rs:239). A node relay needs a server-purpose leaf issued through role enrollment (D131 one-time token). We never relax the name or purpose checks.

Selection (D185 + D197 M5): the score is the end-to-end RTT through the relay, RTT(A,R) + RTT(R,B), measured with authenticated probes through the relay, never A's leg alone. Rank by median and p95 of 5 samples over ≥3 s. A relay must be eligible first (healthy, capacity left, MTU ok). Quality only orders the eligible ones. Each node keeps one home relay connection for rendezvous and for being reached. It opens a data relay per pair only when that relay wins by the M5 margin.

Failover:

Event Detection Action Budget
Relay QUIC connection lost (explicit close / ICMP / reset) at once the pair moves to the next-best already-measured relay; the server's relay is always the last resort (D163) ≤ 1 RTT to the new relay if its connection is warm, 1 RTT + QUIC handshake (0-RTT resumption, D176) if cold
Silent relay loss no authenticated receive for 3 s on an active pair (research-nat timers) as above detector is a proposal; must be measured against D265's three-RTT reconnect budget
Direct path dies WG handshake age plus our probe on the active pair fall back to the relay that is still warm (we keep the home relay open) the same shared #201 selector

Terminal/VNC/backup streams already keep their own byte offsets (D128, infraternal-agent/src/relay.rs). A route change below them loses no application bytes. Arbitrary inner UDP can drop packets during a switch. That is stated, not hidden.

4. Port mapping (PCP / NAT-PMP / UPnP-IGD)

D196 has already decided the order and the default. Design points that follow from our code:

  • Map the WG listen port itself: internal port = wg_port, UDP only, only the node's own address. The mapped external IP:port becomes a port-mapped candidate. It is proven like any other candidate (a lease is not reachability; a CGNAT can sit in front).
  • PCP and NAT-PMP run at the same time as the first discovery probes. UPnP starts only if both fail within 250 ms (proposal). Traffic never waits for mapping: relay and punching run in parallel.
  • IPv6: a PCP MAP on IPv6 opens a firewall pinhole on a home CPE. It is the only standard way to make a node with an IPv6 address but a stateful firewall reachable from outside. Worth having.
  • Lease: request 7,200 s; renew at random 1/2–5/8 of the granted lifetime (RFC 6887 §11.2.1); on a gateway epoch reset, redo the mapping. Turning the switch off releases only our lease.
  • Cost: one UDP exchange at start and one per renewal. No idle timer when the node has no active pairs.

5. Relay → direct upgrade without breaking streams

WireGuard sessions are independent of the endpoint. Device::endpoint() already changes the endpoint and keeps handshake state, so the inner keys, counters and every inner TCP connection survive the move. The design has to handle the roaming hazard:

  1. Prove first: the direct path is proven (nonce both ways plus one probe at data size, so MTU is checked) while traffic still flows through the relay.
  2. Win check: the M5 rule (direct wins on equal quality: RTT within max(5 ms, 15%) and no worse loss). If direct is materially worse (it happens: some direct routes are worse than a well-placed relay), stay relayed.
  3. Switch: A calls Device::endpoint(B, direct). The next packet from A arrives at B on the direct path. B's kernel roams to it automatically, so B needs no control message and no extra round trip. B's agent sees the endpoint change in counters and confirms.
  4. Drain and stop the flip-back: the local relay shim (seam C) still delivers in-flight packets for one relayed RTT (proposal: max(2×RTT, 250 ms)). After that it stops injecting into the kernel and keeps the channel open for a 10 s grace period with injection off. Without this, a late relayed packet makes the kernel roam back to the shim. The flip-back must be a namespace acceptance test: switch, inject a delayed relayed packet, assert the endpoint stays direct.
  5. Rollback: if the direct path stops receiving within the grace period, re-enable injection and set the endpoint back. The relay channel is still warm, so the cost is ≈ 1 relayed RTT.

What the user sees (D278): add-host shows "Connected (relayed)" → "Direct". After that, only the Mesh label changes. The switch never shows a notice.

6. The latency case: server in Germany, clients in India

Measured legs (research/data-path/REPORT.md, 300-sample runs): monitor01 (Essen) ↔ Mac VM (India) 198.1 ms mean; build box (Falkenstein) ↔ Mac 152.7 ms p50 TCP connect; Mac ↔ o1 (Hyderabad estimate) 23.4 ms (5 pings); monitor01 ↔ o1 171.1 ms (5 pings).

Pair Via DE server relay Via India relay (o1-like) Direct
India host ↔ India host ≈ 198 + 198 = ~396 ms ≈ 23 + 23 = ~46 ms (assumed symmetric) probably 10–40 ms (unmeasured: open item on data-path)
India host ↔ DE host ≈ 198 ms ≈ 23 + 171 = ~194 ms ≈ 198 ms
DE ↔ DE ≈ 10 ms bad (~340 ms), never chosen ≈ 10 ms

What follows:

  • The server stays in Germany. The fix for India↔India is direct first, else a relay near both ends, never the DE server for data. The DE server relay is only the last resort and the place where coordination happens.
  • Time to first byte matters as much as steady state. Today rendezvous goes through the DE coordinator: candidate exchange costs about 1 RTT to DE for each side in parallel (~200 ms), then punching about 1 direct RTT. Relayed traffic can start after one QUIC handshake to the home relay (0-RTT on resumption). Start relayed at once and upgrade to direct in about 0.3–0.6 s for India↔India. Users should never wait on the punch.
  • Rendezvous through the home relay (Q2) cuts the India↔India setup from ~200 ms (DE) to ~25 ms when both sides use an India relay as home. Authorization still comes from the coordinator, as tickets signed in advance (D166 keeps working while the coordinator is offline).
  • Relay scoring must use the sum of both legs. Nearest-to-me picks wrongly for India↔DE (both choices ≈ 195 ms; the tie goes to the more stable relay, using the stable node-ID tie-break).

7. Library and engine candidates (not chosen; owner decides on measurements)

Taken from the inventory in research-nat §10, cut down to what this design needs:

Need Candidates Trade-off to measure
Relay transport existing quinn (D176) DATAGRAM already in the binary; no new engine. Measure DATAGRAM throughput and p99 at 200 ms RTT with loss
ICE-like checklist (a) our own small checker over the discovery.rs book, (b) str0m's ICE agent (already in the binary for the browser direct path, D192), (c) rtc-ice (a) smallest, most code to own; (b) zero extra binary size if it can drive a caller-owned raw socket, but needs a check of fit with the kernel WG port; (c) new dependency graph
Port mapping (a) our own PCP/NAT-PMP + minimal SSDP/IGD, (b) portmapper crate, (c) igd-next + our own PCP/NAT-PMP stripped size delta (D243: >16 KiB needs a justification, >64 KiB goes to the owner), idle tasks/timers, how the XML parser is exposed
Socket seam seam A / B / C (§1) per-packet µs, CPU per Gbit/s, RSS per active relayed pair × 1,000 (D170)

Each needs cargo tree -e features, a stripped static delta on x86_64 and aarch64, RSS at 1/100/1,000 active peers, and setup p50/p99 on the perf VM with the India↔Germany latency proxy.

8. Gates for this slice (beyond scripts/verify fast)

Namespace matrix (scripts/mesh-endpoint-proof.py, already written, not yet run): EIM/EIF, EIM/APDF, EDM on each side; UDP denied (TLS 443 only); stale endpoint after a rebind; suspend and roam; PTB dropped at low MTU; relay killed mid-stream; the flip-back test from §5.4; DE↔IN latency proxy (198 ms). Numbers per run: setup p50/p99, time to direct, the number of bytes in a numbered stream that are lost (must be zero for reliable streams), CPU and RSS per pair.


Open questions for the owner

Q1. How does discovery see the kernel WireGuard socket? (blocks everything; §1)

  1. Seam A: a userspace shim owns the port for all traffic. Example: a 1 Gbit/s backup between two DE hosts goes through userspace, at about one core.
  2. Seam B: raw-send + AF_PACKET receive on the WG port; the kernel keeps all data. Example: same backup, zero userspace copies; but birthday hard-NAT is impossible later.
  3. Seam C (recommended): B for direct paths, the userspace shim only while a pair is relayed or hard-NAT. Example: the backup between DE hosts stays in the kernel; an India phone hotspot ↔ Mac pair relays through the shim, where the relay hop costs more than the copy anyway.
    Next step: a perf-VM job measures A against B (µs per packet, CPU per Gbit/s), then you choose.

Q2. Where does rendezvous signalling go?

  1. Only through the DE coordinator. Example: two India hosts wait ~200 ms extra before punching.
  2. Through the home relay, with tickets signed in advance by the coordinator (recommended, after 1 works). Example: both hosts use an India relay as home; setup ≈ 25 ms; still works while monitor01 restarts (D166).
  3. Both, whichever answers first. Costs a duplicate exchange on every setup.

Q3. Which relays may a pair use?

  1. Any relay in the same mesh (recommended). It forwards ciphertext only. Example: your Hyderabad VM relays for a teammate's laptop.
  2. Only relays on nodes that one of the two endpoints' owners controls. Example: a teammate's pair never touches your VM; India↔India falls back to DE (~396 ms).
  3. Admin picks a relay allow-list per mesh. Conflicts with D278/intuitive defaults; only as an override.

Q4. When does hard-NAT prediction get built? (D196 says "if measurements show many relayed pairs that could have been direct")

  1. When ≥10% of relayed pair-hours have one endpoint-independent side and the relay costs ≥30 ms over the estimated direct RTT, over 2 weeks of prod counters. Example: India mobile hotspot (EDM) ↔ home Mac (EIM) relayed via Hyderabad at 46 ms, while direct would be ~15 ms: counts.
  2. Any relayed pair with an EIM side counts. Builds it earlier and more often for small wins.
  3. Option 1, and try sequential port prediction first (one socket, cheap) before the 256-socket technique (recommended).

Q5. Numbers for the switch rule (D197 M5 "clear, sustained win")

  1. RTT saving ≥ max(5 ms, 15%) sustained over 5 samples / ≥3 s, 10 s dwell; direct wins inside that band (recommended, the same numbers as #201's selector). Example: relay 46 ms vs direct 40 ms → direct wins (equal band); relay 46 vs a new relay 43 → stays.
  2. A tighter 2 ms / 5%. More switches on jittery mobile links.
  3. A looser 20 ms / 25%. Fewer switches; India↔DE nearly never changes relay.

Q6. TLS 443 fallback scope in the first release

  1. Plain TLS on 443 with ALPN demux next to HTTPS (D196 minimum).
  2. 1 + WebSocket upgrade (recommended). Example: a hotel network allows only HTTP-looking 443 traffic through a transparent proxy.
  3. 2 + authenticated HTTP CONNECT proxy. Example: a corporate network with a proxy PAC file; needs proxy credentials, so it touches the fewest-secrets rule.

Q7. Relay framing over QUIC

  1. DATAGRAM frames (recommended): no head-of-line blocking, matches WG. Example: a 2% loss mobile link: one lost packet stalls only its own inner flow.
  2. One QUIC stream per pair: reliable, but loss stalls all of the pair's inner flows (~1 RTT ≈ 200 ms on IN↔DE).

Q8. Port-mapping and ICE checklist implementation (§7): owner choice after the size/RSS/idle comparison job. No recommendation until it is measured. What I lean towards: our own checker over discovery.rs (the smallest feature graph), with str0m's ICE agent measured as the alternative because it is already in the binary.

Proposed order once Q1 is answered: real-socket observation (seam) → relay component with QUIC DATAGRAM + TLS 443 → rendezvous + simultaneous open → upgrade/flip-back handling on the #201 selector → PCP/NAT-PMP/UPnP → node-relay qualification and identities → hard-NAT experiments only if Q4 triggers.

Nothing above is measured unless it cites research/data-path or the #220 receipts. The µs/CPU figures for seam A are estimates that the Q1 job must replace.

## Design proposal: direct-path NAT traversal + relay fallback (orchestrator synthesis, 2026-10-21) Read-only design pass. No code, no decision changed, no library chosen. This builds on what is already on #220: the `research-nat` design at `41531643` (`research/nat-traversal/README.md`, 1,083 lines) and the `mesh-relay` checkpoint at `176aca846` (`crates/infraternal-mesh/src/discovery.rs`, `contracts/mesh-discovery.md`). This comment does not repeat that document. It turns it into one concrete design for the kernel-WireGuard data plane we already ship, adds the India↔Germany arithmetic, and lists the questions that still need the owner. **Already decided, not reopened:** D163 (kernel WireGuard on Linux; the server always runs a relay; direct first, back to direct when it opens), D131/D132/D197 (any node can relay; add-host turns relay on; one command; "Run a relay here" box), D176 (quinn + rustls + ring), D185 (relay with the lowest measured RTT, switch mid-session without losing bytes), D196 (our own discovery + simultaneous open; hard+hard relays in stage 1; PCP and NAT-PMP first, then UPnP, on by default with a per-node switch; TCP/TLS 443), D197 M5 (switch only on a clear, sustained win; direct wins a tie), D278 (no NAT controls; the path shows only in add-host/loading steps and the Mesh label), "latency is the design case": the server stays on monitor01. --- ### 0. Where the code is today (dev `aba6d85ce`, branch `mesh-relay` `176aca846`) | Piece | File | State | |---|---|---| | Path type | `crates/infraternal-mesh/src/backend.rs:10` `PathKind { Unknown, Direct, Relayed { relay } }` | type only, nothing produces `Relayed` | | Advertised endpoint | `crates/plugins/mesh/src/node_coordination.rs` + `dataplane.rs:38` `Peer.endpoint: Option<SocketAddr>` | built from the **control-socket IP + WG listen port**. It is a guess, not an observed mapping (finding on #220) | | Endpoint switch | `crates/plugins/mesh/src/wireguard.rs:661` `Device::endpoint()` | sets `WGPEER_A_ENDPOINT` and keeps the handshake state. **This is the seam for moving relay→direct.** | | Endpoint read-back | `wireguard.rs` counters | throw away `WGPEER_A_ENDPOINT`; the uncommitted patch on #220 fixes this | | Discovery book | `discovery.rs` (mesh-relay) | pure and bounded: 16 observations per socket, tied to a generation, IPv4 and IPv6 kept apart. No socket, no timer | | Relay component | `crates/plugins/relay/src/{lib,cli}.rs` | `relay up/down` are declared `not_available_yet` | | Byte-preserving relay | `crates/infraternal-agent/src/relay.rs` | offsets and credit for **terminal** streams (D128), not datagrams | | Server-route selector | `dataplane.rs:70` `ServerPath { mesh, enrolled_fallback }` + #201 | the shared D197 M5 selector the mesh must reuse | Two blockers come from the code, not from network theory: 1. **The kernel owns the WG UDP socket.** A STUN-style probe sent from any other socket learns a different NAT mapping. Every candidate must be observed **from the WG listen port itself** (see §1). 2. **Kernel WireGuard roams.** It moves a peer's endpoint to the source of the latest authenticated packet. Any relay design that hands packets to the kernel from a local shim address can make the kernel "roam back" to the relay after a switch to direct (see §5). --- ### 1. Candidate discovery on the real socket **Proposal (hybrid "same 5-tuple side channel"):** the kernel keeps the WG fast path. Discovery and connectivity checks run as *side-channel* packets that use the **same source IP:port as the WG socket**: - **Send:** a raw socket (`AF_INET/AF_INET6`, `SOCK_RAW`, `IPPROTO_UDP`, we run as root per D163) writes UDP datagrams with source port = WG listen port. To the NAT they are the same flow, so they create and refresh the same mapping. - **Receive:** an `AF_PACKET` socket with a classic-BPF filter: `udp dport == wg_port`, and the payload starts with our probe magic and does not start with a WireGuard message type byte (1–4 followed by three zero bytes). The kernel WG module also sees the probe and drops it as invalid. That is a few hundred nanoseconds for each probe packet; data packets never reach userspace. - **Observers:** the server (DE) and every reachable relay answer a probe with the source IP:port they saw. The answer is authenticated with the node's enrolled QUIC identity over the control channel (it carries the transaction ID), so a spoofed UDP reply cannot plant a candidate. The `discovery.rs` book stores it. Two observers on different IPs give `MappingEvidence::Same/Different` (endpoint-independent vs dependent mapping), as the contract already says. - **Candidates per node:** host (each non-link-local interface address + WG port), server-reflexive (each observer, per family), port-mapped (§4), relay (§3). These stay within the existing 16-observation cap. | Socket seam option | Fast-path cost | Correctness | Notes | |---|---|---|---| | **A. Userspace adapter owns the UDP port** (kernel WG endpoint = loopback shim) | One extra copy and syscall pair per packet in each direction. On the 12-core host a userspace UDP forward is about 5–20 µs and about 1 core per ~1–2 Gbit/s (unmeasured here: **needs a perf-VM number**) | Simplest for demux and for many sockets | Kernel WG becomes a crypto box behind a proxy. Gives up the kernel fast path that D163 chose | | **B. Raw-send + AF_PACKET receive on the WG port** (proposed) | Zero on data packets. Probes cost about 1 µs each | Same NAT mapping as the data flow by construction. Must prove the conntrack entry is shared (nftables NAT on the node itself can rewrite) | Needs a CAP_NET_RAW path (we are root). One BPF filter per node, not per peer | | **C. B for direct, A only for relayed or hard-NAT pairs** | Zero on direct paths; userspace cost only where a relay already costs more | Best of both | Recommended. The shim exists only while a pair is relayed | Network change (netlink `RTM_NEWADDR/DELADDR/NEWROUTE`, resume after suspend): bump the discovery generation (`Discovery::restart`), withdraw old candidates, re-probe at once. Relay stays usable the whole time. ### 2. Hole punching (simultaneous open) With seam B, **kernel WireGuard does the punching itself**. A WG handshake initiation is a UDP packet from the right 5-tuple. 1. A wants B. The coordinator (or the relay home, §6 Q2) swaps the candidate lists and a shared `punch_at` time. Both sides receive it inside one control round trip. 2. Both sides send authenticated probes (raw socket, WG port) to every peer candidate, paced as in research-nat §3: one new check every 50 ms per node; retransmit at 250/750/1750 ms; at most 40 probes per pair per attempt. The first probes on both sides open each NAT's mapping and filter. 3. The first probe pair that succeeds in both directions (nonce echoed, identity-bound) proves the path. Only then does `Device::endpoint()` set the WG endpoint to the proven address. A short `persistent_keepalive` (proposal: 20 s, only while the pair is active) holds the mapping. The current code has a fixed keepalive (#219). 4. If nothing succeeds, the pair stays relayed. Retry at 5, 15, 30, 60 s, then every 60 s, with jitter. A network event triggers a retry earlier. Expected outcome by NAT pair (RFC 4787 terms, mapping/filtering; success rates must be **measured**, D196): | A \ B | public / EIM-EIF | EIM + APDF (typical home) | EDM ("hard", CGNAT, many mobiles) | |---|---|---|---| | public / EIM-EIF | direct | direct | direct (B reaches A's stable address) | | EIM + APDF | direct | direct (simultaneous open) | usually relay; prediction case (§2a) | | EDM | direct | usually relay | relay in stage 1 (D196) | #### 2a. Hard NAT and the birthday technique (later, only on evidence) For one EDM side and one stable side: the hard side opens `m` mappings and the easy side tries `k` random ports out of a pool of `N`. P ≈ 1 − (1 − m/N)^k. With m=256, N=64,512 and k=1,024 this gives about 98% **in the model**. Real port pools, port-preserving allocators and conntrack limits change it. research-nat §3 explains why it is not measured. **Constraint from our code:** the hard side's `m` sockets must be the WG socket for the winner to carry data. With kernel WG there is **one** listen port, so the birthday technique *requires* seam A (the userspace shim) for that pair, or a per-pair second WG interface. So it costs exactly what seam C already pays for relayed pairs: it fits C, and it does not fit a pure B. **Port prediction** (sequential allocators): cheaper. One side probes `last_seen_port + 1..+16` from the single WG port. It is worth measuring first because it needs no extra sockets. Trigger for building either one (D196 says "only if measurements show many relayed pairs that could have been direct"): see Q4. ### 3. Relay: data path, selection, failover **Data path:** the relay forwards **WireGuard ciphertext only**. It never has a mesh key (research-nat §4). - **UDP-capable networks:** node ↔ relay over the existing QUIC (D176) with **QUIC DATAGRAM frames** (RFC 9221). They are unreliable and unordered, which matches WG semantics. There is no head-of-line blocking, and one QUIC connection per node to each relay is shared by all pairs. The per-packet header is a 4-byte channel ID (pair + key generation) bound at channel setup. Cost: the QUIC packet adds about 25–40 bytes on top of WG's overhead. The relayed MTU must drop to about 1,380 − 40. We must prove the Packet Too Big path (#219 already flags the fixed 1,380). - **UDP blocked:** TLS over TCP 443 with length-delimited records. It shares 443 with the server's HTTPS through **ALPN demux** in a generic plugin listener (D198; research-nat notes the listener owners are separate today, `runtime.rs:305/420`). TCP-over-TCP meltdown does not apply: the inner flow is WG/UDP. Inner TCP still sees head-of-line stalls on loss, which is accepted for this fallback. - **Relay identity:** node leaves are client-auth only (`ca.rs:239`). A node relay needs a server-purpose leaf issued through role enrollment (D131 one-time token). We never relax the name or purpose checks. **Selection (D185 + D197 M5):** the score is the **end-to-end** RTT through the relay, `RTT(A,R) + RTT(R,B)`, measured with authenticated probes through the relay, never A's leg alone. Rank by median and p95 of 5 samples over ≥3 s. A relay must be *eligible* first (healthy, capacity left, MTU ok). Quality only orders the eligible ones. Each node keeps one **home** relay connection for rendezvous and for being reached. It opens a data relay per pair only when that relay wins by the M5 margin. **Failover:** | Event | Detection | Action | Budget | |---|---|---|---| | Relay QUIC connection lost (explicit close / ICMP / reset) | at once | the pair moves to the next-best already-measured relay; the server's relay is always the last resort (D163) | ≤ 1 RTT to the new relay if its connection is warm, 1 RTT + QUIC handshake (0-RTT resumption, D176) if cold | | Silent relay loss | no authenticated receive for 3 s on an active pair (research-nat timers) | as above | detector is a proposal; must be measured against D265's three-RTT reconnect budget | | Direct path dies | WG handshake age plus our probe on the active pair | fall back to the relay that is still warm (we keep the home relay open) | the same shared #201 selector | Terminal/VNC/backup streams already keep their own byte offsets (D128, `infraternal-agent/src/relay.rs`). A route change below them loses no application bytes. Arbitrary inner UDP can drop packets during a switch. That is stated, not hidden. ### 4. Port mapping (PCP / NAT-PMP / UPnP-IGD) D196 has already decided the order and the default. Design points that follow from our code: - Map **the WG listen port itself**: internal port = `wg_port`, UDP only, only the node's own address. The mapped external IP:port becomes a `port-mapped` candidate. It is proven like any other candidate (a lease is not reachability; a CGNAT can sit in front). - PCP and NAT-PMP run at the same time as the first discovery probes. UPnP starts only if both fail within 250 ms (proposal). Traffic never waits for mapping: relay and punching run in parallel. - **IPv6:** a PCP MAP on IPv6 opens a *firewall pinhole* on a home CPE. It is the only standard way to make a node with an IPv6 address but a stateful firewall reachable from outside. Worth having. - Lease: request 7,200 s; renew at random 1/2–5/8 of the **granted** lifetime (RFC 6887 §11.2.1); on a gateway epoch reset, redo the mapping. Turning the switch off releases only our lease. - **Cost:** one UDP exchange at start and one per renewal. No idle timer when the node has no active pairs. ### 5. Relay → direct upgrade without breaking streams WireGuard sessions are independent of the endpoint. `Device::endpoint()` already changes the endpoint and **keeps handshake state**, so the inner keys, counters and every inner TCP connection survive the move. The design has to handle the roaming hazard: 1. **Prove first:** the direct path is proven (nonce both ways plus one probe at data size, so MTU is checked) while traffic still flows through the relay. 2. **Win check:** the M5 rule (direct wins on equal quality: RTT within max(5 ms, 15%) and no worse loss). If direct is materially worse (it happens: some direct routes are worse than a well-placed relay), stay relayed. 3. **Switch:** A calls `Device::endpoint(B, direct)`. The next packet from A arrives at B on the direct path. B's kernel *roams* to it automatically, so B needs no control message and no extra round trip. B's agent sees the endpoint change in counters and confirms. 4. **Drain and stop the flip-back:** the local relay shim (seam C) still delivers in-flight packets for **one relayed RTT** (proposal: max(2×RTT, 250 ms)). After that it **stops injecting into the kernel** and keeps the channel open for a 10 s grace period with injection off. Without this, a late relayed packet makes the kernel roam back to the shim. The flip-back must be a namespace acceptance test: switch, inject a delayed relayed packet, assert the endpoint stays direct. 5. **Rollback:** if the direct path stops receiving within the grace period, re-enable injection and set the endpoint back. The relay channel is still warm, so the cost is ≈ 1 relayed RTT. What the user sees (D278): add-host shows "Connected (relayed)" → "Direct". After that, only the Mesh label changes. The switch never shows a notice. ### 6. The latency case: server in Germany, clients in India Measured legs (research/data-path/REPORT.md, 300-sample runs): monitor01 (Essen) ↔ Mac VM (India) **198.1 ms** mean; build box (Falkenstein) ↔ Mac **152.7 ms** p50 TCP connect; Mac ↔ o1 (Hyderabad estimate) **23.4 ms** (5 pings); monitor01 ↔ o1 **171.1 ms** (5 pings). | Pair | Via DE server relay | Via India relay (o1-like) | Direct | |---|---|---|---| | India host ↔ India host | ≈ 198 + 198 = **~396 ms** | ≈ 23 + 23 = **~46 ms** (assumed symmetric) | probably 10–40 ms (**unmeasured**: open item on data-path) | | India host ↔ DE host | ≈ 198 ms | ≈ 23 + 171 = ~194 ms | ≈ 198 ms | | DE ↔ DE | ≈ 10 ms | bad (~340 ms), never chosen | ≈ 10 ms | What follows: - The server stays in Germany. The fix for India↔India is **direct first, else a relay near both ends**, never the DE server for data. The DE server relay is only the last resort and the place where coordination happens. - **Time to first byte** matters as much as steady state. Today rendezvous goes through the DE coordinator: candidate exchange costs about 1 RTT to DE for each side in parallel (~200 ms), then punching about 1 direct RTT. Relayed traffic can start after one QUIC handshake to the home relay (0-RTT on resumption). Start relayed at once and upgrade to direct in about 0.3–0.6 s for India↔India. Users should never wait on the punch. - **Rendezvous through the home relay** (Q2) cuts the India↔India setup from ~200 ms (DE) to ~25 ms when both sides use an India relay as home. Authorization still comes from the coordinator, as tickets signed in advance (D166 keeps working while the coordinator is offline). - **Relay scoring must use the sum of both legs.** Nearest-to-me picks wrongly for India↔DE (both choices ≈ 195 ms; the tie goes to the more stable relay, using the stable node-ID tie-break). ### 7. Library and engine candidates (not chosen; owner decides on measurements) Taken from the inventory in research-nat §10, cut down to what this design needs: | Need | Candidates | Trade-off to measure | |---|---|---| | Relay transport | existing quinn (D176) DATAGRAM | already in the binary; no new engine. Measure DATAGRAM throughput and p99 at 200 ms RTT with loss | | ICE-like checklist | (a) our own small checker over the `discovery.rs` book, (b) `str0m`'s ICE agent (already in the binary for the browser direct path, D192), (c) `rtc-ice` | (a) smallest, most code to own; (b) zero extra binary size if it can drive a caller-owned raw socket, but needs a check of fit with the kernel WG port; (c) new dependency graph | | Port mapping | (a) our own PCP/NAT-PMP + minimal SSDP/IGD, (b) `portmapper` crate, (c) `igd-next` + our own PCP/NAT-PMP | stripped size delta (D243: >16 KiB needs a justification, >64 KiB goes to the owner), idle tasks/timers, how the XML parser is exposed | | Socket seam | seam A / B / C (§1) | per-packet µs, CPU per Gbit/s, RSS per active relayed pair × 1,000 (D170) | Each needs `cargo tree -e features`, a stripped static delta on x86_64 and aarch64, RSS at 1/100/1,000 active peers, and setup p50/p99 on the perf VM with the India↔Germany latency proxy. ### 8. Gates for this slice (beyond `scripts/verify fast`) Namespace matrix (`scripts/mesh-endpoint-proof.py`, already written, not yet run): EIM/EIF, EIM/APDF, EDM on each side; UDP denied (TLS 443 only); stale endpoint after a rebind; suspend and roam; PTB dropped at low MTU; relay killed mid-stream; the **flip-back test** from §5.4; DE↔IN latency proxy (198 ms). Numbers per run: setup p50/p99, time to direct, the number of bytes in a numbered stream that are lost (must be zero for reliable streams), CPU and RSS per pair. --- ### Open questions for the owner **Q1. How does discovery see the kernel WireGuard socket?** (blocks everything; §1) 1. Seam A: a userspace shim owns the port for all traffic. *Example: a 1 Gbit/s backup between two DE hosts goes through userspace, at about one core.* 2. Seam B: raw-send + AF_PACKET receive on the WG port; the kernel keeps all data. *Example: same backup, zero userspace copies; but birthday hard-NAT is impossible later.* 3. **Seam C (recommended):** B for direct paths, the userspace shim only while a pair is relayed or hard-NAT. *Example: the backup between DE hosts stays in the kernel; an India phone hotspot ↔ Mac pair relays through the shim, where the relay hop costs more than the copy anyway.* Next step: a perf-VM job measures A against B (µs per packet, CPU per Gbit/s), then you choose. **Q2. Where does rendezvous signalling go?** 1. Only through the DE coordinator. *Example: two India hosts wait ~200 ms extra before punching.* 2. **Through the home relay, with tickets signed in advance by the coordinator (recommended, after 1 works).** *Example: both hosts use an India relay as home; setup ≈ 25 ms; still works while monitor01 restarts (D166).* 3. Both, whichever answers first. *Costs a duplicate exchange on every setup.* **Q3. Which relays may a pair use?** 1. **Any relay in the same mesh (recommended).** It forwards ciphertext only. *Example: your Hyderabad VM relays for a teammate's laptop.* 2. Only relays on nodes that one of the two endpoints' owners controls. *Example: a teammate's pair never touches your VM; India↔India falls back to DE (~396 ms).* 3. Admin picks a relay allow-list per mesh. *Conflicts with D278/intuitive defaults; only as an override.* **Q4. When does hard-NAT prediction get built?** (D196 says "if measurements show many relayed pairs that could have been direct") 1. When ≥10% of relayed pair-hours have one endpoint-independent side **and** the relay costs ≥30 ms over the estimated direct RTT, over 2 weeks of prod counters. *Example: India mobile hotspot (EDM) ↔ home Mac (EIM) relayed via Hyderabad at 46 ms, while direct would be ~15 ms: counts.* 2. Any relayed pair with an EIM side counts. *Builds it earlier and more often for small wins.* 3. **Option 1, and try sequential port prediction first (one socket, cheap) before the 256-socket technique (recommended).** **Q5. Numbers for the switch rule** (D197 M5 "clear, sustained win") 1. **RTT saving ≥ max(5 ms, 15%) sustained over 5 samples / ≥3 s, 10 s dwell; direct wins inside that band (recommended, the same numbers as #201's selector).** *Example: relay 46 ms vs direct 40 ms → direct wins (equal band); relay 46 vs a new relay 43 → stays.* 2. A tighter 2 ms / 5%. *More switches on jittery mobile links.* 3. A looser 20 ms / 25%. *Fewer switches; India↔DE nearly never changes relay.* **Q6. TLS 443 fallback scope in the first release** 1. Plain TLS on 443 with ALPN demux next to HTTPS (D196 minimum). 2. **1 + WebSocket upgrade (recommended).** *Example: a hotel network allows only HTTP-looking 443 traffic through a transparent proxy.* 3. 2 + authenticated HTTP CONNECT proxy. *Example: a corporate network with a proxy PAC file; needs proxy credentials, so it touches the fewest-secrets rule.* **Q7. Relay framing over QUIC** 1. **DATAGRAM frames (recommended):** no head-of-line blocking, matches WG. *Example: a 2% loss mobile link: one lost packet stalls only its own inner flow.* 2. One QUIC stream per pair: reliable, but loss stalls all of the pair's inner flows (~1 RTT ≈ 200 ms on IN↔DE). **Q8. Port-mapping and ICE checklist implementation** (§7): owner choice after the size/RSS/idle comparison job. No recommendation until it is measured. What I lean towards: our own checker over `discovery.rs` (the smallest feature graph), with `str0m`'s ICE agent measured as the alternative because it is already in the binary. Proposed order once Q1 is answered: real-socket observation (seam) → relay component with QUIC DATAGRAM + TLS 443 → rendezvous + simultaneous open → upgrade/flip-back handling on the #201 selector → PCP/NAT-PMP/UPnP → node-relay qualification and identities → hard-NAT experiments only if Q4 triggers. *Nothing above is measured unless it cites research/data-path or the #220 receipts. The µs/CPU figures for seam A are estimates that the Q1 job must replace.*
Author
Owner

Grill round 1 answered by the owner (2026-10-30), recorded as D313 on dev (db8c1bdf6).

Q1 seam C · Q2 home relay with pre-signed tickets (after coordinator-only works) · Q3 any relay in the mesh · Q4 counters-gated, sequential prediction first · Q5 max(5 ms, 15%) over 5 samples / ≥3 s, 10 s dwell, direct wins ties · Q6 TLS+ALPN on 443 plus WebSocket · Q7 QUIC DATAGRAM.

Open: Q8 (port mapping + connection checklist implementation) waits on the comparison job. Next: one measurement job builds a perf kit for (a) seam A vs B, µs per packet and CPU per Gbit/s, and (b) the Q8 candidates' stripped static size delta (x86_64/aarch64), cargo tree -e features, idle tasks/timers and RSS at 1/100/1,000 peers. The orchestrator runs it on a perf-test VM.

**Grill round 1 answered by the owner (2026-10-30), recorded as D313 on dev (db8c1bdf6).** Q1 seam **C** · Q2 home relay with pre-signed tickets (after coordinator-only works) · Q3 any relay in the mesh · Q4 counters-gated, sequential prediction first · Q5 max(5 ms, 15%) over 5 samples / ≥3 s, 10 s dwell, direct wins ties · Q6 TLS+ALPN on 443 plus WebSocket · Q7 QUIC DATAGRAM. Open: **Q8** (port mapping + connection checklist implementation) waits on the comparison job. Next: one measurement job builds a perf kit for (a) seam A vs B, µs per packet and CPU per Gbit/s, and (b) the Q8 candidates' stripped static size delta (x86_64/aarch64), `cargo tree -e features`, idle tasks/timers and RSS at 1/100/1,000 peers. The orchestrator runs it on a perf-test VM.
Author
Owner

Starting measurement-only research on mesh-measure-220, base db8c1bdf6 (origin/dev). Read #220 body/all 30 comments, shared rules, repository contracts and D313. Preparing standalone seam perf relay kit and throwaway Q8 size probes; no shipped dependency or product changes. Build-host smoke will be labelled separately from perf evidence.

Starting measurement-only research on mesh-measure-220, base db8c1bdf6 (origin/dev). Read #220 body/all 30 comments, shared rules, repository contracts and D313. Preparing standalone seam perf relay kit and throwaway Q8 size probes; no shipped dependency or product changes. Build-host smoke will be labelled separately from perf evidence.
Author
Owner

Measurement checkpoint: branch mesh-measure-220, base db8c1bdf6. Pinned stripped-static baselines built sequentially: x86_64 21,437,872 B; aarch64 19,601,832 B. No product inputs changed. Q8 feature builds are running in a disposable origin/dev export; lock resolution retained every baseline (name, version, source) tuple.

Finding: neither str0m nor its extracted ICE crate is appears in origin/dev Cargo.lock at this base. Measuring is 0.11.1 as a new dependency; the design's zero-increment premise does not apply here. A caller-owned raw/packet socket ICE probe is being added.

Parser finding (source inspection, not a runtime security verdict): portmapper 0.20.0 depends on igd-next 0.17, whose common/parsing.rs entry points call xmltree::Element::parse directly. The separate igd-next 0.18 candidate adds MAX_RESPONSE_BYTES and MAX_XML_DEPTH=64 checks. Feature trees, full license inventory and per-target deltas will be attached with final results.

Kernel-only functional namespace smoke passed inside a disposable privileged Debian 13 container on the aarch64 build host; before/after links, netns, module names, qdiscs and net.* sysctls diff was empty. Build host, not perf evidence. Host sudo requires a password, so the perf VM root/systemd wrapper remains for orchestrator execution.

Measurement checkpoint: branch mesh-measure-220, base db8c1bdf6. Pinned stripped-static baselines built sequentially: x86_64 21,437,872 B; aarch64 19,601,832 B. No product inputs changed. Q8 feature builds are running in a disposable origin/dev export; lock resolution retained every baseline (name, version, source) tuple. Finding: neither str0m nor its extracted ICE crate is appears in origin/dev Cargo.lock at this base. Measuring is 0.11.1 as a new dependency; the design's zero-increment premise does not apply here. A caller-owned raw/packet socket ICE probe is being added. Parser finding (source inspection, not a runtime security verdict): portmapper 0.20.0 depends on igd-next 0.17, whose common/parsing.rs entry points call xmltree::Element::parse directly. The separate igd-next 0.18 candidate adds MAX_RESPONSE_BYTES and MAX_XML_DEPTH=64 checks. Feature trees, full license inventory and per-target deltas will be attached with final results. Kernel-only functional namespace smoke passed inside a disposable privileged Debian 13 container on the aarch64 build host; before/after links, netns, module names, qdiscs and net.* sysctls diff was empty. Build host, not perf evidence. Host sudo requires a password, so the perf VM root/systemd wrapper remains for orchestrator execution.
Author
Owner

Step 1 committed/pushed: 0e3fc2b85, standalone Q1 kit and real-port ICE probe. Functional smoke passed baseline/A/B, 1-pair shim allocation and both raw ICE endpoints (12 sent/12 accepted at each endpoint); all five before/after configuration diffs were empty. Build host, not perf evidence. Fixed an early probe exit that stopped replying before the other endpoint nominated; the passing probe keeps servicing packets through its bounded proof window. No product source/dependency changes. Q8 final size sweep is running sequentially; final full gate remains pending.

build-host-smoke.log
baseline-artifacts.json

Step 1 committed/pushed: 0e3fc2b85, standalone Q1 kit and real-port ICE probe. Functional smoke passed baseline/A/B, 1-pair shim allocation and both raw ICE endpoints (12 sent/12 accepted at each endpoint); all five before/after configuration diffs were empty. Build host, not perf evidence. Fixed an early probe exit that stopped replying before the other endpoint nominated; the passing probe keeps servicing packets through its bounded proof window. No product source/dependency changes. Q8 final size sweep is running sequentially; final full gate remains pending. [build-host-smoke.log](https://git.kayg.org/attachments/a04b7263-0588-49e6-bb0a-aff34f0579d4) [baseline-artifacts.json](https://git.kayg.org/attachments/aeb37fa4-c937-42ab-88f8-f6a02b1aae3b)
Author
Owner

Q8 measurement checkpoint at 8bb97ab88c8de18e40b7c65dfb5a9392a901cc21 (base db8c1bdf6242). All 14 stripped static candidate/harness builds succeeded with the pinned recipe, real reachable call paths, and all 393 original package tuples retained. No shipped manifest, lockfile or production source changed. Baseline x86_64 = 21,437,872 bytes; aarch64 = 19,601,832. Shared harness overhead = +896 / +4,864 bytes.

Candidate x86_64 bytes Δ base aarch64 bytes Δ base Δ harness x86/ARM
own-map estimate 21,447,280 +9,408 19,614,840 +13,008 +8,512 / +8,144
portmapper 23,548,240 +2,110,368 21,412,264 +1,810,432 +2,109,472 / +1,805,568
igd 22,224,976 +787,104 20,290,848 +689,016 +786,208 / +684,152
own-check estimate 21,445,200 +7,328 19,612,280 +10,448 +6,432 / +5,584
is 21,645,712 +207,840 19,786,072 +184,240 +206,944 / +179,376
rtc 21,855,024 +417,152 19,999,920 +398,088 +416,256 / +393,224

The own rows are incomplete prototype estimates; igd also includes our prototype PCP/NAT-PMP path. These are minimum reachable-path deltas, not complete feature budgets. Neither str0m nor is exists in the recorded dev lockfile, so its zero-increment premise does not hold here.

Functional raw-socket proof: both is 0.11.1 endpoints connected while the real kernel WG device owned UDP/20000 (sent=12 accepted=12 each). The final short namespace pass succeeded for baseline/A/B/RSS/raw ICE, with five empty before/after state diffs: build host, not perf evidence. Constructor observations: own variants 0 tasks/timers; portmapper 1 service task and no lease timer without mapping; igd 0; is 0 tasks, initial deadline None, after first tick Some(3s); rtc 0 tasks and empty-constructor deadline None. Features/parser/licence inventory and final gate follow in the finish comment.

mesh-measure-220.tar.gz
build-host-smoke-final.log

Kit SHA256: 0d3b861df6aff85d18b47d5ac9285c5f0fc19b21a04e391c633985e5222eec6c (1,341,333 bytes). Orchestrator: extract into ~/infraternal-perf/mesh-measure-220, then run:

cd ~/infraternal-perf/mesh-measure-220
sudo ./run.sh > ../mesh-measure-220.jsonl 2>&1

Each case takes its own perf lock inside the root perf.slice scope; do not add an outer perf-run lock. The full perf run is pending orchestrator relay. No build-host timing is offered as performance evidence.

The first full gate exposed my scratch-path setup error: its short TMPDIR/test-state path was a symlink, deliberately refused by replacement admission. I will rerun with a real private directory. The separate .sha256 upload was rejected with HTTP 422; the digest is recorded above.

Q8 measurement checkpoint at `8bb97ab88c8de18e40b7c65dfb5a9392a901cc21` (base `db8c1bdf6242`). All 14 stripped static candidate/harness builds succeeded with the pinned recipe, real reachable call paths, and all 393 original package tuples retained. No shipped manifest, lockfile or production source changed. Baseline x86_64 = 21,437,872 bytes; aarch64 = 19,601,832. Shared harness overhead = +896 / +4,864 bytes. | Candidate | x86_64 bytes | Δ base | aarch64 bytes | Δ base | Δ harness x86/ARM | |---|---:|---:|---:|---:|---:| | own-map **estimate** | 21,447,280 | +9,408 | 19,614,840 | +13,008 | +8,512 / +8,144 | | portmapper | 23,548,240 | +2,110,368 | 21,412,264 | +1,810,432 | +2,109,472 / +1,805,568 | | igd | 22,224,976 | +787,104 | 20,290,848 | +689,016 | +786,208 / +684,152 | | own-check **estimate** | 21,445,200 | +7,328 | 19,612,280 | +10,448 | +6,432 / +5,584 | | is | 21,645,712 | +207,840 | 19,786,072 | +184,240 | +206,944 / +179,376 | | rtc | 21,855,024 | +417,152 | 19,999,920 | +398,088 | +416,256 / +393,224 | The own rows are incomplete prototype estimates; igd also includes our prototype PCP/NAT-PMP path. These are minimum reachable-path deltas, not complete feature budgets. Neither str0m nor `is` exists in the recorded dev lockfile, so its zero-increment premise does not hold here. Functional raw-socket proof: both `is` 0.11.1 endpoints connected while the real kernel WG device owned UDP/20000 (`sent=12 accepted=12` each). The final short namespace pass succeeded for baseline/A/B/RSS/raw ICE, with five empty before/after state diffs: **build host, not perf evidence**. Constructor observations: own variants 0 tasks/timers; portmapper 1 service task and no lease timer without mapping; igd 0; is 0 tasks, initial deadline None, after first tick Some(3s); rtc 0 tasks and empty-constructor deadline None. Features/parser/licence inventory and final gate follow in the finish comment. [mesh-measure-220.tar.gz](https://git.kayg.org/attachments/1ea0ad6f-f50e-484d-8b9b-df24d1d8bcfe) [build-host-smoke-final.log](https://git.kayg.org/attachments/43e2c5f5-b3eb-4c01-840b-e3f5cd33a7a6) Kit SHA256: `0d3b861df6aff85d18b47d5ac9285c5f0fc19b21a04e391c633985e5222eec6c` (1,341,333 bytes). Orchestrator: extract into `~/infraternal-perf/mesh-measure-220`, then run: ```sh cd ~/infraternal-perf/mesh-measure-220 sudo ./run.sh > ../mesh-measure-220.jsonl 2>&1 ``` Each case takes its own perf lock inside the root perf.slice scope; do not add an outer perf-run lock. The full perf run is pending orchestrator relay. No build-host timing is offered as performance evidence. The first full gate exposed my scratch-path setup error: its short TMPDIR/test-state path was a symlink, deliberately refused by replacement admission. I will rerun with a real private directory. The separate `.sha256` upload was rejected with HTTP 422; the digest is recorded above.
Author
Owner

Measurement handoff, head 8bb97ab88c8de18e40b7c65dfb5a9392a901cc21, pushed to mesh-measure-220 only. Base db8c1bdf6242; no foundational choice or production dependency change.

Q8 stripped static bytes (x86_64 / aarch64), pinned static recipe and original baseline package versions. Baselines: 21,437,872 / 19,601,832 bytes. Harness-only deltas: +896 / +4,864 bytes.

Candidate x86_64 bytes Δ base aarch64 bytes Δ base Δ harness x86/ARM
own-map estimate 21,447,280 +9,408 19,614,840 +13,008 +8,512 / +8,144
portmapper 23,548,240 +2,110,368 21,412,264 +1,810,432 +2,109,472 / +1,805,568
igd 22,224,976 +787,104 20,290,848 +689,016 +786,208 / +684,152
own-check estimate 21,445,200 +7,328 19,612,280 +10,448 +6,432 / +5,584
is 21,645,712 +207,840 19,786,072 +184,240 +206,944 / +179,376
rtc 21,855,024 +417,152 19,999,920 +398,088 +416,256 / +393,224
Candidate Features / unused state Licence Untrusted parser surface
Own mapping estimate std-only; 0 tasks/timers AGPL-3.0-only PCP/NAT-PMP bounded packet sketch; 64 KiB HTTP/XML tag sketch. Missing lease/auth/schema and complete SOAP handling.
portmapper 0.20.0 defaults disabled; netwatch, metrics stubs; igd-next 0.17 default + aio_tokio still pulls sync/attohttpc; 1 service task, no lease timer without mapping MIT OR Apache-2.0; attohttpc MPL-2.0 secondary-licence route Connected gateway UDP response with nonce/protocol/port checks. Older IGD HTTP collect/XML tree lacks 0.18 explicit byte/depth limits.
igd-next 0.18 + own PCP/NAT-PMP no defaults, aio_tokio; 0 service tasks/unused timers MIT + own AGPL-3.0-only Gateway HTTP description/schema/SOAP XML; explicit 1 MiB / depth 64 limits. Own protocol portion remains a prototype estimate.
Own checker estimate prior discovery.rs fixture + bounded 4-attempt sketch; 0 tasks/timers AGPL-3.0-only Observation/checklist fixture only; production authenticated receive and pacing omitted.
str0m ICE core: is 0.11.1 default SHA1; hmac/crc/serde/str0m-proto; 0 tasks; fresh deadline None, first tick Some(3s) MIT OR Apache-2.0 Caller-fed STUN header/cookie/length/attributes before identity/integrity checks; caller must bound ingress/state.
rtc-ice 0.21.1 default crypto-ring; rtc-shared/crypto/stun/mdns/sansio; 0 tasks; empty constructor deadline None MIT OR Apache-2.0 Caller-fed STUN/mDNS/candidate parsers; no XML; caller must bound ingress and candidate state.

All inventoried licence options are compatible with AGPL; no candidate dependency is shipped by this branch. The new MPL-2.0 attohttpc source does not carry an additional incompatible-with-secondary-licences notice. Full inventories and feature trees are attached for review; this is not a new foundational choice.

Unused task counts/deadlines are functional observations plus source inspection, not measured wakeup/CPU acceptance. Minimal active paths exited successfully in an isolated container; portmapper reported no gateway, so no successful real-router mapping is claimed.

The is row measures the exact extracted ICE core used by str0m 0.24.1, not the unrelated media stack. Neither package was present in the recorded dev baseline. The committed raw-socket probe connected both endpoints next to the real kernel WG port (sent=12 accepted=12 each); it is a socket/API proof, not a production authenticated demultiplexer or NAT/conntrack proof.

Q1 kit: static workers for both architectures, sources, run script, README and notices; 72 data cases (baseline/A/B-filter-only/B-with-probes × 3 payload sizes × 1/8 flows × 3 reps), RSS 1/100/1,000 active pairs, and raw ICE proof. Each case independently locks through perf-lock inside a root systemd perf.slice scope, with timeout/cleanup and before/after host configuration diff. The five-case container smoke passed with every diff empty: build host, not perf evidence. The real perf VM numbers and root systemd wrapper validation await orchestrator relay; no build-host timing/RSS is offered as performance evidence. Broader #220 §8 product NAT/roaming/flip-back/TLS443/MTU/handoff gates are not claimed.

Kit SHA256 0d3b861df6aff85d18b47d5ac9285c5f0fc19b21a04e391c633985e5222eec6c, 1,341,333 bytes. Exact orchestrator command after extracting the attached archive:

cd ~/infraternal-perf/mesh-measure-220
sudo ./run.sh > ../mesh-measure-220.jsonl 2>&1

Do not add an outer perf-run lock: every case locks separately. The expected measurement run is below 20 minutes (30-minute budget, excluding queue time).

Production x86_64 and aarch64 artifacts are SHA256-identical to baseline: 0 bytes shipped growth on both targets. Final scripts/verify fast gate details and cleanup receipts follow below. Initial test-environment errors (symlink ancestor, then a 108-byte Unix socket path) are preserved in the logs; the final run uses real private directories and a 107-byte socket path. No production code or size cap was adjusted.

Final gate is red, not waived: scripts/verify fast exited 1. Nine Rust rows pass; test fails in the unchanged SSH streaming fixture and static builds fails the unchanged baseline budgets. The TypeScript phase is not reached after the Rust failure. Exact test recheck on this branch and an untouched git archive db8c1bdf6242 source export both fail identically; their logs are in the evidence bundle. No unrelated product fix or budget ratchet is included.

Verbatim final Rust gate output:

+---------------------------+--------------+
| Rust gate                 | Result       |
+---------------------------+--------------+
| fmt                       | PASS         |
| VT native fmt             | PASS         |
| CLI plugin compile-out    | PASS         |
| clippy                    | PASS         |
| SSH runtime test helper   | PASS         |
| real-binary fixture paths | PASS         |
| test                      | FAIL (101)   |
| SSH inode reuse fixture   | PASS         |
| shim process-per-user     | PASS         |
| cargo-deny                | PASS         |
| static builds             | FAIL (1)     |
+---------------------------+--------------+

Verbatim relevant diagnostics:

streaming transfer stalled or refused; hashed=true staged_bytes=Ok(552579)
PT_INTERP: absent; size: 21437872 bytes; cap: 21431472 bytes; delta: +6400 bytes
binary exceeds the 21431472-byte size budget: 21437872 bytes
PT_INTERP: absent; size: 19601832 bytes; cap: 19605896 bytes; delta: -4064 bytes
binary shrank to 19601832 bytes; ratchet the aarch64-unknown-linux-musl ceiling down from 19605896 bytes

The research-specific cleanup regression suite passes all four cases, static C/ICE builds and the isolated Q8 probe checks pass, and both raw ICE endpoints connect. The full gate transcript remains attached, including the failure; this is a measurement handoff, not a green product acceptance claim.

Cleanup completed: CARGO_TARGET_DIR, candidate binaries, both disposable source exports, relay staging, job-local tools, temporary roots and the unique smoke image deleted. No process is executing from the job artifacts. Branch worktree clean; remote head matches 8bb97ab88c8de18e40b7c65dfb5a9392a901cc21. Final kit retained for relay. Issue left open.

Attachments (83-file evidence bundle includes resolved Q8 Cargo.lock, complete feature trees, licences, all artifact/hash receipts, build logs, active/idle functional probes, review, baseline failure and cleanup receipts):

mesh-measure-220.tar.gz
build-host-smoke-final.log
q8-evidence.tar.gz
verify-fast.log
cleanup-receipt.json

Evidence bundle SHA256: ffdf5990041799c34f07e9aec662b10c820d4a7aa164fa70fac227d1d673729d.

Measurement handoff, head `8bb97ab88c8de18e40b7c65dfb5a9392a901cc21`, pushed to `mesh-measure-220` only. Base `db8c1bdf6242`; no foundational choice or production dependency change. Q8 stripped static bytes (x86_64 / aarch64), pinned static recipe and original baseline package versions. Baselines: 21,437,872 / 19,601,832 bytes. Harness-only deltas: +896 / +4,864 bytes. | Candidate | x86_64 bytes | Δ base | aarch64 bytes | Δ base | Δ harness x86/ARM | |---|---:|---:|---:|---:|---:| | own-map **estimate** | 21,447,280 | +9,408 | 19,614,840 | +13,008 | +8,512 / +8,144 | | portmapper | 23,548,240 | +2,110,368 | 21,412,264 | +1,810,432 | +2,109,472 / +1,805,568 | | igd | 22,224,976 | +787,104 | 20,290,848 | +689,016 | +786,208 / +684,152 | | own-check **estimate** | 21,445,200 | +7,328 | 19,612,280 | +10,448 | +6,432 / +5,584 | | is | 21,645,712 | +207,840 | 19,786,072 | +184,240 | +206,944 / +179,376 | | rtc | 21,855,024 | +417,152 | 19,999,920 | +398,088 | +416,256 / +393,224 | | Candidate | Features / unused state | Licence | Untrusted parser surface | |---|---|---|---| | Own mapping estimate | std-only; 0 tasks/timers | AGPL-3.0-only | PCP/NAT-PMP bounded packet sketch; 64 KiB HTTP/XML tag sketch. Missing lease/auth/schema and complete SOAP handling. | | portmapper 0.20.0 | defaults disabled; netwatch, metrics stubs; igd-next 0.17 default + aio_tokio still pulls sync/attohttpc; 1 service task, no lease timer without mapping | MIT OR Apache-2.0; attohttpc MPL-2.0 secondary-licence route | Connected gateway UDP response with nonce/protocol/port checks. Older IGD HTTP collect/XML tree lacks 0.18 explicit byte/depth limits. | | igd-next 0.18 + own PCP/NAT-PMP | no defaults, aio_tokio; 0 service tasks/unused timers | MIT + own AGPL-3.0-only | Gateway HTTP description/schema/SOAP XML; explicit 1 MiB / depth 64 limits. Own protocol portion remains a prototype estimate. | | Own checker estimate | prior discovery.rs fixture + bounded 4-attempt sketch; 0 tasks/timers | AGPL-3.0-only | Observation/checklist fixture only; production authenticated receive and pacing omitted. | | str0m ICE core: is 0.11.1 | default SHA1; hmac/crc/serde/str0m-proto; 0 tasks; fresh deadline None, first tick Some(3s) | MIT OR Apache-2.0 | Caller-fed STUN header/cookie/length/attributes before identity/integrity checks; caller must bound ingress/state. | | rtc-ice 0.21.1 | default crypto-ring; rtc-shared/crypto/stun/mdns/sansio; 0 tasks; empty constructor deadline None | MIT OR Apache-2.0 | Caller-fed STUN/mDNS/candidate parsers; no XML; caller must bound ingress and candidate state. | All inventoried licence options are compatible with AGPL; no candidate dependency is shipped by this branch. The new MPL-2.0 attohttpc source does not carry an additional incompatible-with-secondary-licences notice. Full inventories and feature trees are attached for review; this is not a new foundational choice. Unused task counts/deadlines are functional observations plus source inspection, not measured wakeup/CPU acceptance. Minimal active paths exited successfully in an isolated container; portmapper reported no gateway, so no successful real-router mapping is claimed. The `is` row measures the exact extracted ICE core used by str0m 0.24.1, not the unrelated media stack. Neither package was present in the recorded dev baseline. The committed raw-socket probe connected both endpoints next to the real kernel WG port (`sent=12 accepted=12` each); it is a socket/API proof, not a production authenticated demultiplexer or NAT/conntrack proof. Q1 kit: static workers for both architectures, sources, run script, README and notices; 72 data cases (baseline/A/B-filter-only/B-with-probes × 3 payload sizes × 1/8 flows × 3 reps), RSS 1/100/1,000 active pairs, and raw ICE proof. Each case independently locks through perf-lock inside a root systemd perf.slice scope, with timeout/cleanup and before/after host configuration diff. The five-case container smoke passed with every diff empty: **build host, not perf evidence**. The real perf VM numbers and root systemd wrapper validation await orchestrator relay; no build-host timing/RSS is offered as performance evidence. Broader #220 §8 product NAT/roaming/flip-back/TLS443/MTU/handoff gates are not claimed. Kit SHA256 `0d3b861df6aff85d18b47d5ac9285c5f0fc19b21a04e391c633985e5222eec6c`, 1,341,333 bytes. Exact orchestrator command after extracting the attached archive: ```sh cd ~/infraternal-perf/mesh-measure-220 sudo ./run.sh > ../mesh-measure-220.jsonl 2>&1 ``` Do not add an outer perf-run lock: every case locks separately. The expected measurement run is below 20 minutes (30-minute budget, excluding queue time). Production x86_64 and aarch64 artifacts are SHA256-identical to baseline: **0 bytes shipped growth** on both targets. Final `scripts/verify fast` gate details and cleanup receipts follow below. Initial test-environment errors (symlink ancestor, then a 108-byte Unix socket path) are preserved in the logs; the final run uses real private directories and a 107-byte socket path. No production code or size cap was adjusted. Final gate is **red**, not waived: `scripts/verify fast` exited 1. Nine Rust rows pass; `test` fails in the unchanged SSH streaming fixture and `static builds` fails the unchanged baseline budgets. The TypeScript phase is not reached after the Rust failure. Exact test recheck on this branch and an untouched `git archive db8c1bdf6242` source export both fail identically; their logs are in the evidence bundle. No unrelated product fix or budget ratchet is included. Verbatim final Rust gate output: ```text +---------------------------+--------------+ | Rust gate | Result | +---------------------------+--------------+ | fmt | PASS | | VT native fmt | PASS | | CLI plugin compile-out | PASS | | clippy | PASS | | SSH runtime test helper | PASS | | real-binary fixture paths | PASS | | test | FAIL (101) | | SSH inode reuse fixture | PASS | | shim process-per-user | PASS | | cargo-deny | PASS | | static builds | FAIL (1) | +---------------------------+--------------+ ``` Verbatim relevant diagnostics: ```text streaming transfer stalled or refused; hashed=true staged_bytes=Ok(552579) PT_INTERP: absent; size: 21437872 bytes; cap: 21431472 bytes; delta: +6400 bytes binary exceeds the 21431472-byte size budget: 21437872 bytes PT_INTERP: absent; size: 19601832 bytes; cap: 19605896 bytes; delta: -4064 bytes binary shrank to 19601832 bytes; ratchet the aarch64-unknown-linux-musl ceiling down from 19605896 bytes ``` The research-specific cleanup regression suite passes all four cases, static C/ICE builds and the isolated Q8 probe checks pass, and both raw ICE endpoints connect. The full gate transcript remains attached, including the failure; this is a measurement handoff, not a green product acceptance claim. Cleanup completed: CARGO_TARGET_DIR, candidate binaries, both disposable source exports, relay staging, job-local tools, temporary roots and the unique smoke image deleted. No process is executing from the job artifacts. Branch worktree clean; remote head matches `8bb97ab88c8de18e40b7c65dfb5a9392a901cc21`. Final kit retained for relay. Issue left open. Attachments (83-file evidence bundle includes resolved Q8 Cargo.lock, complete feature trees, licences, all artifact/hash receipts, build logs, active/idle functional probes, review, baseline failure and cleanup receipts): [mesh-measure-220.tar.gz](https://git.kayg.org/attachments/1ea0ad6f-f50e-484d-8b9b-df24d1d8bcfe) [build-host-smoke-final.log](https://git.kayg.org/attachments/43e2c5f5-b3eb-4c01-840b-e3f5cd33a7a6) [q8-evidence.tar.gz](https://git.kayg.org/attachments/8a503109-9cad-4e65-a013-4a2af9e5e78a) [verify-fast.log](https://git.kayg.org/attachments/0f3feb0c-4c7b-4236-9039-3c5aec9dcbbd) [cleanup-receipt.json](https://git.kayg.org/attachments/f10fa010-5c32-4991-ad8d-b5a0fa52dea9) Evidence bundle SHA256: `ffdf5990041799c34f07e9aec662b10c820d4a7aa164fa70fac227d1d673729d`.
Author
Owner

#220 perf-VM results (perf-test-03, 4 cores / 8 GB, Debian 13, kernel 6.12.113; kit 0d3b861d… + orchestrator fix ced872e68). 77 cases, one perf-lock hold each, every per-case state diff empty, exit 0. The run was serialised with other work on the VM through the shared lock. Medians over 3 repetitions:

payload flows seam one-way p50 / p99 µs Gbit/s core-s per Gbit loss
1400 B 1 kernel baseline 155 / 468 1.48 1.88 0%
1400 B 1 B (filter + probes) 168 / 504 1.32–1.50 1.93–2.07 0%
1400 B 1 A (userspace shim) 460 / 1,487 0.64 3.94 0%
1400 B 8 baseline 3,028 / 12,215 2.09 1.81 12%
1400 B 8 B 3,331–3,415 / ~12–13k 2.01–2.11 1.84–1.88 13%
1400 B 8 A 3,977 / 8,035 0.52 5.95 76%
512 B 1 baseline / B / A 142 / 148 / 756 p50 0.56 / 0.56 / 0.25 4.75 / 4.86 / 9.89 0%
64 B 1 baseline / B / A 188 / 146 / 492 p50 0.078 / 0.067 / 0.033 34 / 38 / 75 0%

(The 8-flow loss in every seam comes from the bounded 64-packet window saturating a 4-core VM; compare the seams with each other, not against zero.)

  • Seam B costs about the same as the kernel: within ~3–10% on throughput and CPU at every size. The discovery probes add nothing measurable. Discovery send→observe: p50 ≈ 29–45 µs, p99 ≈ 48–86 µs (100/100 observed per case).
  • Seam A costs about 2× the CPU per Gbit, less than half the single-flow throughput, and +300–600 µs one-way p50. Under 8 flows it drops 76%.
  • Shim memory for relayed pairs is negligible: 96 KiB per endpoint at 1 pair, 132 KiB at 1,000 pairs, about 270 B per pair at 1,000.
  • str0m's ICE agent drives a caller-owned raw socket on the kernel WireGuard port: PASS raw ICE connected in both directions (11/11 and 11/12 accepted).

Reading: this supports D313's seam C. Direct paths stay in the kernel at no measurable cost, and the shim is used only where the relay hop dominates. Its memory per pair is too small to matter at D170 scale.

Q8 (owner choice), size per candidate (x86_64 / aarch64): own PCP/NAT-PMP ≈ 9.4 / 13.0 KB (estimate) · igd-next+own 787 / 689 KB · portmapper 2.11 / 1.81 MB; own checklist ≈ 7.3 / 10.4 KB (estimate) · str0m ICE 208 / 184 KB (fits seam C, proven above) · rtc-ice 417 / 398 KB.

Raw JSONL kept outside the repo on the orchestrator host (~/.cache/codex-scratch/mesh-220/evidence/). perf-test-03 before/after diff: empty.

**#220 perf-VM results (perf-test-03, 4 cores / 8 GB, Debian 13, kernel 6.12.113; kit `0d3b861d…` + orchestrator fix ced872e68).** 77 cases, one perf-lock hold each, every per-case state diff empty, exit 0. The run was serialised with other work on the VM through the shared lock. Medians over 3 repetitions: | payload | flows | seam | one-way p50 / p99 µs | Gbit/s | core-s per Gbit | loss | |---|---|---|---:|---:|---:|---:| | 1400 B | 1 | kernel baseline | 155 / 468 | 1.48 | 1.88 | 0% | | 1400 B | 1 | **B** (filter + probes) | 168 / 504 | 1.32–1.50 | 1.93–2.07 | 0% | | 1400 B | 1 | **A** (userspace shim) | 460 / 1,487 | 0.64 | 3.94 | 0% | | 1400 B | 8 | baseline | 3,028 / 12,215 | 2.09 | 1.81 | 12% | | 1400 B | 8 | B | 3,331–3,415 / ~12–13k | 2.01–2.11 | 1.84–1.88 | 13% | | 1400 B | 8 | A | 3,977 / 8,035 | 0.52 | 5.95 | **76%** | | 512 B | 1 | baseline / B / A | 142 / 148 / 756 p50 | 0.56 / 0.56 / 0.25 | 4.75 / 4.86 / 9.89 | 0% | | 64 B | 1 | baseline / B / A | 188 / 146 / 492 p50 | 0.078 / 0.067 / 0.033 | 34 / 38 / 75 | 0% | (The 8-flow loss in every seam comes from the bounded 64-packet window saturating a 4-core VM; compare the seams with each other, not against zero.) - **Seam B costs about the same as the kernel:** within ~3–10% on throughput and CPU at every size. The discovery probes add nothing measurable. Discovery send→observe: p50 ≈ 29–45 µs, p99 ≈ 48–86 µs (100/100 observed per case). - **Seam A costs** about 2× the CPU per Gbit, less than half the single-flow throughput, and +300–600 µs one-way p50. Under 8 flows it drops 76%. - **Shim memory for relayed pairs is negligible:** 96 KiB per endpoint at 1 pair, 132 KiB at 1,000 pairs, about **270 B per pair** at 1,000. - **str0m's ICE agent drives a caller-owned raw socket on the kernel WireGuard port:** `PASS raw ICE connected` in both directions (11/11 and 11/12 accepted). **Reading:** this supports D313's seam C. Direct paths stay in the kernel at no measurable cost, and the shim is used only where the relay hop dominates. Its memory per pair is too small to matter at D170 scale. **Q8 (owner choice), size per candidate (x86_64 / aarch64):** own PCP/NAT-PMP ≈ 9.4 / 13.0 KB (estimate) · igd-next+own 787 / 689 KB · portmapper 2.11 / 1.81 MB; own checklist ≈ 7.3 / 10.4 KB (estimate) · str0m ICE 208 / 184 KB (fits seam C, proven above) · rtc-ice 417 / 398 KB. Raw JSONL kept outside the repo on the orchestrator host (`~/.cache/codex-scratch/mesh-220/evidence/`). perf-test-03 before/after diff: empty.
Author
Owner

Starting slice 1 of the D313 order on branch mesh-relay-220, base a4f15f8f7 (origin/dev, D316). Build host 10by32-02-std (aarch64, root available for namespace tests). Read #220 (all 36 comments), D163/D166/D176/D185/D196/D197/D201/D313/D316, research/mesh-measure-220 (8bb97ab88/ced872e68) and the earlier mesh-relay checkpoint commits 1127aa19d (discovery book) and 176aca846 (discovery contract), which are not on any remote branch now; I will carry them forward rather than redo them. Slice plan follows in the next comment.

Starting slice 1 of the D313 order on branch `mesh-relay-220`, base `a4f15f8f7` (origin/dev, D316). Build host 10by32-02-std (aarch64, root available for namespace tests). Read #220 (all 36 comments), D163/D166/D176/D185/D196/D197/D201/D313/D316, `research/mesh-measure-220` (8bb97ab88/ced872e68) and the earlier `mesh-relay` checkpoint commits 1127aa19d (discovery book) and 176aca846 (discovery contract), which are not on any remote branch now; I will carry them forward rather than redo them. Slice plan follows in the next comment.
Author
Owner

Slice plan for the whole D313/D316 order (mesh-relay-220)

Sizes are estimates until measured with the pinned static recipe (D243: >16 KB needs a justification here, >64 KB goes to the owner). Every slice: atomic commits, fmt/clippy/tests per commit, scripts/verify iterate while working, scripts/verify fast on the head, numbers labelled by machine.

# Slice Main pieces Tests and gates Expected size
1 Seam C observation + QUIC DATAGRAM relay data path (this job) carry forward the discovery book (1127aa19d) and its contract (176aca846); mesh::seam: probes sent from the kernel WG port through a send-only IPPROTO_RAW socket, replies received on one AF_PACKET socket with a classic BPF filter (WG data never leaves the kernel); relay plugin runtime: relay service on quinn with ALPN infraternal-relay/1, mTLS against the deployment CA, links formed only when both hosts ask for each other, zero-copy DATAGRAM forwarding of WG ciphertext, bounded tables, probe answers on the relay's own UDP port (first-byte demux in front of quinn); relay client; mesh::relay_path shim: one loopback UDP socket per relayed peer, WG endpoint moved with Client::endpoint, failover to the next relay unit + property tests for every codec and the link table; loopback relay tests; rootless namespace proof (unshare -Urnm): EIM/EIF and EDM NATs, two relays, observation evidence Stable vs DestinationDependent, TCP stream through the relay with SHA-256 equal, relay killed mid-stream (failover to the second relay, gap reported), probes change no WG state and WG data never reaches the capture socket, stale mapping after a NAT rebind (generation retired, stream intact) 0 B in the shipped image if the runtime stays behind a feature the binary does not enable yet; reported either way
2 Relay component wiring + port 443 (Q6) infraternal relay up/down/status real (D198: nothing runs when off), server relay on by default (D163), agent node relays pairs from coordinator data (additive Peer.host, relay list), relay order by end-to-end RTT (D185); TLS on 443 with ALPN demux next to HTTPS + WebSocket upgrade, same link framing UDP-denied namespace (TLS only), HTTP-only proxy path (WebSocket), occupied 443 shared with HTTPS, wrong purpose/name/expired identities refused, CLI --json +30–60 KB (quinn, openssl, tungstenite already in the binary)
3 Rendezvous + simultaneous open (Q2) coordinator-only candidate swap first, then via the home relay with coordinator-signed tickets (works while the coordinator restarts, D166); both sides set the WG endpoint at a shared punch_at, kernel WG handshakes punch 9-cell NAT matrix (EIM/EIF, EIM/APDF, EDM each side) with expected outcomes; time to direct p50/p99; coordinator restart mid-rendezvous +10–20 KB
4 Upgrade and flip-back on the #201 selector (Q5) switch at max(5 ms, 15%) over 5 samples / ≥3 s, 10 s dwell, direct wins ties; shim drain then injection off for a grace period; rollback while the relay is warm flip-back test (late relayed packet after the switch, endpoint stays direct), zero TCP loss across the switch, rollback when direct dies +5–15 KB
5 Own port mapping (D196, D316) PCP, NAT-PMP, minimal UPnP-IGD for the WG port; lease renewal at random 1/2–5/8 of the granted lifetime; per-node switch on by default; parser for router replies with property tests, a robustness corpus and a coverage-guided target namespace gateway responder; mapped candidate proven like any other; malformed replies refused ~9–13 KB (estimate on #220)
6 str0m ICE core for path checks (D316) caller-owned raw socket on the WG port (proven on perf-test-03), candidates from slices 1/3/5 all NAT matrix cells reach connected where expected; re-check on network change +208 KB x86_64 / +184 KB aarch64 (accepted in D316)
7 Node-relay qualification (D131, D313 Q3) server-purpose leaf for node relays through role enrollment, outside-proved reachability per family, resource caps, admin allow-list override wrong purpose/name/expiry refused; eligibility probes; caps hold at 1,000 pairs small

Hard-NAT prediction stays out until the Q4 counters trigger it.

Slice 1 contract notes (additive, no frozen field changes): relay link addresses are the host UUID from the verified urn:infraternal:host:<uuid> SAN; a link forms only when both hosts ask for each other, so the relay needs no ticket in slice 1 and a node cannot inject into a pair that did not ask for it. Probe replies are authenticated with an HMAC keyed from the relay connection's TLS exporter, so an off-path sender cannot plant a mapping.

## Slice plan for the whole D313/D316 order (mesh-relay-220) Sizes are estimates until measured with the pinned static recipe (D243: >16 KB needs a justification here, >64 KB goes to the owner). Every slice: atomic commits, fmt/clippy/tests per commit, `scripts/verify iterate` while working, `scripts/verify fast` on the head, numbers labelled by machine. | # | Slice | Main pieces | Tests and gates | Expected size | |---|---|---|---|---| | 1 | **Seam C observation + QUIC DATAGRAM relay data path (this job)** | carry forward the discovery book (1127aa19d) and its contract (176aca846); `mesh::seam`: probes sent from the kernel WG port through a send-only `IPPROTO_RAW` socket, replies received on one `AF_PACKET` socket with a classic BPF filter (WG data never leaves the kernel); relay plugin runtime: relay service on quinn with ALPN `infraternal-relay/1`, mTLS against the deployment CA, links formed only when both hosts ask for each other, zero-copy DATAGRAM forwarding of WG ciphertext, bounded tables, probe answers on the relay's own UDP port (first-byte demux in front of quinn); relay client; `mesh::relay_path` shim: one loopback UDP socket per relayed peer, WG endpoint moved with `Client::endpoint`, failover to the next relay | unit + property tests for every codec and the link table; loopback relay tests; rootless namespace proof (`unshare -Urnm`): EIM/EIF and EDM NATs, two relays, observation evidence Stable vs DestinationDependent, TCP stream through the relay with SHA-256 equal, relay killed mid-stream (failover to the second relay, gap reported), probes change no WG state and WG data never reaches the capture socket, stale mapping after a NAT rebind (generation retired, stream intact) | 0 B in the shipped image if the runtime stays behind a feature the binary does not enable yet; reported either way | | 2 | Relay component wiring + port 443 (Q6) | `infraternal relay up/down/status` real (D198: nothing runs when off), server relay on by default (D163), agent node relays pairs from coordinator data (additive `Peer.host`, relay list), relay order by end-to-end RTT (D185); TLS on 443 with ALPN demux next to HTTPS + WebSocket upgrade, same link framing | UDP-denied namespace (TLS only), HTTP-only proxy path (WebSocket), occupied 443 shared with HTTPS, wrong purpose/name/expired identities refused, CLI `--json` | +30–60 KB (quinn, openssl, tungstenite already in the binary) | | 3 | Rendezvous + simultaneous open (Q2) | coordinator-only candidate swap first, then via the home relay with coordinator-signed tickets (works while the coordinator restarts, D166); both sides set the WG endpoint at a shared `punch_at`, kernel WG handshakes punch | 9-cell NAT matrix (EIM/EIF, EIM/APDF, EDM each side) with expected outcomes; time to direct p50/p99; coordinator restart mid-rendezvous | +10–20 KB | | 4 | Upgrade and flip-back on the #201 selector (Q5) | switch at max(5 ms, 15%) over 5 samples / ≥3 s, 10 s dwell, direct wins ties; shim drain then injection off for a grace period; rollback while the relay is warm | flip-back test (late relayed packet after the switch, endpoint stays direct), zero TCP loss across the switch, rollback when direct dies | +5–15 KB | | 5 | Own port mapping (D196, D316) | PCP, NAT-PMP, minimal UPnP-IGD for the WG port; lease renewal at random 1/2–5/8 of the granted lifetime; per-node switch on by default; parser for router replies with property tests, a robustness corpus and a coverage-guided target | namespace gateway responder; mapped candidate proven like any other; malformed replies refused | ~9–13 KB (estimate on #220) | | 6 | str0m ICE core for path checks (D316) | caller-owned raw socket on the WG port (proven on perf-test-03), candidates from slices 1/3/5 | all NAT matrix cells reach connected where expected; re-check on network change | +208 KB x86_64 / +184 KB aarch64 (accepted in D316) | | 7 | Node-relay qualification (D131, D313 Q3) | server-purpose leaf for node relays through role enrollment, outside-proved reachability per family, resource caps, admin allow-list override | wrong purpose/name/expiry refused; eligibility probes; caps hold at 1,000 pairs | small | Hard-NAT prediction stays out until the Q4 counters trigger it. Slice 1 contract notes (additive, no frozen field changes): relay link addresses are the host UUID from the verified `urn:infraternal:host:<uuid>` SAN; a link forms only when both hosts ask for each other, so the relay needs no ticket in slice 1 and a node cannot inject into a pair that did not ask for it. Probe replies are authenticated with an HMAC keyed from the relay connection's TLS exporter, so an off-path sender cannot plant a mapping.
Author
Owner

Paused for the VM reboot. Head 4fe5cd912 on mesh-relay-220 (base a4f15f8f7). The slice plan is in the comment above.

Done (pushed, each with fmt/clippy/crate tests green):

  • 894faf492, 8a1a054d0: the earlier discovery book and its contract, carried forward.
  • 39dd91062 relay link/probe codecs (10 unit tests); e2b33c838 relay service + client on quinn DATAGRAM, mutual-consent links, zero-copy forwarding, probe answers on the same UDP port (4 loopback integration tests).
  • 8b2256225 kernel peer endpoint in Counters (it caught one out-of-bounds index on a short payload).
  • 513d2b9a3 seam C: IPPROTO_RAW send from the WG port and an AF_PACKET classic-BPF tap.
  • 744c1bdfb relay MTU discovery bound raised to 1472. Finding: with a cap of 1452, a full WG packet plus the link id is 2 bytes over quinn's DATAGRAM limit, so every full-size packet was dropped and relayed TCP stalled. A 1500-byte IPv6 underlay is still 2 bytes short; that goes to the low-MTU slice.
  • c39e2cf80 relay_path shim: lowest-index relay settling, failover, and a debounced first-session start by the lower host id. Finding: crossing initiations, or two initiations within the kernel's timestamp granularity, cost a full 5 s WG retry.
  • 4fe5cd912 (WIP) fixture + scripts/mesh-relay-proof.py.

Namespace proof, debug build, build host, aarch64, not perf evidence, passed 6/6 runs. EIM/EIF evidence Stable, EDM DestinationDependent. The tap delivered only the 2 probe replies after 32 MiB relayed + 32 MiB direct. Link to first relayed handshake: 38–40 ms. 32 MiB relayed stream: SHA-256 equal, 0 too-large drops. Relay killed 1 s into a 384 MiB stream: detected in 3.0 s (idle timeout), longest gap 3.16 s, SHA-256 equal. With no relay left the pair reports relay: null (100% ping loss) and recovers about 2–3.5 s after a relay restarts. NAT rebind: old mapping retired, new one observed, 16 MiB SHA-256 equal. Host diff of links/netns/modules/net.* was empty on every run; everything ran in unshare -Urnm. Nothing of this job is running and no host namespace or link is left. bridge/veth were already loaded by another job before my last runs (modules_loaded_by_run: []).

Next: replace the WIP commit with a proper one; release-build numbers; contracts/mesh-relay.md; scripts/verify iterate and fast; size notes (expected 0 B shipped, since no binary feature enables relay/runtime yet); delete the target directory.

**Paused for the VM reboot.** Head `4fe5cd912` on `mesh-relay-220` (base `a4f15f8f7`). The slice plan is in the comment above. Done (pushed, each with fmt/clippy/crate tests green): - `894faf492`, `8a1a054d0`: the earlier discovery book and its contract, carried forward. - `39dd91062` relay link/probe codecs (10 unit tests); `e2b33c838` relay service + client on quinn DATAGRAM, mutual-consent links, zero-copy forwarding, probe answers on the same UDP port (4 loopback integration tests). - `8b2256225` kernel peer endpoint in `Counters` (it caught one out-of-bounds index on a short payload). - `513d2b9a3` seam C: `IPPROTO_RAW` send from the WG port and an `AF_PACKET` classic-BPF tap. - `744c1bdfb` relay MTU discovery bound raised to 1472. Finding: with a cap of 1452, a full WG packet plus the link id is 2 bytes over quinn's DATAGRAM limit, so every full-size packet was dropped and relayed TCP stalled. A 1500-byte IPv6 underlay is still 2 bytes short; that goes to the low-MTU slice. - `c39e2cf80` relay_path shim: lowest-index relay settling, failover, and a debounced first-session start by the lower host id. Finding: crossing initiations, or two initiations within the kernel's timestamp granularity, cost a full 5 s WG retry. - `4fe5cd912` (WIP) fixture + `scripts/mesh-relay-proof.py`. Namespace proof, debug build, **build host, aarch64, not perf evidence**, passed 6/6 runs. EIM/EIF evidence `Stable`, EDM `DestinationDependent`. The tap delivered only the 2 probe replies after 32 MiB relayed + 32 MiB direct. Link to first relayed handshake: 38–40 ms. 32 MiB relayed stream: SHA-256 equal, 0 too-large drops. Relay killed 1 s into a 384 MiB stream: detected in 3.0 s (idle timeout), longest gap 3.16 s, SHA-256 equal. With no relay left the pair reports `relay: null` (100% ping loss) and recovers about 2–3.5 s after a relay restarts. NAT rebind: old mapping retired, new one observed, 16 MiB SHA-256 equal. Host diff of links/netns/modules/net.* was empty on every run; everything ran in `unshare -Urnm`. Nothing of this job is running and no host namespace or link is left. bridge/veth were already loaded by another job before my last runs (`modules_loaded_by_run: []`). Next: replace the WIP commit with a proper one; release-build numbers; contracts/mesh-relay.md; `scripts/verify iterate` and `fast`; size notes (expected 0 B shipped, since no binary feature enables `relay`/`runtime` yet); delete the target directory.
Author
Owner

Starting J5 on branch mesh-relay-220 at WIP head 4fe5cd912d. Current origin/dev base is 42ab330358. I read #220 and all 39 comments, D163/D176/D185/D196/D197/D201/D313/D316, and #262 §5. I will preserve the requested pre-rebase snapshot before splitting the WIP and rebasing.

Starting J5 on branch mesh-relay-220 at WIP head 4fe5cd912df5f8a2c4d19c63a48050efe43441cc. Current origin/dev base is 42ab330358eb06d47acade8d0f2c35b9225af74b. I read #220 and all 39 comments, D163/D176/D185/D196/D197/D201/D313/D316, and #262 §5. I will preserve the requested pre-rebase snapshot before splitting the WIP and rebasing.
Author
Owner

Contract proposal before the requested documentation/test edits:

  • Freeze the slice-1 relay frames as a four-byte big-endian nonzero link ID followed by unchanged WireGuard ciphertext; keep link control messages kind-sized and bounded to 21 bytes, with host IDs sourced only from the verified enrollment SAN.
  • Record seam C ownership: kernel WireGuard owns direct-path traffic on its UDP port; one send-only raw socket emits discovery probes with that source port, one per-node AF_PACKET/cBPF tap receives only matching probe replies, and one connected loopback UDP shim socket per relayed peer feeds ciphertext back to the kernel. The relay sees neither plaintext nor keys.
  • Record slice-1 failover as the shared ordered relay list: request all live relays, select the lowest common index, move to the next live relay on loss, and do not fail back to a recovered relay. Measured RTT selection and the #201 switch rule remain later work.
  • Record D313 Q6 status plainly: TLS+ALPN on 443 and the WebSocket upgrade are required for the first release, while this slice currently exposes only QUIC DATAGRAM on UDP; neither 443 path is implemented or claimed here.

Finding: crates/plugins/relay/src/wire.rs has bounded decoder unit cases but no relay-specific fuzz target. I will add a parser fuzz target using the existing cargo-fuzz setup, without a new runtime dependency.

Contract proposal before the requested documentation/test edits: - Freeze the slice-1 relay frames as a four-byte big-endian nonzero link ID followed by unchanged WireGuard ciphertext; keep link control messages kind-sized and bounded to 21 bytes, with host IDs sourced only from the verified enrollment SAN. - Record seam C ownership: kernel WireGuard owns direct-path traffic on its UDP port; one send-only raw socket emits discovery probes with that source port, one per-node AF_PACKET/cBPF tap receives only matching probe replies, and one connected loopback UDP shim socket per relayed peer feeds ciphertext back to the kernel. The relay sees neither plaintext nor keys. - Record slice-1 failover as the shared ordered relay list: request all live relays, select the lowest common index, move to the next live relay on loss, and do not fail back to a recovered relay. Measured RTT selection and the #201 switch rule remain later work. - Record D313 Q6 status plainly: TLS+ALPN on 443 and the WebSocket upgrade are required for the first release, while this slice currently exposes only QUIC DATAGRAM on UDP; neither 443 path is implemented or claimed here. Finding: `crates/plugins/relay/src/wire.rs` has bounded decoder unit cases but no relay-specific fuzz target. I will add a parser fuzz target using the existing cargo-fuzz setup, without a new runtime dependency.
Author
Owner

Finding with evidence: scripts/mesh-relay-proof.py:48 hard-codes LABEL = "build host, aarch64, not perf evidence", while this task requires rerunning the proof on infraternal-dev-01-compute01 (x86_64). I will derive the displayed architecture from the running host so the new receipt cannot mislabel the run.

Finding with evidence: `scripts/mesh-relay-proof.py:48` hard-codes `LABEL = "build host, aarch64, not perf evidence"`, while this task requires rerunning the proof on `infraternal-dev-01-compute01` (x86_64). I will derive the displayed architecture from the running host so the new receipt cannot mislabel the run.
Author
Owner

Environment update at 2026-10-31 16:20 UTC: infraternal-dev-01-compute01 is unreachable (No route to host) and the owner is restarting it. Branch mesh-relay-220 is rebased and pushed at 9bb63f72415e020620de7da7cf9395a5fdcba0a6. Per the updated build-slot rules, I am moving architecture-neutral tests to dev-02 under ~/infraternal-ops/build-slot; x86 static size and final scripts/verify fast remain pending on compute01.

Environment update at 2026-10-31 16:20 UTC: `infraternal-dev-01-compute01` is unreachable (`No route to host`) and the owner is restarting it. Branch `mesh-relay-220` is rebased and pushed at `9bb63f72415e020620de7da7cf9395a5fdcba0a6`. Per the updated build-slot rules, I am moving architecture-neutral tests to dev-02 under `~/infraternal-ops/build-slot`; x86 static size and final `scripts/verify fast` remain pending on compute01.
Author
Owner

Finding: the first queued dev-02 validation stopped at cargo fmt --all --check before tests ran. It reported one rustfmt layout difference in the new relay frame fuzz target. I applied the exact formatter layout to fuzz/fuzz_targets/relay_wire_decode.rs; the feature-on tests and clippy are now requeued under the dev-02 build slot. No behavior changed.

Finding: the first queued dev-02 validation stopped at `cargo fmt --all --check` before tests ran. It reported one rustfmt layout difference in the new relay frame fuzz target. I applied the exact formatter layout to `fuzz/fuzz_targets/relay_wire_decode.rs`; the feature-on tests and clippy are now requeued under the dev-02 build slot. No behavior changed.
Author
Owner

Finding — header-only relay DATAGRAMs were accepted. wire::split returned Some((LinkId(9), [])) for [00 00 00 09], although the frame contract requires ciphertext after the four-byte link ID. I changed the existing unit assertion first and reproduced the failure on dev-02 with cargo test -p infraternal-plugin-relay --lib --features runtime datagram_header_needs: it failed with left: Some((LinkId(9), [])), right: None. I’m fixing the parser to reject header-only frames and adding that malformed case to the reproducible fuzz corpus.

Finding — header-only relay DATAGRAMs were accepted. `wire::split` returned `Some((LinkId(9), []))` for `[00 00 00 09]`, although the frame contract requires ciphertext after the four-byte link ID. I changed the existing unit assertion first and reproduced the failure on dev-02 with `cargo test -p infraternal-plugin-relay --lib --features runtime datagram_header_needs`: it failed with `left: Some((LinkId(9), []))`, `right: None`. I’m fixing the parser to reject header-only frames and adding that malformed case to the reproducible fuzz corpus.
Author
Owner

Follow-up evidence for the empty-payload finding: after freeing only this worktree's 1.9 GB incremental cache to recover from dev-02's full disk, the relay suite ran all 10 unit tests successfully. One loopback integration test then timed out because its first test payload is size 0 and it expects the four-byte link-ID-only frame to arrive. That assertion conflicts with the contract's requirement that the DATAGRAM carry a WireGuard packet. I’m updating the integration test to assert that this malformed frame is dropped and to keep the non-empty forwarding checks.

Follow-up evidence for the empty-payload finding: after freeing only this worktree's 1.9 GB incremental cache to recover from dev-02's full disk, the relay suite ran all 10 unit tests successfully. One loopback integration test then timed out because its first test payload is size 0 and it expects the four-byte link-ID-only frame to arrive. That assertion conflicts with the contract's requirement that the DATAGRAM carry a WireGuard packet. I’m updating the integration test to assert that this malformed frame is dropped and to keep the non-empty forwarding checks.
Author
Owner

Finding from the first compute01 scripts/verify fast run at 608f95c0c69e0d4cb4d19a62abe994fab810eeea:

  • The x86 static image measured 21,432,880 bytes against the 21,431,472-byte D243 cap (delta: +1408 bytes). The aarch64 image measured 19,606,664 bytes against 19,605,896 (delta: +768 bytes).
  • replay_size_review failed in the shared test fixture because its fallback Unix socket path was 109 bytes, over Linux's 108-byte bound.

The feature graph shows the new seam C module and socket2 dependency under mesh/agent, which is enabled in the default build. I have moved both under the opt-in mesh/relay feature in the working tree. I am reproducing the socket-path failure on the clean origin/dev worktree, then I will rerun the full gate with a short fixture-state root and recheck both static sizes.

Finding from the first compute01 `scripts/verify fast` run at `608f95c0c69e0d4cb4d19a62abe994fab810eeea`: - The x86 static image measured 21,432,880 bytes against the 21,431,472-byte D243 cap (`delta: +1408 bytes`). The aarch64 image measured 19,606,664 bytes against 19,605,896 (`delta: +768 bytes`). - `replay_size_review` failed in the shared test fixture because its fallback Unix socket path was 109 bytes, over Linux's 108-byte bound. The feature graph shows the new seam C module and `socket2` dependency under `mesh/agent`, which is enabled in the default build. I have moved both under the opt-in `mesh/relay` feature in the working tree. I am reproducing the socket-path failure on the clean `origin/dev` worktree, then I will rerun the full gate with a short fixture-state root and recheck both static sizes.
Author
Owner

Follow-up evidence for the fixture-path failure: the clean baseline worktree on compute01 is detached at 42ab330358eb06d47acade8d0f2c35b9225af74b. Calling the unchanged baseline scripts/agent_fixture.py::fixture_state("rr257") with both test-state overrides unset produced the same error:

ValueError: test state path is too long for an AF_UNIX socket after fallback: 109 bytes

This reproduces the failure on unmodified origin/dev; the final gate will use the fixture's supported INFRATERNAL_AGENT_TEST_STATE_ROOT=/tmp/mr220 override. No product code change is needed for the test-path issue.

Follow-up evidence for the fixture-path failure: the clean baseline worktree on compute01 is detached at `42ab330358eb06d47acade8d0f2c35b9225af74b`. Calling the unchanged baseline `scripts/agent_fixture.py::fixture_state("rr257")` with both test-state overrides unset produced the same error: `ValueError: test state path is too long for an AF_UNIX socket after fallback: 109 bytes` This reproduces the failure on unmodified `origin/dev`; the final gate will use the fixture's supported `INFRATERNAL_AGENT_TEST_STATE_ROOT=/tmp/mr220` override. No product code change is needed for the test-path issue.
Author
Owner

Progress at pushed head 0d1142b0bae22e9f794689bd346d162702970b59:

  • The final-head namespace fixture rebuilt and passed on dev-02 (aarch64). The 32 MiB relayed stream and 384 MiB failover stream had matching SHA-256; too_large_A=0, too_large_B=0; the 16 MiB rebind stream matched; host_state_diff was empty with unchanged: true. The target and temporary /tmp copies were removed; dev-02 is back to 25 GB free.
  • scripts/verify fast on compute01 is queued under build-slot because all four slots are occupied. It is running with INFRATERNAL_AGENT_TEST_STATE_ROOT=/tmp/mr220 to avoid the 109-byte fallback socket path reproduced on unmodified origin/dev. Log: /home/claude/.cache/codex-scratch/mesh-relay-220/evidence/verify-fast-final.log.

Next: finish the gate, verify both static size rows and exact D243 deltas, then post the finish comment.

Progress at pushed head `0d1142b0bae22e9f794689bd346d162702970b59`: - The final-head namespace fixture rebuilt and passed on dev-02 (aarch64). The 32 MiB relayed stream and 384 MiB failover stream had matching SHA-256; `too_large_A=0`, `too_large_B=0`; the 16 MiB rebind stream matched; `host_state_diff` was empty with `unchanged: true`. The target and temporary `/tmp` copies were removed; dev-02 is back to 25 GB free. - `scripts/verify fast` on compute01 is queued under build-slot because all four slots are occupied. It is running with `INFRATERNAL_AGENT_TEST_STATE_ROOT=/tmp/mr220` to avoid the 109-byte fallback socket path reproduced on unmodified `origin/dev`. Log: `/home/claude/.cache/codex-scratch/mesh-relay-220/evidence/verify-fast-final.log`. Next: finish the gate, verify both static size rows and exact D243 deltas, then post the finish comment.
Author
Owner

Finding/update: after the branch was rebased to 42ab330, origin/dev advanced to 105b1a28 (including the pinned Nix dev shell at 57491e918). The first queued final-gate attempt also entered build-slot without that dev shell, so Rust checks reported cargo: command not found; it did not produce a valid final gate. I am preserving the current pushed head, rebasing onto current origin/dev, and will rerun the final gate through nix develop -c ~/infraternal-ops/build-slot ... on compute01.

Finding/update: after the branch was rebased to `42ab330`, `origin/dev` advanced to `105b1a28` (including the pinned Nix dev shell at `57491e918`). The first queued final-gate attempt also entered `build-slot` without that dev shell, so Rust checks reported `cargo: command not found`; it did not produce a valid final gate. I am preserving the current pushed head, rebasing onto current `origin/dev`, and will rerun the final gate through `nix develop -c ~/infraternal-ops/build-slot ...` on compute01.
Author
Owner

Progress: rebased and pushed mesh-relay-220 at 553a8709bbdd66918d0bad2353881154be1b1d01, based on current origin/dev 105b1a28d3b3ea86487c457249ab3f953a4c27d3. Preserved the pre-rebase pushed head at wip/mesh-relay-220-pre-final-rebase; the original requested WIP snapshot remains at wip/mesh-relay-220-pre-rebase. Compute01's worktree is on the final head. scripts/verify fast is now running via nix develop -c env ... ~/infraternal-ops/build-slot ./scripts/verify fast; log: /home/claude/.cache/codex-scratch/mesh-relay-220/evidence/verify-fast-553a870.log. I will report the verbatim gate summary and exact D243 size deltas after it finishes.

Progress: rebased and pushed `mesh-relay-220` at `553a8709bbdd66918d0bad2353881154be1b1d01`, based on current `origin/dev` `105b1a28d3b3ea86487c457249ab3f953a4c27d3`. Preserved the pre-rebase pushed head at `wip/mesh-relay-220-pre-final-rebase`; the original requested WIP snapshot remains at `wip/mesh-relay-220-pre-rebase`. Compute01's worktree is on the final head. `scripts/verify fast` is now running via `nix develop -c env ... ~/infraternal-ops/build-slot ./scripts/verify fast`; log: `/home/claude/.cache/codex-scratch/mesh-relay-220/evidence/verify-fast-553a870.log`. I will report the verbatim gate summary and exact D243 size deltas after it finishes.
Author
Owner

Finding during the final-head namespace rerun: invoking the proof from the private worktree path with outer sudo leaves the host snapshot unchanged, but the inner unshare -Urnm process cannot traverse /home/claude/worktrees/... after it enters the user namespace (/usr/bin/python3: can't open file ...: [Errno 13] Permission denied). I will copy only the proof script and its built fixture into a short, world-readable /tmp path, rerun through build-slot as root, and remove the copies afterward.

Finding during the final-head namespace rerun: invoking the proof from the private worktree path with outer sudo leaves the host snapshot unchanged, but the inner `unshare -Urnm` process cannot traverse `/home/claude/worktrees/...` after it enters the user namespace (`/usr/bin/python3: can't open file ...: [Errno 13] Permission denied`). I will copy only the proof script and its built fixture into a short, world-readable `/tmp` path, rerun through `build-slot` as root, and remove the copies afterward.
Author
Owner

Progress at 553a8709: final-head relay tests passed on dev-02 with test-support enabled (10 unit + 4 QUIC loopback tests) and clippy passed. The root namespace proof is queued through build-slot after I removed this worktree's target and staged only the executable fixture/script under /tmp; dev-02 has since fallen to 16 GB free, below its 25 GB slot threshold. Compute01's final scripts/verify fast is also queued while all four slots are occupied. Logs are under /home/claude/.cache/codex-scratch/mesh-relay-220/evidence/ on the respective hosts.

Progress at `553a8709`: final-head relay tests passed on dev-02 with `test-support` enabled (10 unit + 4 QUIC loopback tests) and clippy passed. The root namespace proof is queued through build-slot after I removed this worktree's target and staged only the executable fixture/script under `/tmp`; dev-02 has since fallen to 16 GB free, below its 25 GB slot threshold. Compute01's final `scripts/verify fast` is also queued while all four slots are occupied. Logs are under `/home/claude/.cache/codex-scratch/mesh-relay-220/evidence/` on the respective hosts.
Author
Owner

Capacity update: dev-02 free space has fallen from 16 GB to 9.6 GB (96% used), so build-slot continues holding the final-head namespace proof below its 25 GB threshold. This job's target/ is removed; only the 127 MB copied fixture/script needed by the proof remains under /tmp/mr220-final-553a870. I am not touching other jobs' files. Compute01's scripts/verify fast remains queued behind its four active slots. The final proof and x86 gate are still pending.

Capacity update: dev-02 free space has fallen from 16 GB to 9.6 GB (96% used), so `build-slot` continues holding the final-head namespace proof below its 25 GB threshold. This job's `target/` is removed; only the 127 MB copied fixture/script needed by the proof remains under `/tmp/mr220-final-553a870`. I am not touching other jobs' files. Compute01's `scripts/verify fast` remains queued behind its four active slots. The final proof and x86 gate are still pending.
Author
Owner

Final-head namespace proof passed at 553a8709bbdd66918d0bad2353881154be1b1d01 on dev-02 (aarch64, functional evidence; not perf evidence), as root through build-slot. EIM/EIF observed Stable; EDM observed DestinationDependent. The 32 MiB relayed stream had matching SHA-256 and too_large_A=0, too_large_B=0. A relay killed during a 384 MiB stream failed over with matching SHA-256; longest gap 3,163.8 ms, both ends on relay 2 after 3,004.5 ms. Traffic recovered 2,344.8 ms after relay restart. NAT rebind retired the old mapping and delivered a 16 MiB stream with matching SHA-256. The proof exited 0; host_state_diff was empty with unchanged: true, and every module loaded by the run was unloaded (still_loaded: []). Full receipt: /home/claude/.cache/codex-scratch/mesh-relay-220/evidence/mesh-relay-proof-553a870.log on dev-02.

Final-head namespace proof passed at `553a8709bbdd66918d0bad2353881154be1b1d01` on dev-02 (aarch64, functional evidence; not perf evidence), as root through build-slot. EIM/EIF observed `Stable`; EDM observed `DestinationDependent`. The 32 MiB relayed stream had matching SHA-256 and `too_large_A=0`, `too_large_B=0`. A relay killed during a 384 MiB stream failed over with matching SHA-256; longest gap 3,163.8 ms, both ends on relay 2 after 3,004.5 ms. Traffic recovered 2,344.8 ms after relay restart. NAT rebind retired the old mapping and delivered a 16 MiB stream with matching SHA-256. The proof exited 0; `host_state_diff` was empty with `unchanged: true`, and every module loaded by the run was unloaded (`still_loaded: []`). Full receipt: `/home/claude/.cache/codex-scratch/mesh-relay-220/evidence/mesh-relay-proof-553a870.log` on dev-02.
Author
Owner

Progress: namespace proof and feature-on relay tests are complete at 553a8709. The required compute01 final gate is still waiting in ~/infraternal-ops/build-slot after 21 minutes because all four slots remain occupied; its log contains only the queue message so far. It is running under the current branch's Nix dev shell and the /tmp/mr220 fixture-path override. I will wait for the slot and post the exact D243 rows plus the verbatim gate summary when it exits.

Progress: namespace proof and feature-on relay tests are complete at `553a8709`. The required compute01 final gate is still waiting in `~/infraternal-ops/build-slot` after 21 minutes because all four slots remain occupied; its log contains only the queue message so far. It is running under the current branch's Nix dev shell and the `/tmp/mr220` fixture-path override. I will wait for the slot and post the exact D243 rows plus the verbatim gate summary when it exits.
Author
Owner

Progress after 30 minutes: the final compute01 gate remains queued, with all four build slots still occupied. The clean implementation is pushed at 553a8709bbdd66918d0bad2353881154be1b1d01; namespace proof, 10 unit + 4 loopback tests, and feature-on clippy are complete. I am continuing to wait for compute01 to run scripts/verify fast; no source changes are pending.

Progress after 30 minutes: the final compute01 gate remains queued, with all four build slots still occupied. The clean implementation is pushed at `553a8709bbdd66918d0bad2353881154be1b1d01`; namespace proof, 10 unit + 4 loopback tests, and feature-on clippy are complete. I am continuing to wait for compute01 to run `scripts/verify fast`; no source changes are pending.
Author
Owner

Queue update: stopped the old starved waiter by its unique process group 3981575; ps -g 3981575 confirmed no remaining process. Relaunched the unchanged scripts/verify fast command through the updated FIFO build-slot wrapper. The new log reports build-slot: [23:17:20] all 4 slots busy; queue position 1, waiting...; no other jobs were stopped. New log: /home/claude/.cache/codex-scratch/mesh-relay-220/evidence/verify-fast-553a870-fifo.log.

Queue update: stopped the old starved waiter by its unique process group `3981575`; `ps -g 3981575` confirmed no remaining process. Relaunched the unchanged `scripts/verify fast` command through the updated FIFO build-slot wrapper. The new log reports `build-slot: [23:17:20] all 4 slots busy; queue position 1, waiting...`; no other jobs were stopped. New log: `/home/claude/.cache/codex-scratch/mesh-relay-220/evidence/verify-fast-553a870-fifo.log`.
Author
Owner

Finding from the FIFO-run final gate at 553a8709 (base 105b1a28): the x86_64 static artifact is 21,433,008 bytes against D243's 21,431,472-byte cap, delta: +1536 bytes. The default relay CLI compile-out check and clippy passed; the gate is continuing through the aarch64 static build. I am tracing the remaining shipped bytes and will report the aarch64 row before adjusting the feature boundary.

Finding from the FIFO-run final gate at `553a8709` (base `105b1a28`): the x86_64 static artifact is 21,433,008 bytes against D243's 21,431,472-byte cap, `delta: +1536 bytes`. The default relay CLI compile-out check and clippy passed; the gate is continuing through the aarch64 static build. I am tracing the remaining shipped bytes and will report the aarch64 row before adjusting the feature boundary.
Author
Owner

Finding while tracing the +1,536-byte x86 D243 delta: infraternal-mesh::discovery is exported unconditionally from the shared mesh crate, while current references are limited to the relay namespace fixture and its dedicated tests. The relay fixture is feature-gated, but this public module is still part of the default mesh dependency graph. I will gate the discovery book and its test target behind the relay feature, enable that feature only from infraternal-plugin-mesh/relay, and update the contract to name that boundary; then I will verify feature-on behavior and rerun D243.

Finding while tracing the +1,536-byte x86 D243 delta: `infraternal-mesh::discovery` is exported unconditionally from the shared mesh crate, while current references are limited to the relay namespace fixture and its dedicated tests. The relay fixture is feature-gated, but this public module is still part of the default mesh dependency graph. I will gate the discovery book and its test target behind the relay feature, enable that feature only from `infraternal-plugin-mesh/relay`, and update the contract to name that boundary; then I will verify feature-on behavior and rerun D243.
Author
Owner

Finding from the FIFO run at 553a8709 (rebased on 105b1a28): the D243 static rows were x86_64 21,433,008 B / cap 21,431,472 B (+1,536 B) and aarch64 19,606,664 B / cap 19,605,896 B (+768 B). The default image still included infraternal-mesh::discovery, which has no default-path consumers. I gated it behind infraternal-mesh/relay, enabled by infraternal-plugin-mesh/relay, in pushed commit 2813200b0 rather than widening either cap.

The same gate had three remote_native_tests::terminal::gone_review failures with path must be shorter than SUN_LEN. Those tests use INFRATERNAL_TEST_STATE_ROOT from crates/infraternal-agent/src/lib.rs; the separate INFRATERNAL_AGENT_TEST_STATE_ROOT only sets the fixture helper's root. Re-running the five gone_review tests on dev-02 with INFRATERNAL_TEST_STATE_ROOT=/tmp/mr220-tests passed 5/5. The final x86 gate now sets both short roots. I will attach its transcript and final D243 rows with the finish comment.

Finding from the FIFO run at `553a8709` (rebased on `105b1a28`): the D243 static rows were x86_64 21,433,008 B / cap 21,431,472 B (`+1,536 B`) and aarch64 19,606,664 B / cap 19,605,896 B (`+768 B`). The default image still included `infraternal-mesh::discovery`, which has no default-path consumers. I gated it behind `infraternal-mesh/relay`, enabled by `infraternal-plugin-mesh/relay`, in pushed commit `2813200b0` rather than widening either cap. The same gate had three `remote_native_tests::terminal::gone_review` failures with `path must be shorter than SUN_LEN`. Those tests use `INFRATERNAL_TEST_STATE_ROOT` from `crates/infraternal-agent/src/lib.rs`; the separate `INFRATERNAL_AGENT_TEST_STATE_ROOT` only sets the fixture helper's root. Re-running the five `gone_review` tests on dev-02 with `INFRATERNAL_TEST_STATE_ROOT=/tmp/mr220-tests` passed 5/5. The final x86 gate now sets both short roots. I will attach its transcript and final D243 rows with the finish comment.
Author
Owner

Progress at pushed head 2813200b0f03159733ab744a237025bd70d3075d (base 105b1a28d3b3ea86487c457249ab3f953a4c27d3):

  • Feature-on validation passed on dev-02: infraternal-mesh 24 unit/property tests; relay plugin 10 unit tests + 4 QUIC loopback integration tests; feature-on clippy; and the relay fixture build.
  • The final-head root namespace proof passed on dev-02/aarch64 (functional evidence, not perf evidence): EIM/EIF Stable, EDM DestinationDependent; 32 MiB relayed stream SHA-256 equal with too_large_A=0, too_large_B=0; 384 MiB failover stream SHA-256 equal with 3,120.1 ms maximum gap; 16 MiB rebind stream SHA-256 equal and old mapping retired. host_state_diff was empty with unchanged: true; every module loaded by the run was unloaded.
  • The corrected scripts/verify fast is queued on compute01 through the FIFO build-slot, with both supported short fixture roots set. It started at queue position 6; no other job was stopped.

Logs are under /home/claude/.cache/codex-scratch/mesh-relay-220/evidence/ on the respective VMs. I will attach the proof and gate transcripts with the finish comment.

Progress at pushed head `2813200b0f03159733ab744a237025bd70d3075d` (base `105b1a28d3b3ea86487c457249ab3f953a4c27d3`): - Feature-on validation passed on dev-02: `infraternal-mesh` 24 unit/property tests; relay plugin 10 unit tests + 4 QUIC loopback integration tests; feature-on clippy; and the relay fixture build. - The final-head root namespace proof passed on dev-02/aarch64 (functional evidence, not perf evidence): EIM/EIF `Stable`, EDM `DestinationDependent`; 32 MiB relayed stream SHA-256 equal with `too_large_A=0`, `too_large_B=0`; 384 MiB failover stream SHA-256 equal with 3,120.1 ms maximum gap; 16 MiB rebind stream SHA-256 equal and old mapping retired. `host_state_diff` was empty with `unchanged: true`; every module loaded by the run was unloaded. - The corrected `scripts/verify fast` is queued on compute01 through the FIFO `build-slot`, with both supported short fixture roots set. It started at queue position 6; no other job was stopped. Logs are under `/home/claude/.cache/codex-scratch/mesh-relay-220/evidence/` on the respective VMs. I will attach the proof and gate transcripts with the finish comment.
Author
Owner

Progress at pushed head 2813200b0f03159733ab744a237025bd70d3075d: feature-on tests/clippy, fuzz run, and final-head namespace proof have passed; the evidence is attached to earlier comments. The required x86 scripts/verify fast is still live under the FIFO wrapper on compute01, queue position 6 for about 30 minutes. It has not started, so there is no final gate output yet. Log: /home/claude/.cache/codex-scratch/mesh-relay-220/evidence/verify-fast-2813200.log. The branch is clean and pushed; I am retaining the queue ticket.

Progress at pushed head `2813200b0f03159733ab744a237025bd70d3075d`: feature-on tests/clippy, fuzz run, and final-head namespace proof have passed; the evidence is attached to earlier comments. The required x86 `scripts/verify fast` is still live under the FIFO wrapper on compute01, queue position 6 for about 30 minutes. It has not started, so there is no final gate output yet. Log: `/home/claude/.cache/codex-scratch/mesh-relay-220/evidence/verify-fast-2813200.log`. The branch is clean and pushed; I am retaining the queue ticket.
Author
Owner

Finding from the active compute01 verify fast at 2813200b0: I confirmed INFRATERNAL_TEST_STATE_ROOT=/tmp/mr220-rust and INFRATERNAL_AGENT_TEST_STATE_ROOT=/tmp/mr220. On compute01 /tmp is tmpfs, while /home/claude/worktrees/mesh-relay-220 and the home cache are on /dev/sda1 (ext4). The seven failures are in restart/recovery executable-image fixtures; the transcript shows CrossesDevices (EXDEV) for hard-linking across those filesystems and PermissionDenied in the related executable trust-path checks. This is a test-root placement issue, not a relay code failure. I am letting this run finish to retain its D243 static rows, then will rerun the full gate with both roots below /home/claude/.cache so fixture hardlinks stay on ext4.

Finding from the active compute01 `verify fast` at `2813200b0`: I confirmed `INFRATERNAL_TEST_STATE_ROOT=/tmp/mr220-rust` and `INFRATERNAL_AGENT_TEST_STATE_ROOT=/tmp/mr220`. On compute01 `/tmp` is `tmpfs`, while `/home/claude/worktrees/mesh-relay-220` and the home cache are on `/dev/sda1` (ext4). The seven failures are in restart/recovery executable-image fixtures; the transcript shows `CrossesDevices` (`EXDEV`) for hard-linking across those filesystems and `PermissionDenied` in the related executable trust-path checks. This is a test-root placement issue, not a relay code failure. I am letting this run finish to retain its D243 static rows, then will rerun the full gate with both roots below `/home/claude/.cache` so fixture hardlinks stay on ext4.
Author
Owner

Finding from the fresh 2813200b0 static build on compute01: x86_64 measured 21,430,960 B against the existing 21,431,472 B D243 ceiling (delta: -512 B). scripts/verify fast correctly reports that shrink as a ratchet failure because D243 has no hidden slack. This is consistent with the relay runtime/discovery being gated off in the default build. The aarch64 row is still compiling; after it completes I will set both D243 ceilings to the fresh measured artifacts, then run the corrected-root full gate again.

Finding from the fresh `2813200b0` static build on compute01: x86_64 measured 21,430,960 B against the existing 21,431,472 B D243 ceiling (`delta: -512 B`). `scripts/verify fast` correctly reports that shrink as a ratchet failure because D243 has no hidden slack. This is consistent with the relay runtime/discovery being gated off in the default build. The aarch64 row is still compiling; after it completes I will set both D243 ceilings to the fresh measured artifacts, then run the corrected-root full gate again.
Author
Owner

Progress: origin/dev advanced to 0d96a74287727946779270a12662dfc6067ede25 while the compute01 gate was running. I preserved the previous pushed head at wip/mesh-relay-220-pre-dev-advance-rebase, rebased cleanly, and force-with-lease pushed mesh-relay-220 at 8872ba42c0b797f7e543eae80f067a48111622fa on the new base. The currently active size run is still compiling its prior 2813200b0 checkout; I will use it only as preliminary measurement evidence, then run the final gate with home-cache fixture roots against the rebased head. The namespace proof will also be rerun at the rebased head before the finish comment.

Progress: `origin/dev` advanced to `0d96a74287727946779270a12662dfc6067ede25` while the compute01 gate was running. I preserved the previous pushed head at `wip/mesh-relay-220-pre-dev-advance-rebase`, rebased cleanly, and force-with-lease pushed `mesh-relay-220` at `8872ba42c0b797f7e543eae80f067a48111622fa` on the new base. The currently active size run is still compiling its prior `2813200b0` checkout; I will use it only as preliminary measurement evidence, then run the final gate with home-cache fixture roots against the rebased head. The namespace proof will also be rerun at the rebased head before the finish comment.
Author
Owner

Preliminary compute01 gate at old pre-rebase head 2813200b0 completed. It is not the final result: workspace tests failed because both fixture roots were on /tmp (tmpfs), and the exact-size checker rejected the unratcheted D243 rows. Fresh static rows were x86_64 21,430,960 B / cap 21,431,472 B (-512 B) and aarch64 19,608,712 B / cap 19,605,896 B (+2,816 B). The default feature graph on the rebased checkout 8872ba42c has infraternal-plugin-mesh features agent/server, infraternal-mesh default only, and infraternal-plugin-relay default only; neither mesh relay nor relay runtime is enabled. I will record the measured exact ceilings without widening them, then rerun the full gate on 8872ba42c with both roots under /home/claude/.cache.

Preliminary compute01 gate at old pre-rebase head `2813200b0` completed. It is not the final result: workspace tests failed because both fixture roots were on `/tmp` (`tmpfs`), and the exact-size checker rejected the unratcheted D243 rows. Fresh static rows were x86_64 21,430,960 B / cap 21,431,472 B (`-512 B`) and aarch64 19,608,712 B / cap 19,605,896 B (`+2,816 B`). The default feature graph on the rebased checkout `8872ba42c` has `infraternal-plugin-mesh` features `agent`/`server`, `infraternal-mesh` `default` only, and `infraternal-plugin-relay` `default` only; neither mesh `relay` nor relay `runtime` is enabled. I will record the measured exact ceilings without widening them, then rerun the full gate on `8872ba42c` with both roots under `/home/claude/.cache`.
Author
Owner

Final-head namespace proof rerun passed at 8872ba42c0b797f7e543eae80f067a48111622fa on dev-02/aarch64 as root through build-slot (functional evidence, not perf evidence). Exact result: EIM/EIF observation Stable; EDM observation DestinationDependent; relayed 32 MiB stream SHA-256 equal, too_large_A=0, too_large_B=0; relay-kill 384 MiB failover stream SHA-256 equal with maximum gap 3164.3 ms; relay restart recovered in 2188.9 ms; NAT rebind retired the old mapping and delivered a 16 MiB stream with SHA-256 equal. still_loaded: [], host_state_diff empty with unchanged: true, exit_code=0. Receipt: /home/kayg/.local/state/infraternal-orchestrator/J5-mesh-relay-220/evidence/mesh-relay-proof-8872ba.log.

Final-head namespace proof rerun passed at `8872ba42c0b797f7e543eae80f067a48111622fa` on dev-02/aarch64 as root through `build-slot` (functional evidence, not perf evidence). Exact result: EIM/EIF observation `Stable`; EDM observation `DestinationDependent`; relayed 32 MiB stream SHA-256 equal, `too_large_A=0`, `too_large_B=0`; relay-kill 384 MiB failover stream SHA-256 equal with maximum gap `3164.3 ms`; relay restart recovered in `2188.9 ms`; NAT rebind retired the old mapping and delivered a 16 MiB stream with SHA-256 equal. `still_loaded: []`, `host_state_diff` empty with `unchanged: true`, `exit_code=0`. Receipt: `/home/kayg/.local/state/infraternal-orchestrator/J5-mesh-relay-220/evidence/mesh-relay-proof-8872ba.log`.
Author
Owner

Fresh feature-on validation at rebased head 8872ba42c0b797f7e543eae80f067a48111622fa on dev-02 passed through build-slot: infraternal-mesh --features relay (24 tests including discovery properties), relay unit tests (10/10), and QUIC loopback relay integration tests (4/4). The parser's bit-corruption and malformed-frame property cases pass. Receipts: /home/kayg/.local/state/infraternal-orchestrator/J5-mesh-relay-220/evidence/mesh-property-tests-8872ba.log and feature-tests-8872ba.log. The target directory was removed afterward; dev-02 is back to 45 GB free.

Fresh feature-on validation at rebased head `8872ba42c0b797f7e543eae80f067a48111622fa` on dev-02 passed through `build-slot`: `infraternal-mesh --features relay` (24 tests including discovery properties), relay unit tests (10/10), and QUIC loopback relay integration tests (4/4). The parser's bit-corruption and malformed-frame property cases pass. Receipts: `/home/kayg/.local/state/infraternal-orchestrator/J5-mesh-relay-220/evidence/mesh-property-tests-8872ba.log` and `feature-tests-8872ba.log`. The target directory was removed afterward; dev-02 is back to 45 GB free.
Author
Owner

Queue update for the corrected final gate at 8872ba42c0b797f7e543eae80f067a48111622fa: the compute01 scripts/verify fast ticket is live in the updated FIFO build-slot at position 13 since 01:01:34 UTC. Both fixture-root variables are confirmed in the waiting process and point to /home/claude/.cache. No build has started yet; I have kept the ticket and have not stopped other jobs. Final-head namespace proof and feature-on tests are complete; the x86 gate remains the outstanding step.

Queue update for the corrected final gate at `8872ba42c0b797f7e543eae80f067a48111622fa`: the compute01 `scripts/verify fast` ticket is live in the updated FIFO `build-slot` at position 13 since 01:01:34 UTC. Both fixture-root variables are confirmed in the waiting process and point to `/home/claude/.cache`. No build has started yet; I have kept the ticket and have not stopped other jobs. Final-head namespace proof and feature-on tests are complete; the x86 gate remains the outstanding step.
Author
Owner

D243 ratchet committed and pushed as df8f69b3092f1077eafd4d154e4d6e3047929afb on mesh-relay-220. The new exact limits match the preliminary static artifacts: x86_64 21,430,960 B (-512 B vs the prior ceiling); aarch64 19,608,712 B (+2,816 B, under 16 KiB). scripts/test-static-size.py passed the exact-cap, +1-byte rejection, shrink-ratchet and unsupported-target cases for both targets. The full corrected-root scripts/verify fast remains queued on compute01 at FIFO position 13; it will run the committed content (the compute worktree already has these exact docs/script changes).

D243 ratchet committed and pushed as `df8f69b3092f1077eafd4d154e4d6e3047929afb` on `mesh-relay-220`. The new exact limits match the preliminary static artifacts: x86_64 21,430,960 B (`-512 B` vs the prior ceiling); aarch64 19,608,712 B (`+2,816 B`, under 16 KiB). `scripts/test-static-size.py` passed the exact-cap, +1-byte rejection, shrink-ratchet and unsupported-target cases for both targets. The full corrected-root `scripts/verify fast` remains queued on compute01 at FIFO position 13; it will run the committed content (the compute worktree already has these exact docs/script changes).
Author
Owner

FIFO progress: the current live-ticket count is 6 ahead of the final gate (initially position 13); the wrapper log prints only the first position, so it still contains the original message. The final gate has not acquired a slot yet; its ticket is live and retained.

FIFO progress: the current live-ticket count is 6 ahead of the final gate (initially position 13); the wrapper log prints only the first position, so it still contains the original message. The final gate has not acquired a slot yet; its ticket is live and retained.
Author
Owner

Queue progress: 5 live tickets remain ahead of the final compute01 gate. Its FIFO ticket is still live; no final build/test stage has started yet. The log file remains /home/claude/.cache/codex-scratch/mesh-relay-220/evidence/verify-fast-8872ba.log.

Queue progress: 5 live tickets remain ahead of the final compute01 gate. Its FIFO ticket is still live; no final build/test stage has started yet. The log file remains `/home/claude/.cache/codex-scratch/mesh-relay-220/evidence/verify-fast-8872ba.log`.
Author
Owner

Queue progress: 4 live FIFO tickets remain ahead of the corrected final gate. The gate ticket is still live; it has not started yet.

Queue progress: 4 live FIFO tickets remain ahead of the corrected final gate. The gate ticket is still live; it has not started yet.
Author
Owner

FIFO progress: 3 live tickets remain ahead of the final compute01 gate; its ticket is live and retained.

FIFO progress: 3 live tickets remain ahead of the final compute01 gate; its ticket is live and retained.
Author
Owner

FIFO progress at 2026-11-01 01:35 IST: the previous racing waiter was replaced with a live ticket on the FIFO build-slot. It is currently ticket 3 (two live tickets ahead); the final scripts/verify fast build has not started yet. Remote worktree is clean at df8f69b309. I am retaining this ticket and will record the exact final gate output and D243 rows when it runs. Log: /home/claude/.cache/codex-scratch/mesh-relay-220/evidence/verify-fast-8872ba.log.

FIFO progress at 2026-11-01 01:35 IST: the previous racing waiter was replaced with a live ticket on the FIFO build-slot. It is currently ticket 3 (two live tickets ahead); the final scripts/verify fast build has not started yet. Remote worktree is clean at df8f69b3092f1077eafd4d154e4d6e3047929afb. I am retaining this ticket and will record the exact final gate output and D243 rows when it runs. Log: /home/claude/.cache/codex-scratch/mesh-relay-220/evidence/verify-fast-8872ba.log.
Author
Owner

Compute01 milestone at 2026-11-01 01:41 IST: the FIFO ticket acquired a slot and scripts/verify fast is running against df8f69b309. The current transcript has reached clippy after Rust tests and the plugin compile-out row; no final gate result is available yet. Full live log: /home/claude/.cache/codex-scratch/mesh-relay-220/evidence/verify-fast-8872ba.log.

Compute01 milestone at 2026-11-01 01:41 IST: the FIFO ticket acquired a slot and scripts/verify fast is running against df8f69b3092f1077eafd4d154e4d6e3047929afb. The current transcript has reached clippy after Rust tests and the plugin compile-out row; no final gate result is available yet. Full live log: /home/claude/.cache/codex-scratch/mesh-relay-220/evidence/verify-fast-8872ba.log.
Author
Owner

Finished #220 slice 1 at df8f69b3092f1077eafd4d154e4d6e3047929afb, clean and pushed on mesh-relay-220, rebased onto origin/dev 0d96a74287727946779270a12662dfc6067ede25. The WIP is split into atomic commits; the pre-rebase snapshots remain pushed under wip/mesh-relay-220-pre-rebase, wip/mesh-relay-220-pre-final-rebase, and wip/mesh-relay-220-pre-dev-advance-rebase.

contracts/mesh-relay.md records control and DATAGRAM framing, seam C socket ownership, slice-1 ordered relay/failover rules, and the D313 Q6 status. TLS+ALPN on 443 and the WebSocket-on-443 fallback are required for the first release but are not implemented in this slice; HTTP CONNECT remains excluded.

The default feature graph does not enable infraternal-plugin-mesh/relay or infraternal-plugin-relay/runtime; the CLI plugin compile-out gate passed. Relay runtime code adds 0 B to the default shipping image. The final static rows and D243 boundary checks from scripts/verify fast are:

PT_INTERP: absent; size: 21430960 bytes; cap: 21430960 bytes; delta: +0 bytes
PT_INTERP: absent; size: 19608712 bytes; cap: 19608712 bytes; delta: +0 bytes
x86_64-unknown-linux-musl: exact 21430960-byte cap PASS; 21430961 bytes rejected PASS; smaller artifact requires ratchet PASS
aarch64-unknown-linux-musl: exact 19608712-byte cap PASS; 19608713 bytes rejected PASS; smaller artifact requires ratchet PASS
unsupported target: rejected PASS

Against the previous D243 ceilings (21,431,472 / 19,605,896 bytes), the exact cap deltas are −512 B x86_64 / +2,816 B aarch64, both below 16 KiB. The full final gate ran on claude@infraternal-dev-01-compute01 through build-slot. Verbatim Rust summary:

+---------------------------+--------------+
| Rust gate                 | Result       |
+---------------------------+--------------+
| fmt                       | PASS         |
| VT native fmt             | PASS         |
| CLI plugin compile-out    | PASS         |
| clippy                    | PASS         |
| SSH runtime test helper   | PASS         |
| real-binary fixture paths | PASS         |
| test                      | PASS         |
| SSH inode reuse fixture   | PASS         |
| shim process-per-user     | PASS         |
| cargo-deny                | PASS         |
| gate architecture         | PASS         |
| static builds             | PASS         |
+---------------------------+--------------+

TypeScript protocol output: 39 pass, 0 fail, 50325 expect() calls; the Rust/TypeScript stream differential reported 155 streams, 16716 schedules, zero divergences. The full transcript is attached.

The final-head namespace proof on dev-02/aarch64 was run as root through build-slot (functional evidence, not performance evidence). Quoted results:

EIM/EIF: Stable; EDM: DestinationDependent
stream_relayed: bytes=33554432, sha256_equal=true, too_large_A=0, too_large_B=0
relay_killed_mid_stream: bytes=402653184, sha256_equal=true, max_gap_ms=3164.3
no_relay.recovered_after_restart_ms: 2188.9
rebind: bytes=16777216, sha256_equal=true, old_retired=true
host_state_diff: links=[], netns=[], modules=[], sysctl=[], unchanged=true
still_loaded: []
exit_code=0

Feature-on evidence: mesh/discovery property suite 24 passed; relay unit tests 10/10; QUIC loopback integration tests 4/4. The relay frame parser fuzz target completed 10,000 inputs; this toolchain build lacked coverage instrumentation, as noted in the attached log.

Perf-VM evidence already recorded above on #220: seam B was within about 3–10% of the kernel baseline; seam A used about 2× CPU per Gbit/s and added 300–600 µs one-way p50; shim state was about 270 B per relayed pair at 1,000 pairs.

The compute01 target and per-test roots were removed after the gate; the remote worktree is clean at the pushed head. This issue remains open.

Finished #220 slice 1 at `df8f69b3092f1077eafd4d154e4d6e3047929afb`, clean and pushed on `mesh-relay-220`, rebased onto `origin/dev` `0d96a74287727946779270a12662dfc6067ede25`. The WIP is split into atomic commits; the pre-rebase snapshots remain pushed under `wip/mesh-relay-220-pre-rebase`, `wip/mesh-relay-220-pre-final-rebase`, and `wip/mesh-relay-220-pre-dev-advance-rebase`. `contracts/mesh-relay.md` records control and DATAGRAM framing, seam C socket ownership, slice-1 ordered relay/failover rules, and the D313 Q6 status. TLS+ALPN on 443 and the WebSocket-on-443 fallback are required for the first release but are not implemented in this slice; HTTP CONNECT remains excluded. The default feature graph does not enable `infraternal-plugin-mesh/relay` or `infraternal-plugin-relay/runtime`; the `CLI plugin compile-out` gate passed. Relay runtime code adds **0 B** to the default shipping image. The final static rows and D243 boundary checks from `scripts/verify fast` are: ```text PT_INTERP: absent; size: 21430960 bytes; cap: 21430960 bytes; delta: +0 bytes PT_INTERP: absent; size: 19608712 bytes; cap: 19608712 bytes; delta: +0 bytes x86_64-unknown-linux-musl: exact 21430960-byte cap PASS; 21430961 bytes rejected PASS; smaller artifact requires ratchet PASS aarch64-unknown-linux-musl: exact 19608712-byte cap PASS; 19608713 bytes rejected PASS; smaller artifact requires ratchet PASS unsupported target: rejected PASS ``` Against the previous D243 ceilings (21,431,472 / 19,605,896 bytes), the exact cap deltas are **−512 B x86_64 / +2,816 B aarch64**, both below 16 KiB. The full final gate ran on `claude@infraternal-dev-01-compute01` through `build-slot`. Verbatim Rust summary: ```text +---------------------------+--------------+ | Rust gate | Result | +---------------------------+--------------+ | fmt | PASS | | VT native fmt | PASS | | CLI plugin compile-out | PASS | | clippy | PASS | | SSH runtime test helper | PASS | | real-binary fixture paths | PASS | | test | PASS | | SSH inode reuse fixture | PASS | | shim process-per-user | PASS | | cargo-deny | PASS | | gate architecture | PASS | | static builds | PASS | +---------------------------+--------------+ ``` TypeScript protocol output: `39 pass`, `0 fail`, `50325 expect() calls`; the Rust/TypeScript stream differential reported `155 streams, 16716 schedules, zero divergences`. The full transcript is attached. The final-head namespace proof on dev-02/aarch64 was run as root through `build-slot` (functional evidence, not performance evidence). Quoted results: ```text EIM/EIF: Stable; EDM: DestinationDependent stream_relayed: bytes=33554432, sha256_equal=true, too_large_A=0, too_large_B=0 relay_killed_mid_stream: bytes=402653184, sha256_equal=true, max_gap_ms=3164.3 no_relay.recovered_after_restart_ms: 2188.9 rebind: bytes=16777216, sha256_equal=true, old_retired=true host_state_diff: links=[], netns=[], modules=[], sysctl=[], unchanged=true still_loaded: [] exit_code=0 ``` Feature-on evidence: mesh/discovery property suite 24 passed; relay unit tests 10/10; QUIC loopback integration tests 4/4. The relay frame parser fuzz target completed 10,000 inputs; this toolchain build lacked coverage instrumentation, as noted in the attached log. Perf-VM evidence already recorded above on #220: seam B was within about 3–10% of the kernel baseline; seam A used about 2× CPU per Gbit/s and added 300–600 µs one-way p50; shim state was about 270 B per relayed pair at 1,000 pairs. The compute01 target and per-test roots were removed after the gate; the remote worktree is clean at the pushed head. This issue remains open.
Author
Owner

J43 review started on branch review-mesh-relay-220, based on df8f69b3092f1077eafd4d154e4d6e3047929afb (target mesh-relay-220). I will read the contract, decisions, implementation, and measurements, then record evidence-backed findings and failing regression tests on the review branch.

J43 review started on branch `review-mesh-relay-220`, based on `df8f69b3092f1077eafd4d154e4d6e3047929afb` (target `mesh-relay-220`). I will read the contract, decisions, implementation, and measurements, then record evidence-backed findings and failing regression tests on the review branch.
Author
Owner

Checkpoint: review branch review-mesh-relay-220 is pushed at fc04bdb83 from base df8f69b309. It adds two intentionally failing regressions for reconnect link-request state and same-second WireGuard handshake freshness. Next: rerun feature-on suite on dev-02, complete the root namespace proof, then run scripts/verify fast on compute01. Existing failure logs: /tmp/j43-stale-link-red.log and /tmp/j43-same-second-red.log; remote copies are in ~/.cache/codex-scratch/j43-mesh-relay-220/evidence/.

Checkpoint: review branch review-mesh-relay-220 is pushed at fc04bdb83 from base df8f69b3092f. It adds two intentionally failing regressions for reconnect link-request state and same-second WireGuard handshake freshness. Next: rerun feature-on suite on dev-02, complete the root namespace proof, then run scripts/verify fast on compute01. Existing failure logs: /tmp/j43-stale-link-red.log and /tmp/j43-same-second-red.log; remote copies are in ~/.cache/codex-scratch/j43-mesh-relay-220/evidence/.
Author
Owner

Review checkpoint: review-mesh-relay-220 is pushed at b24f68896 from df8f69b309. The initial reconnect regression was a false positive: the contract preserves a still-connected peer’s request after the other host leaves, so I removed it. The same-second handshake regression is confirmed: direct_failed compares second-resolution timestamps with <, so an old handshake in the endpoint-install second suppresses relay fallback. Next: rerun feature-support tests, complete the namespace proof, and run verify fast on compute01.

Review checkpoint: review-mesh-relay-220 is pushed at b24f68896 from df8f69b3092f. The initial reconnect regression was a false positive: the contract preserves a still-connected peer’s request after the other host leaves, so I removed it. The same-second handshake regression is confirmed: direct_failed compares second-resolution timestamps with <, so an old handshake in the endpoint-install second suppresses relay fallback. Next: rerun feature-support tests, complete the namespace proof, and run verify fast on compute01.
Author
Owner

J43 independent review findings — target df8f69b309; review branch review-mesh-relay-220 @ 61f689879.

P0: none established in the source paths reviewed. The WireGuard shim path carries kernel ciphertext; the relay forwards DATAGRAM bytes unchanged after link ownership checks. Admission uses a verified host certificate plus the required membership/revocation callback. Authenticated address probes are fixed-size request/reply, so no open reflector was demonstrated.

P1 — crates/plugins/mesh/src/relay_path.rs:69-70. direct_failed compares only handshake_secs < endpoint-install seconds. A stale successful handshake in the same second compares equal and suppresses relay fallback. The regression at relay_path.rs:690-704 fails under relay,test-support. Mesh plugin result: 70 passed, 1 failed (this regression), 14 ignored. Verdict: hold merge until fixed.

P2 — crates/plugins/relay/src/relay.rs:35-51 and crates/plugins/relay/src/tls.rs:31-37. The defaults allow 4,096 hosts, below D170's 10,000-node headroom, with 1,024 pending requests per host. Configured receive/send DATAGRAM buffers are 512/256 KiB per connection, or 750 MiB allowance at 1,000 connections and 3 GiB at the 4,096-host cap before link tables. Those are caps, not measured RSS; the review could not complete abuse-scale memory measurement.

Relay tests on dev-02 passed with test-support: 10 unit and 4 loopback tests. Mesh discovery passed. D313's quality-switch rule (max(5 ms, 15%) across five samples spanning >=3 seconds, 10-second dwell) is not evaluated by the reported 3,164.3 ms relay-kill gap or 2,188.9 ms restart recovery. The kill gap is ~164 ms over the 3-second QUIC idle timeout. MTU 1,472 covers IPv4/1500; IPv6/1452 remains 2 bytes short and is documented as deferred.

Blocked checks: dev-02 SSH stopped responding after the relay fixture build was launched, so the root namespace proof and fixture cleanup could not be verified. compute01 SSH did not return, so scripts/verify fast and x86_64 size rows were not captured. No coverage-guided fuzz run: cargo-fuzz/nightly were absent in the available Nix dev shell. See findings.md on the review branch for details and evidence paths.

J43 independent review findings — target df8f69b3092f1077eafd4d154e4d6e3047929afb; review branch review-mesh-relay-220 @ 61f689879. P0: none established in the source paths reviewed. The WireGuard shim path carries kernel ciphertext; the relay forwards DATAGRAM bytes unchanged after link ownership checks. Admission uses a verified host certificate plus the required membership/revocation callback. Authenticated address probes are fixed-size request/reply, so no open reflector was demonstrated. P1 — crates/plugins/mesh/src/relay_path.rs:69-70. direct_failed compares only handshake_secs < endpoint-install seconds. A stale successful handshake in the same second compares equal and suppresses relay fallback. The regression at relay_path.rs:690-704 fails under relay,test-support. Mesh plugin result: 70 passed, 1 failed (this regression), 14 ignored. Verdict: hold merge until fixed. P2 — crates/plugins/relay/src/relay.rs:35-51 and crates/plugins/relay/src/tls.rs:31-37. The defaults allow 4,096 hosts, below D170's 10,000-node headroom, with 1,024 pending requests per host. Configured receive/send DATAGRAM buffers are 512/256 KiB per connection, or 750 MiB allowance at 1,000 connections and 3 GiB at the 4,096-host cap before link tables. Those are caps, not measured RSS; the review could not complete abuse-scale memory measurement. Relay tests on dev-02 passed with test-support: 10 unit and 4 loopback tests. Mesh discovery passed. D313's quality-switch rule (max(5 ms, 15%) across five samples spanning >=3 seconds, 10-second dwell) is not evaluated by the reported 3,164.3 ms relay-kill gap or 2,188.9 ms restart recovery. The kill gap is ~164 ms over the 3-second QUIC idle timeout. MTU 1,472 covers IPv4/1500; IPv6/1452 remains 2 bytes short and is documented as deferred. Blocked checks: dev-02 SSH stopped responding after the relay fixture build was launched, so the root namespace proof and fixture cleanup could not be verified. compute01 SSH did not return, so scripts/verify fast and x86_64 size rows were not captured. No coverage-guided fuzz run: cargo-fuzz/nightly were absent in the available Nix dev shell. See findings.md on the review branch for details and evidence paths.
Author
Owner

J43 recovery update at review branch 71f84a007: the staged root namespace proof completed on dev-02 through sudo/build-slot. It reported matching hashes for the relayed 32 MiB stream, the 384 MiB relay-kill stream (max gap 3,121.2 ms), and the 16 MiB rebind stream; still_loaded: [] and host_state_diff was empty (unchanged: true). Temporary /tmp staging was removed. This supersedes my prior comment saying the proof was blocked. The required compute01 scripts/verify fast is now running through FIFO build-slot with both fixture roots under /home/claude/.cache; its log is /home/claude/.cache/codex-scratch/j43-mesh-relay-220/evidence/verify-fast.log.

J43 recovery update at review branch 71f84a007: the staged root namespace proof completed on dev-02 through sudo/build-slot. It reported matching hashes for the relayed 32 MiB stream, the 384 MiB relay-kill stream (max gap 3,121.2 ms), and the 16 MiB rebind stream; `still_loaded: []` and `host_state_diff` was empty (`unchanged: true`). Temporary /tmp staging was removed. This supersedes my prior comment saying the proof was blocked. The required compute01 `scripts/verify fast` is now running through FIFO build-slot with both fixture roots under `/home/claude/.cache`; its log is `/home/claude/.cache/codex-scratch/j43-mesh-relay-220/evidence/verify-fast.log`.
Author
Owner

Compute01 gate adjustment: I stopped only the isolated J43 gate process group after noticing scripts/verify does not set Cargo parallelism. The partial log is preserved as verify-fast-default-jobs.partial.log. The required FIFO build-slot run is relaunched with CARGO_BUILD_JOBS=4 and both short fixture roots on /home/claude/.cache; the current log is verify-fast.log.

Compute01 gate adjustment: I stopped only the isolated J43 gate process group after noticing scripts/verify does not set Cargo parallelism. The partial log is preserved as verify-fast-default-jobs.partial.log. The required FIFO build-slot run is relaunched with CARGO_BUILD_JOBS=4 and both short fixture roots on /home/claude/.cache; the current log is verify-fast.log.
Author
Owner

J43 gate progress at review branch 71f84a007: compute01 scripts/verify fast has passed clippy and the full workspace tests (test: 1095s), then the SSH inode fixture was skipped because the filesystem did not reuse a released inode after 256 attempts. Static path checks passed. The x86_64 artifact row is PT_INTERP: absent; size: 21430960 bytes; cap: 21430960 bytes; delta: +0 bytes. The aarch64 static build is active through FIFO build-slot; final gate exit and ARM row remain pending. Log: /home/claude/.cache/codex-scratch/j43-mesh-relay-220/evidence/verify-fast.log. Next: collect the ARM row/exit, update and push the review report, then post the final P0–P3 verdict.

J43 gate progress at review branch `71f84a007`: compute01 `scripts/verify fast` has passed clippy and the full workspace tests (`test: 1095s`), then the SSH inode fixture was skipped because the filesystem did not reuse a released inode after 256 attempts. Static path checks passed. The x86_64 artifact row is `PT_INTERP: absent; size: 21430960 bytes; cap: 21430960 bytes; delta: +0 bytes`. The aarch64 static build is active through FIFO `build-slot`; final gate exit and ARM row remain pending. Log: `/home/claude/.cache/codex-scratch/j43-mesh-relay-220/evidence/verify-fast.log`. Next: collect the ARM row/exit, update and push the review report, then post the final P0–P3 verdict.
Author
Owner

J43 final independent protocol/security review. Target: df8f69b3092f1077eafd4d154e4d6e3047929afb on mesh-relay-220 (base 0d96a7428). Review branch: review-mesh-relay-220 @ e1a4676cd. Current origin/dev is f4b61cd6c; this review remains pinned to the requested target.

Verdict: HOLD merge pending the P1 direct-fallback fix. Full report: findings.md on the review branch.

  • P0: none established. crates/plugins/mesh/src/relay_path.rs carries kernel WireGuard ciphertext through the shim; crates/plugins/relay/src/relay.rs:261-283 checks link ownership and forwards the DATAGRAM unchanged. No mesh key or decryption path is present. Relay clients authenticate against deployment CA roots; the exact host URI SAN is passed to the required membership/revocation callback (crates/plugins/relay/src/tls.rs:86-143, crates/plugins/relay/src/relay.rs:56-57,455-461). Any admitted same-mesh host may use a configured relay, matching D313 Q3; a different mesh cannot authenticate. Probe replies require an authenticated live-connection exporter key and are equal-size 84-byte replies, so I found no open reflector or cross-mesh capacity path.
  • P1 — crates/plugins/mesh/src/relay_path.rs:69-70. direct_failed compares second-resolution handshake time with <. A handshake before endpoint installation in the same second compares equal and suppresses relay fallback. Regression a_same_second_handshake_cannot_prove_it_followed_the_endpoint_update at relay_path.rs:690-704 fails. Feature-on mesh test result: 70 passed, 1 failed (this regression), 14 ignored. Relay tests passed 10 unit + 4 QUIC loopback; discovery passed 24.
  • P2 — crates/plugins/relay/src/relay.rs:35-52, crates/plugins/relay/src/tls.rs:31-37. Defaults permit 4,096 hosts and 1,024 pending requests per host. QUIC receive/send caps total 750 MiB configured queue allowance at 1,000 connections and 3 GiB at 4,096, before link tables. This is a configured maximum, not measured RSS. It is finite, but below D170's 10,000-node headroom test target and lacks abuse-scale RSS evidence.
  • P3: none found. The parser rejected malformed/unknown fixed frames in tests. J5 ran 10,000 parser inputs without coverage; this Nix shell lacked cargo-fuzz/nightly coverage instrumentation, so no coverage-guided run was completed.

Root namespace proof ran as root on dev-02 through cua/build-slot. J43 measured matching hashes for 32 MiB relayed, 384 MiB relay-kill (max gap 3,121.2 ms), and 16 MiB rebind streams (old mapping retired); still_loaded: [], with links/namespaces/modules/sysctls unchanged. J5's final proof measured 3,164.3 ms kill gap and 2,188.9 ms restart recovery. Kill gaps are 121–164 ms beyond the 3-second idle timeout. These outage measurements do not test D313's quality-switch rule (win >= max(5 ms, 15%), five samples spanning >=3 s, 10 s dwell). The contract disclaims packet continuity on relay loss and defers the byte-preserving selector. MTU 1,472 fixes IPv4/1500; IPv6/1452 remains two bytes short, explicitly deferred. D313 Q6's TLS+ALPN and WebSocket fallbacks on 443 are absent and the contract says this slice does not complete UDP-blocked first-release support.

Feature-off compile-out passed: infraternal-plugin-mesh/relay and infraternal-plugin-relay/runtime are disabled; runtime adds 0 B to the default image. Static deltas against previous D243 ceilings are -512 B x86_64 / +2,816 B aarch64; D243 approval caps have since been removed.

scripts/verify fast on compute01 through FIFO build-slot: exit 0. Verbatim summary:

+---------------------------+--------------+
| Rust gate                 | Result       |
+---------------------------+--------------+
| fmt                       | PASS         |
| VT native fmt             | PASS         |
| CLI plugin compile-out    | PASS         |
| clippy                    | PASS         |
| SSH runtime test helper   | PASS         |
| real-binary fixture paths | PASS         |
| test                      | PASS         |
| SSH inode reuse fixture   | PASS         |
| shim process-per-user     | PASS         |
| cargo-deny                | PASS         |
| gate architecture         | PASS         |
| static builds             | PASS         |
+---------------------------+--------------+

PT_INTERP: absent; size: 21430960 bytes; cap: 21430960 bytes; delta: +0 bytes
PT_INTERP: absent; size: 19608712 bytes; cap: 19608712 bytes; delta: +0 bytes
x86_64-unknown-linux-musl: exact 21430960-byte cap PASS; 21430961 bytes rejected PASS; smaller artifact requires ratchet PASS
aarch64-unknown-linux-musl: exact 19608712-byte cap PASS; 19608713 bytes rejected PASS; smaller artifact requires ratchet PASS
unsupported target: rejected PASS

39 pass
0 fail
50325 expect() calls
Ran 39 tests across 10 files. [5.00s]

stream differential: 155 streams, 16716 schedules, zero divergences

Gate transcript: /home/claude/.cache/codex-scratch/j43-mesh-relay-220/evidence/verify-fast.log; namespace proof: /home/claude/.cache/codex-scratch/j43-mesh-relay-220/evidence/root-proof.log. Build targets and fixture roots were removed; evidence logs were retained. #220 remains open.

J43 final independent protocol/security review. Target: `df8f69b3092f1077eafd4d154e4d6e3047929afb` on `mesh-relay-220` (base `0d96a7428`). Review branch: `review-mesh-relay-220` @ `e1a4676cd`. Current `origin/dev` is `f4b61cd6c`; this review remains pinned to the requested target. **Verdict: HOLD merge pending the P1 direct-fallback fix.** Full report: `findings.md` on the review branch. - **P0: none established.** `crates/plugins/mesh/src/relay_path.rs` carries kernel WireGuard ciphertext through the shim; `crates/plugins/relay/src/relay.rs:261-283` checks link ownership and forwards the DATAGRAM unchanged. No mesh key or decryption path is present. Relay clients authenticate against deployment CA roots; the exact host URI SAN is passed to the required membership/revocation callback (`crates/plugins/relay/src/tls.rs:86-143`, `crates/plugins/relay/src/relay.rs:56-57,455-461`). Any admitted same-mesh host may use a configured relay, matching D313 Q3; a different mesh cannot authenticate. Probe replies require an authenticated live-connection exporter key and are equal-size 84-byte replies, so I found no open reflector or cross-mesh capacity path. - **P1 — `crates/plugins/mesh/src/relay_path.rs:69-70`.** `direct_failed` compares second-resolution handshake time with `<`. A handshake before endpoint installation in the same second compares equal and suppresses relay fallback. Regression `a_same_second_handshake_cannot_prove_it_followed_the_endpoint_update` at `relay_path.rs:690-704` fails. Feature-on mesh test result: 70 passed, 1 failed (this regression), 14 ignored. Relay tests passed 10 unit + 4 QUIC loopback; discovery passed 24. - **P2 — `crates/plugins/relay/src/relay.rs:35-52`, `crates/plugins/relay/src/tls.rs:31-37`.** Defaults permit 4,096 hosts and 1,024 pending requests per host. QUIC receive/send caps total 750 MiB configured queue allowance at 1,000 connections and 3 GiB at 4,096, before link tables. This is a configured maximum, not measured RSS. It is finite, but below D170's 10,000-node headroom test target and lacks abuse-scale RSS evidence. - **P3: none found.** The parser rejected malformed/unknown fixed frames in tests. J5 ran 10,000 parser inputs without coverage; this Nix shell lacked cargo-fuzz/nightly coverage instrumentation, so no coverage-guided run was completed. Root namespace proof ran as root on dev-02 through `cua`/`build-slot`. J43 measured matching hashes for 32 MiB relayed, 384 MiB relay-kill (max gap 3,121.2 ms), and 16 MiB rebind streams (old mapping retired); `still_loaded: []`, with links/namespaces/modules/sysctls unchanged. J5's final proof measured 3,164.3 ms kill gap and 2,188.9 ms restart recovery. Kill gaps are 121–164 ms beyond the 3-second idle timeout. These outage measurements do not test D313's quality-switch rule (win >= max(5 ms, 15%), five samples spanning >=3 s, 10 s dwell). The contract disclaims packet continuity on relay loss and defers the byte-preserving selector. MTU 1,472 fixes IPv4/1500; IPv6/1452 remains two bytes short, explicitly deferred. D313 Q6's TLS+ALPN and WebSocket fallbacks on 443 are absent and the contract says this slice does not complete UDP-blocked first-release support. Feature-off compile-out passed: `infraternal-plugin-mesh/relay` and `infraternal-plugin-relay/runtime` are disabled; runtime adds 0 B to the default image. Static deltas against previous D243 ceilings are -512 B x86_64 / +2,816 B aarch64; D243 approval caps have since been removed. `scripts/verify fast` on compute01 through FIFO `build-slot`: **exit 0**. Verbatim summary: ```text +---------------------------+--------------+ | Rust gate | Result | +---------------------------+--------------+ | fmt | PASS | | VT native fmt | PASS | | CLI plugin compile-out | PASS | | clippy | PASS | | SSH runtime test helper | PASS | | real-binary fixture paths | PASS | | test | PASS | | SSH inode reuse fixture | PASS | | shim process-per-user | PASS | | cargo-deny | PASS | | gate architecture | PASS | | static builds | PASS | +---------------------------+--------------+ PT_INTERP: absent; size: 21430960 bytes; cap: 21430960 bytes; delta: +0 bytes PT_INTERP: absent; size: 19608712 bytes; cap: 19608712 bytes; delta: +0 bytes x86_64-unknown-linux-musl: exact 21430960-byte cap PASS; 21430961 bytes rejected PASS; smaller artifact requires ratchet PASS aarch64-unknown-linux-musl: exact 19608712-byte cap PASS; 19608713 bytes rejected PASS; smaller artifact requires ratchet PASS unsupported target: rejected PASS 39 pass 0 fail 50325 expect() calls Ran 39 tests across 10 files. [5.00s] stream differential: 155 streams, 16716 schedules, zero divergences ``` Gate transcript: `/home/claude/.cache/codex-scratch/j43-mesh-relay-220/evidence/verify-fast.log`; namespace proof: `/home/claude/.cache/codex-scratch/j43-mesh-relay-220/evidence/root-proof.log`. Build targets and fixture roots were removed; evidence logs were retained. #220 remains open.
Author
Owner

J49 starting on branch mesh-relay-220 at target df8f69b3092f1077eafd4d154e4d6e3047929afb, currently based on origin/dev 0d96a7428. I will rebase onto origin/dev f4b61cd6c3f52078cad7573c5367d7a6e75f84f0 before code changes.

Contract proposal before implementation:

  • Compare the WireGuard handshake seconds and nanoseconds against the endpoint-install timestamp. A handshake timestamp equal to or earlier than installation does not prove freshness and must allow relay fallback. I also checked the separate existing-session guard in start_handshake; its nonzero check preserves a live cryptographic session and does not decide endpoint freshness.
  • Replace per-connection DATAGRAM queues (512 KiB receive / 256 KiB send) with a small fixed number of maximum-size datagrams; add a relay-wide byte budget and per-pair byte cap. Hold both reservations in shared Bytes ownership through Quinn's send queue, and drop DATAGRAMs when either budget is full.
  • Record these queue and drop bounds in contracts/mesh-relay.md, then measure relay RSS with many pairs flooding on the perf VM before and after using perf-run --root.

The owner removed D243 approval size caps. I will still report the static image delta against the rebased dev baseline.

J49 starting on branch `mesh-relay-220` at target `df8f69b3092f1077eafd4d154e4d6e3047929afb`, currently based on `origin/dev 0d96a7428`. I will rebase onto `origin/dev f4b61cd6c3f52078cad7573c5367d7a6e75f84f0` before code changes. Contract proposal before implementation: - Compare the WireGuard handshake seconds and nanoseconds against the endpoint-install timestamp. A handshake timestamp equal to or earlier than installation does not prove freshness and must allow relay fallback. I also checked the separate existing-session guard in `start_handshake`; its nonzero check preserves a live cryptographic session and does not decide endpoint freshness. - Replace per-connection DATAGRAM queues (512 KiB receive / 256 KiB send) with a small fixed number of maximum-size datagrams; add a relay-wide byte budget and per-pair byte cap. Hold both reservations in shared `Bytes` ownership through Quinn's send queue, and drop DATAGRAMs when either budget is full. - Record these queue and drop bounds in `contracts/mesh-relay.md`, then measure relay RSS with many pairs flooding on the perf VM before and after using `perf-run --root`. The owner removed D243 approval size caps. I will still report the static image delta against the rebased dev baseline.
Author
Owner

P1 reproducer confirmed on the rebased target. I copied J43's a_same_second_handshake_cannot_prove_it_followed_the_endpoint_update test into the mesh path tests and ran it on claude@infraternal-dev-02-std through build-slot under nix develop. Result: 0 passed, 1 failed, exit 101. It fails because direct_failed compares handshake_secs < since_unix_secs; an equal-second handshake is accepted as fresh even though endpoint-install ordering is unknown. I will use the conservative equality rule. The separate start_handshake nonzero-timestamp check only preserves an existing WireGuard session and is not a freshness comparison.

P1 reproducer confirmed on the rebased target. I copied J43's `a_same_second_handshake_cannot_prove_it_followed_the_endpoint_update` test into the mesh path tests and ran it on `claude@infraternal-dev-02-std` through `build-slot` under `nix develop`. Result: 0 passed, 1 failed, exit 101. It fails because `direct_failed` compares `handshake_secs < since_unix_secs`; an equal-second handshake is accepted as fresh even though endpoint-install ordering is unknown. I will use the conservative equality rule. The separate `start_handshake` nonzero-timestamp check only preserves an existing WireGuard session and is not a freshness comparison.
Author
Owner

J49 progress: P1 is committed and pushed at bc3d654e5. The J43 regression failed at the ambiguous same-second assertion before the <= freshness change, then passed 1/1 on dev-02; same-second-green.log records the passing line. I am compiling the isolated relay flood RSS benchmark on compute01 before changing queue limits. Build target/log: ~/.cache/codex-scratch/j49-mesh-relay-220/{target,baseline-build.log}.

J49 progress: P1 is committed and pushed at bc3d654e5. The J43 regression failed at the ambiguous same-second assertion before the <= freshness change, then passed 1/1 on dev-02; same-second-green.log records the passing line. I am compiling the isolated relay flood RSS benchmark on compute01 before changing queue limits. Build target/log: ~/.cache/codex-scratch/j49-mesh-relay-220/{target,baseline-build.log}.
Author
Owner

J49 WIP checkpoint pushed at 700e9bb83 (after P1 commit bc3d654e5). The feature-on memory regression is red because QueueBudget/queue_forward_datagram are not implemented yet. The RSS harness had one compile error in paired notice reads; that is fixed with split_at_mut. Its pre-change release build is queued on compute01 at build-slot position 9; current log: ~/.cache/codex-scratch/j49-mesh-relay-220/baseline-build.log. Next: complete baseline RSS, implement/account the bounded shared buffers, then run the same flood after the change.

J49 WIP checkpoint pushed at 700e9bb83 (after P1 commit bc3d654e5). The feature-on memory regression is red because QueueBudget/queue_forward_datagram are not implemented yet. The RSS harness had one compile error in paired notice reads; that is fixed with split_at_mut. Its pre-change release build is queued on compute01 at build-slot position 9; current log: ~/.cache/codex-scratch/j49-mesh-relay-220/baseline-build.log. Next: complete baseline RSS, implement/account the bounded shared buffers, then run the same flood after the change.
Author
Owner

J49 follow-up findings and evidence:

  • P1: The seconds-only <= fix passed J43’s stale same-second regression but falsely treated a fresh same-second handshake as stale. The root namespace proof timed out waiting for peer C’s direct event; WireGuard reported a nonzero handshake timestamp in the endpoint-install second, C had no active relay, and the fixture entered the relay path. I changed the comparison to use the kernel’s handshake seconds and nanoseconds against the post-install timestamp. The stale, fresh same-second, and exact-equality cases pass 4/4 on dev-02; the full feature-on mesh suite passes 73 tests with 14 ignored. The failed proof’s host-state check was still clean. Root proof rerun is pending.
  • Selector search: start_handshake checks only whether any session exists before resetting WireGuard state; it does not compare handshake freshness. The other handshake reads report presence or age and do not compare against endpoint installation.
  • P2 baseline: perf VM infraternal-perf-01-backup01, Debian 6.12.113, Ryzen 7 3700X. Pre-budget relay binary from the baseline worktree; perf-run --root with an isolated network namespace, 256 pairs / 512 hosts, 131,072 DATAGRAM attempts. Idle relay RSS/HWM was 26,704 KiB; flood RSS/HWM peaked at 33,772 KiB, +7,068 KiB over idle. Flood phase 2,164 ms; 60,158 sends accepted by clients, 70,914 blocked at their queues. No process exceeded 5% CPU in the pre/post quiet checks; steal was 9/1,478 jiffies. tc is unavailable, so this run used unshaped loopback.

Evidence: /home/claude/.cache/codex-scratch/j49-mesh-relay-220/{before.log,perf-before/}, mesh-feature-tests.log, and freshness-green.log. The final memory table and after-budget measurement will follow.

J49 follow-up findings and evidence: - P1: The seconds-only `<=` fix passed J43’s stale same-second regression but falsely treated a fresh same-second handshake as stale. The root namespace proof timed out waiting for peer C’s direct event; WireGuard reported a nonzero handshake timestamp in the endpoint-install second, C had no active relay, and the fixture entered the relay path. I changed the comparison to use the kernel’s handshake seconds and nanoseconds against the post-install timestamp. The stale, fresh same-second, and exact-equality cases pass 4/4 on dev-02; the full feature-on mesh suite passes 73 tests with 14 ignored. The failed proof’s host-state check was still clean. Root proof rerun is pending. - Selector search: `start_handshake` checks only whether any session exists before resetting WireGuard state; it does not compare handshake freshness. The other handshake reads report presence or age and do not compare against endpoint installation. - P2 baseline: perf VM `infraternal-perf-01-backup01`, Debian 6.12.113, Ryzen 7 3700X. Pre-budget relay binary from the baseline worktree; `perf-run --root` with an isolated network namespace, 256 pairs / 512 hosts, 131,072 DATAGRAM attempts. Idle relay RSS/HWM was 26,704 KiB; flood RSS/HWM peaked at 33,772 KiB, +7,068 KiB over idle. Flood phase 2,164 ms; 60,158 sends accepted by clients, 70,914 blocked at their queues. No process exceeded 5% CPU in the pre/post quiet checks; steal was 9/1,478 jiffies. `tc` is unavailable, so this run used unshaped loopback. Evidence: `/home/claude/.cache/codex-scratch/j49-mesh-relay-220/{before.log,perf-before/}`, `mesh-feature-tests.log`, and `freshness-green.log`. The final memory table and after-budget measurement will follow.
Author
Owner

P1 verification update: the release root namespace proof now passes with the precise timestamp comparison. A→C stays direct and transfers 32 MiB with a matching SHA-256; the 32 MiB relay stream, 384 MiB relay-failover stream, and 16 MiB rebound stream also match hashes. The proof reports still_loaded: [], an empty host_state_diff, and unchanged: true. Receipt: /home/claude/.cache/codex-scratch/j49-mesh-relay-220/root-proof-round2.log.

The contract now records the seconds+nanos ordering rule. I found no second-granularity endpoint freshness comparison elsewhere: start_handshake only checks whether a session exists, while presence and status code read handshake age/state. P1 is ready to commit; P2 after-budget RSS and the final fast gate remain.

P1 verification update: the release root namespace proof now passes with the precise timestamp comparison. A→C stays direct and transfers 32 MiB with a matching SHA-256; the 32 MiB relay stream, 384 MiB relay-failover stream, and 16 MiB rebound stream also match hashes. The proof reports `still_loaded: []`, an empty `host_state_diff`, and `unchanged: true`. Receipt: `/home/claude/.cache/codex-scratch/j49-mesh-relay-220/root-proof-round2.log`. The contract now records the seconds+nanos ordering rule. I found no second-granularity endpoint freshness comparison elsewhere: `start_handshake` only checks whether a session exists, while presence and status code read handshake age/state. P1 is ready to commit; P2 after-budget RSS and the final fast gate remain.
Author
Owner

J49 progress on mesh-relay-220 @ fc1ffa97f (rebased onto origin/dev f4b61cd6c):

  • P1 is implemented and the P1 namespace proof passed. The stale and fresh same-second handshake cases pass, A→C remains direct, relay/failover/rebound streams have matching hashes, and the proof reports still_loaded: [], an empty host_state_diff, and unchanged: true. Selector review found no other endpoint freshness comparison using seconds-only ordering.
  • P2 is implemented and pushed: 64 MiB global forwarding budget, 256 KiB per-pair cap, 1 KiB accounting units plus 128 B per-datagram allowance, shared Bytes ownership with reservations held through Quinn, and DATAGRAM drop/count under pressure. contracts/mesh-relay.md records the 8/128 KiB relay receive/send settings and aggregate bounds.
  • Pre-budget abuse baseline on infraternal-perf-01-backup01 (Debian 6.12.113, Ryzen 7 3700X): 256 pairs / 512 hosts, 131,072 attempts, idle RSS/HWM 26,704 KiB, flood peak RSS/HWM 33,772 KiB (+7,068 KiB), 2,164 ms. Quiet checks passed; tc is unavailable, so loopback was unshaped. Logs: /home/claude/.cache/codex-scratch/j49-mesh-relay-220/before.log and perf-before/.
  • The matching post-budget release build and scripts/verify fast are queued through compute01 FIFO build-slot (last observed positions 13 and 14). Logs: /home/claude/.cache/codex-scratch/j49-mesh-relay-220/after-build.log and verify-fast.log.

Next: collect post-budget RSS and the final gate/static-size delta, then post the round-2 per-finding summary and before/after memory table. Issue remains open.

J49 progress on `mesh-relay-220` @ `fc1ffa97f` (rebased onto `origin/dev` `f4b61cd6c`): - P1 is implemented and the P1 namespace proof passed. The stale and fresh same-second handshake cases pass, A→C remains direct, relay/failover/rebound streams have matching hashes, and the proof reports `still_loaded: []`, an empty `host_state_diff`, and `unchanged: true`. Selector review found no other endpoint freshness comparison using seconds-only ordering. - P2 is implemented and pushed: 64 MiB global forwarding budget, 256 KiB per-pair cap, 1 KiB accounting units plus 128 B per-datagram allowance, shared `Bytes` ownership with reservations held through Quinn, and DATAGRAM drop/count under pressure. `contracts/mesh-relay.md` records the 8/128 KiB relay receive/send settings and aggregate bounds. - Pre-budget abuse baseline on `infraternal-perf-01-backup01` (Debian 6.12.113, Ryzen 7 3700X): 256 pairs / 512 hosts, 131,072 attempts, idle RSS/HWM 26,704 KiB, flood peak RSS/HWM 33,772 KiB (+7,068 KiB), 2,164 ms. Quiet checks passed; `tc` is unavailable, so loopback was unshaped. Logs: `/home/claude/.cache/codex-scratch/j49-mesh-relay-220/before.log` and `perf-before/`. - The matching post-budget release build and `scripts/verify fast` are queued through compute01 FIFO `build-slot` (last observed positions 13 and 14). Logs: `/home/claude/.cache/codex-scratch/j49-mesh-relay-220/after-build.log` and `verify-fast.log`. Next: collect post-budget RSS and the final gate/static-size delta, then post the round-2 per-finding summary and before/after memory table. Issue remains open.
Author
Owner

J49 progress on mesh-relay-220 @ fc1ffa97f:

  • The feature-on dev-02 evidence is complete: mesh tests 73 passed / 14 ignored; relay unit tests 12 passed; QUIC loopback integration tests 4 passed; the shared-budget regression passed 1/1. The round-2 root namespace proof remains clean (still_loaded: [], empty host_state_diff, unchanged: true).
  • The 256-pair pre-budget RSS baseline is recorded in before.log and perf-before/: idle RSS/HWM 26,704 KiB; flood peak RSS/HWM 33,772 KiB (+7,068 KiB); 131,072 attempts in 2,164 ms.
  • The post-budget release build and scripts/verify fast remain queued through compute01 FIFO build-slot. The current queue has two earlier tickets; J49 is next for the build and next after that for the gate. Logs: /home/claude/.cache/codex-scratch/j49-mesh-relay-220/after-build.log and verify-fast.log.

Next: collect the post-budget RSS result, quote the fast-gate output and report D243 static-size deltas, then post the final P1/P2 summary and before/after memory table. No code changes since fc1ffa97f; issue remains open.

J49 progress on `mesh-relay-220` @ `fc1ffa97f`: - The feature-on dev-02 evidence is complete: mesh tests 73 passed / 14 ignored; relay unit tests 12 passed; QUIC loopback integration tests 4 passed; the shared-budget regression passed 1/1. The round-2 root namespace proof remains clean (`still_loaded: []`, empty `host_state_diff`, `unchanged: true`). - The 256-pair pre-budget RSS baseline is recorded in `before.log` and `perf-before/`: idle RSS/HWM 26,704 KiB; flood peak RSS/HWM 33,772 KiB (+7,068 KiB); 131,072 attempts in 2,164 ms. - The post-budget release build and `scripts/verify fast` remain queued through compute01 FIFO `build-slot`. The current queue has two earlier tickets; J49 is next for the build and next after that for the gate. Logs: `/home/claude/.cache/codex-scratch/j49-mesh-relay-220/after-build.log` and `verify-fast.log`. Next: collect the post-budget RSS result, quote the fast-gate output and report D243 static-size deltas, then post the final P1/P2 summary and before/after memory table. No code changes since `fc1ffa97f`; issue remains open.
Author
Owner

J49 gate finding on the current head fc1ffa97f:

  • scripts/verify fast reached clippy and failed to compile infraternal-plugin-mesh test code at crates/plugins/mesh/src/node_actions.rs:841: the Counters initializer lacked handshake_nanos. The P1 change had made that field unconditional while the initializer still included it only under test-support.
  • The fix is checkpointed and pushed at 21f5bb760 as a WIP commit. handshake_nanos, its parser and timestamp tests are now gated on the relay feature, and the node-action literal uses the same feature gate. The relay-enabled path retains the precision comparison; ordinary agent builds avoid the extra field and parsing.
  • The current compute01 gate is still running against fc1ffa97f and will finish before the worktree is updated. Next: rerun feature-on mesh tests and scripts/verify fast on 21f5bb760, collect the D243 size delta, then post the final per-finding summary and RSS table.

Gate log: /home/claude/.cache/codex-scratch/j49-mesh-relay-220/verify-fast.log. Issue remains open.

J49 gate finding on the current head `fc1ffa97f`: - `scripts/verify fast` reached clippy and failed to compile `infraternal-plugin-mesh` test code at `crates/plugins/mesh/src/node_actions.rs:841`: the `Counters` initializer lacked `handshake_nanos`. The P1 change had made that field unconditional while the initializer still included it only under `test-support`. - The fix is checkpointed and pushed at `21f5bb760` as a WIP commit. `handshake_nanos`, its parser and timestamp tests are now gated on the `relay` feature, and the node-action literal uses the same feature gate. The relay-enabled path retains the precision comparison; ordinary agent builds avoid the extra field and parsing. - The current compute01 gate is still running against `fc1ffa97f` and will finish before the worktree is updated. Next: rerun feature-on mesh tests and `scripts/verify fast` on `21f5bb760`, collect the D243 size delta, then post the final per-finding summary and RSS table. Gate log: `/home/claude/.cache/codex-scratch/j49-mesh-relay-220/verify-fast.log`. Issue remains open.
Author
Owner

J49 checkpoint pushed at b4c9ba1ae on mesh-relay-220.

  • The feature-off compile failure came from applying #[cfg] to an assignment inside a match arm. I moved the gate onto feature-specific match arms; relay builds still parse seconds+nanos, feature-off builds keep seconds-only counters.
  • On dev-02 under nix develop and build-slot, mesh tests passed with relay off: 64 passed, 14 ignored; relay on: 73 passed, 14 ignored. Relay tests passed 12 unit and 4 loopback tests, including shared-budget and relink-cap regressions.
  • The saved root namespace proof ends with still_loaded: [], empty host-state diffs, and unchanged: true.
  • The compute01 scripts/verify fast run remains active against fc1ffa97f; its log is /home/claude/.cache/codex-scratch/j49-mesh-relay-220/verify-fast.log. I have not changed that worktree while it runs.

Next: collect the old gate result, update the compute01 worktree to this head, rerun the required fast gate and record the D243 size delta, then split/squash the round-1 fixes into clean P1 and P2 commits and post the final per-finding memory summary.

J49 checkpoint pushed at `b4c9ba1ae` on `mesh-relay-220`. - The feature-off compile failure came from applying `#[cfg]` to an assignment inside a match arm. I moved the gate onto feature-specific match arms; relay builds still parse seconds+nanos, feature-off builds keep seconds-only counters. - On dev-02 under `nix develop` and `build-slot`, mesh tests passed with relay off: 64 passed, 14 ignored; relay on: 73 passed, 14 ignored. Relay tests passed 12 unit and 4 loopback tests, including shared-budget and relink-cap regressions. - The saved root namespace proof ends with `still_loaded: []`, empty host-state diffs, and `unchanged: true`. - The compute01 `scripts/verify fast` run remains active against `fc1ffa97f`; its log is `/home/claude/.cache/codex-scratch/j49-mesh-relay-220/verify-fast.log`. I have not changed that worktree while it runs. Next: collect the old gate result, update the compute01 worktree to this head, rerun the required fast gate and record the D243 size delta, then split/squash the round-1 fixes into clean P1 and P2 commits and post the final per-finding memory summary.
Author
Owner

J49 gate restart: the earlier compute01 process had exited or been removed before producing a summary; there was no matching J49 process and the log ended during OpenSSL generation. The compute01 worktree was clean, so I fetched b4c9ba1ae and reset that worktree safely. It is still based on the requested f4b61cd6c (git merge-base confirmed exact match).

A fresh scripts/verify fast is now running through build-slot on compute01. Log: /home/claude/.cache/codex-scratch/j49-mesh-relay-220/verify-fast-r2.log. I will report its actual output and D243 static-size delta; the old partial log is retained as evidence of the abandoned run.

J49 gate restart: the earlier compute01 process had exited or been removed before producing a summary; there was no matching J49 process and the log ended during OpenSSL generation. The compute01 worktree was clean, so I fetched `b4c9ba1ae` and reset that worktree safely. It is still based on the requested `f4b61cd6c` (`git merge-base` confirmed exact match). A fresh `scripts/verify fast` is now running through `build-slot` on compute01. Log: `/home/claude/.cache/codex-scratch/j49-mesh-relay-220/verify-fast-r2.log`. I will report its actual output and D243 static-size delta; the old partial log is retained as evidence of the abandoned run.
Author
Owner

J49 round-1 fixes pushed on mesh-relay-220 at 04e9e1241, based on the requested origin/dev commit f4b61cd6c. The final tree is identical to the tested checkpoint tree. Clean commits: 1fc88b2cb (P1) and 04e9e1241 (P2).

P1 — stale same-second handshake blocked relay fallback

direct_failed now compares WireGuard handshake seconds and nanoseconds with the endpoint-install SystemTime. Equal or earlier timestamps allow relay fallback; a later handshake in the same second keeps the direct path. J43's a_same_second_handshake_cannot_prove_it_followed_the_endpoint_update passes, alongside fresh-same-second and exact-equality cases. A selector search found no other seconds-only endpoint-freshness comparison; start_handshake only checks whether a cryptographic session exists. Precision parsing/storage is relay-feature-only, keeping feature-off counter layout unchanged.

P2 — 3 GiB configured relay DATAGRAM queue allowance

The relay now enforces a 64 MiB global forwarding budget and 256 KiB per-pair cap, charging 1 KiB units after a 128-byte per-frame overhead. Reservations travel with shared Bytes storage through Quinn. Relay receive queues are 8 KiB per connection; the 128 KiB send ceiling is bounded in aggregate by the shared reservations. On pressure or a full send queue, DATAGRAMs are dropped and counted, consistent with their unreliable delivery semantics. At 4,096 hosts, the contract bounds configured relay receive plus forwarding queues to 96 MiB, excluding fixed connection/control/link-table state.

Perf VM infraternal-perf-01-backup01, same perf-run --root namespace workload, 256 pairs / 512 hosts / 131,072 attempts:

Metric Before After
Idle RSS/HWM 26,704 KiB 26,632 KiB
Flood peak RSS/HWM 33,772 KiB 33,156 KiB
Peak over idle 7,068 KiB 6,524 KiB
Flood elapsed 2,164 ms 2,173 ms
Client sends accepted / blocked 60,158 / 70,914 75,789 / 55,283

Both runs passed the perf-run quiet check; tc is unavailable on the VM, so loopback was unshaped. Evidence logs: /home/claude/.cache/codex-scratch/j49-mesh-relay-220/{before.log,after.log} and perf-before/, perf-after/.

Feature checks on dev-02 through build-slot / nix develop: mesh feature-off 64 passed / 14 ignored; mesh relay-on 73 passed / 14 ignored; relay tests 12 unit and 4 QUIC loopback passed, including both budget regressions. The root namespace proof reports matching direct, relay, failover and rebound hashes, still_loaded: [], empty host-state diffs and unchanged: true (receipt: /home/claude/.cache/codex-scratch/j49-mesh-relay-220/root-proof-round2.log).

The fresh scripts/verify fast is queued on compute01 through build-slot (initial position 11). I will add its verbatim output and the static D243 size delta when it completes. D243 approval caps were removed; I am reporting the measured delta, not stopping on a cap.

J49 round-1 fixes pushed on `mesh-relay-220` at `04e9e1241`, based on the requested `origin/dev` commit `f4b61cd6c`. The final tree is identical to the tested checkpoint tree. Clean commits: `1fc88b2cb` (P1) and `04e9e1241` (P2). ### P1 — stale same-second handshake blocked relay fallback `direct_failed` now compares WireGuard handshake seconds and nanoseconds with the endpoint-install `SystemTime`. Equal or earlier timestamps allow relay fallback; a later handshake in the same second keeps the direct path. J43's `a_same_second_handshake_cannot_prove_it_followed_the_endpoint_update` passes, alongside fresh-same-second and exact-equality cases. A selector search found no other seconds-only endpoint-freshness comparison; `start_handshake` only checks whether a cryptographic session exists. Precision parsing/storage is relay-feature-only, keeping feature-off counter layout unchanged. ### P2 — 3 GiB configured relay DATAGRAM queue allowance The relay now enforces a 64 MiB global forwarding budget and 256 KiB per-pair cap, charging 1 KiB units after a 128-byte per-frame overhead. Reservations travel with shared `Bytes` storage through Quinn. Relay receive queues are 8 KiB per connection; the 128 KiB send ceiling is bounded in aggregate by the shared reservations. On pressure or a full send queue, DATAGRAMs are dropped and counted, consistent with their unreliable delivery semantics. At 4,096 hosts, the contract bounds configured relay receive plus forwarding queues to 96 MiB, excluding fixed connection/control/link-table state. Perf VM `infraternal-perf-01-backup01`, same `perf-run --root` namespace workload, 256 pairs / 512 hosts / 131,072 attempts: | Metric | Before | After | | --- | ---: | ---: | | Idle RSS/HWM | 26,704 KiB | 26,632 KiB | | Flood peak RSS/HWM | 33,772 KiB | 33,156 KiB | | Peak over idle | 7,068 KiB | 6,524 KiB | | Flood elapsed | 2,164 ms | 2,173 ms | | Client sends accepted / blocked | 60,158 / 70,914 | 75,789 / 55,283 | Both runs passed the perf-run quiet check; `tc` is unavailable on the VM, so loopback was unshaped. Evidence logs: `/home/claude/.cache/codex-scratch/j49-mesh-relay-220/{before.log,after.log}` and `perf-before/`, `perf-after/`. Feature checks on dev-02 through `build-slot` / `nix develop`: mesh feature-off 64 passed / 14 ignored; mesh relay-on 73 passed / 14 ignored; relay tests 12 unit and 4 QUIC loopback passed, including both budget regressions. The root namespace proof reports matching direct, relay, failover and rebound hashes, `still_loaded: []`, empty host-state diffs and `unchanged: true` (receipt: `/home/claude/.cache/codex-scratch/j49-mesh-relay-220/root-proof-round2.log`). The fresh `scripts/verify fast` is queued on compute01 through `build-slot` (initial position 11). I will add its verbatim output and the static D243 size delta when it completes. D243 approval caps were removed; I am reporting the measured delta, not stopping on a cap.
Author
Owner

J49 checkpoint remains clean and pushed at 04e9e1241; no source changes are pending. The compute01 gate ticket has moved to FIFO position 6 but has not acquired a slot yet. Its log currently contains only the initial queue line: /home/claude/.cache/codex-scratch/j49-mesh-relay-220/verify-fast-r2.log. The branch's mesh-relay-220 tree is unchanged from the tested checkpoint. I will post the gate summary and static-size delta when the queued run finishes.

J49 checkpoint remains clean and pushed at `04e9e1241`; no source changes are pending. The compute01 gate ticket has moved to FIFO position 6 but has not acquired a slot yet. Its log currently contains only the initial queue line: `/home/claude/.cache/codex-scratch/j49-mesh-relay-220/verify-fast-r2.log`. The branch's `mesh-relay-220` tree is unchanged from the tested checkpoint. I will post the gate summary and static-size delta when the queued run finishes.
Author
Owner

J49 resuming from the pushed WIP. mesh-relay-220 has been rebased onto current origin/dev 37bbda45554f6e603a6856fc23319e17d8a1b14f and force-pushed with an explicit lease at 960a2a364d90face69abcd0d55025e612449d3f7. The P1 and P2 fixes remain separate commits (964607892 and 960a2a364).

Next I am rerunning feature-on tests and the root namespace proof on dev-02, then scripts/verify fast on compute01. Current dev contains D243 report-only size reporting from 5418f9357; I will include both shipping-target deltas with the gate result. Logs will be under each VM's .cache/codex-scratch/j49-mesh-relay-220/. Issue remains open.

J49 resuming from the pushed WIP. `mesh-relay-220` has been rebased onto current `origin/dev` `37bbda45554f6e603a6856fc23319e17d8a1b14f` and force-pushed with an explicit lease at `960a2a364d90face69abcd0d55025e612449d3f7`. The P1 and P2 fixes remain separate commits (`964607892` and `960a2a364`). Next I am rerunning feature-on tests and the root namespace proof on dev-02, then `scripts/verify fast` on compute01. Current dev contains D243 report-only size reporting from `5418f9357`; I will include both shipping-target deltas with the gate result. Logs will be under each VM's `.cache/codex-scratch/j49-mesh-relay-220/`. Issue remains open.
Author
Owner

J49 post-rebase validation on 960a2a364d90face69abcd0d55025e612449d3f7:

  • P1: the relay-off mesh suite and relay-on mesh suite both passed from the current dev base. The freshness tests cover stale same-second, fresh same-second, and exact equality. No seconds-only endpoint-install comparison exists elsewhere in the selector path.
  • P2: relay runtime tests passed: 12 unit tests and 4 QUIC loopback tests; the relay fixture example rebuilt. The bounded shared-buffer regressions are included in the 12 unit tests. Current run log: /home/claude/.cache/codex-scratch/j49-mesh-relay-220/feature-on-current.log.
  • Namespace proof: the cua sudo/build-slot rerun passed. Direct and relayed 32 MiB streams, the 384 MiB relay-kill/failover stream, and the 16 MiB rebound stream all had matching hashes; the old mapping retired. It ended with still_loaded: [], empty link/netns/module/sysctl diffs, and unchanged: true. Receipt: /home/claude/.cache/codex-scratch/j49-mesh-relay-220/root-proof-current.log. An earlier unprivileged invocation failed to clean modules; I removed the four modules it alone loaded and verified they were absent before the successful proof.

Next: scripts/verify fast on compute01; current dev reports static-size deltas instead of applying D243 size caps.

J49 post-rebase validation on `960a2a364d90face69abcd0d55025e612449d3f7`: - **P1:** the relay-off mesh suite and relay-on mesh suite both passed from the current dev base. The freshness tests cover stale same-second, fresh same-second, and exact equality. No seconds-only endpoint-install comparison exists elsewhere in the selector path. - **P2:** relay runtime tests passed: 12 unit tests and 4 QUIC loopback tests; the relay fixture example rebuilt. The bounded shared-buffer regressions are included in the 12 unit tests. Current run log: `/home/claude/.cache/codex-scratch/j49-mesh-relay-220/feature-on-current.log`. - **Namespace proof:** the `cua` sudo/build-slot rerun passed. Direct and relayed 32 MiB streams, the 384 MiB relay-kill/failover stream, and the 16 MiB rebound stream all had matching hashes; the old mapping retired. It ended with `still_loaded: []`, empty link/netns/module/sysctl diffs, and `unchanged: true`. Receipt: `/home/claude/.cache/codex-scratch/j49-mesh-relay-220/root-proof-current.log`. An earlier unprivileged invocation failed to clean modules; I removed the four modules it alone loaded and verified they were absent before the successful proof. Next: `scripts/verify fast` on compute01; current dev reports static-size deltas instead of applying D243 size caps.
Author
Owner

J49 scripts/verify fast finished on compute01 at 960a2a364d90face69abcd0d55025e612449d3f7; the full log is /home/claude/.cache/codex-scratch/j49-mesh-relay-220/verify-fast-current.log.

All Rust rows passed except the workspace test row (exit 101). The failing test is infraternal/tests/status_restart.rs::service_states_are_truthful_in_text_and_json; the system service query timed out after 0.62 s and returned system/server/activating. Clippy, fixtures, cargo-deny, architecture, and both static builds passed. D243 deltas are x86_64 +6,368 B and aarch64 +6,256 B, report-only. I am rerunning this one test in isolation and against unmodified origin/dev to determine whether the failure is branch-specific.

J49 `scripts/verify fast` finished on compute01 at `960a2a364d90face69abcd0d55025e612449d3f7`; the full log is `/home/claude/.cache/codex-scratch/j49-mesh-relay-220/verify-fast-current.log`. All Rust rows passed except the workspace test row (exit 101). The failing test is `infraternal/tests/status_restart.rs::service_states_are_truthful_in_text_and_json`; the system service query timed out after 0.62 s and returned `system/server/activating`. Clippy, fixtures, cargo-deny, architecture, and both static builds passed. D243 deltas are x86_64 +6,368 B and aarch64 +6,256 B, report-only. I am rerunning this one test in isolation and against unmodified `origin/dev` to determine whether the failure is branch-specific.
Author
Owner

The isolated service_states_are_truthful_in_text_and_json rerun also fails on J49 head 960a2a364 in 0.23 s with system service; service manager query timed out. No other build/gate process is running from J49. I am using the same compute01 worktree, temporarily detached at origin/dev, to run the exact test through build-slot, then restoring mesh-relay-220; this will establish whether the failure is pre-existing without leaving another worktree or target directory.

The isolated `service_states_are_truthful_in_text_and_json` rerun also fails on J49 head `960a2a364` in 0.23 s with `system service; service manager query timed out`. No other build/gate process is running from J49. I am using the same compute01 worktree, temporarily detached at `origin/dev`, to run the exact test through `build-slot`, then restoring `mesh-relay-220`; this will establish whether the failure is pre-existing without leaving another worktree or target directory.
Author
Owner

Baseline comparison complete: on the same compute01 machine and through build-slot, origin/dev 37bbda45554f6e603a6856fc23319e17d8a1b14f passed service_states_are_truthful_in_text_and_json 1/1 in 1.35 s. The temporary detached checkout restored mesh-relay-220 to 960a2a364d90face69abcd0d55025e612449d3f7 with a clean worktree.

This does not reproduce the J49 full-gate failure on dev. I am rerunning scripts/verify fast under build-slot at the same head to check whether the earlier 0.62 s system-service timeout was load-sensitive.

Baseline comparison complete: on the same compute01 machine and through build-slot, `origin/dev` `37bbda45554f6e603a6856fc23319e17d8a1b14f` passed `service_states_are_truthful_in_text_and_json` 1/1 in 1.35 s. The temporary detached checkout restored `mesh-relay-220` to `960a2a364d90face69abcd0d55025e612449d3f7` with a clean worktree. This does not reproduce the J49 full-gate failure on dev. I am rerunning `scripts/verify fast` under build-slot at the same head to check whether the earlier 0.62 s system-service timeout was load-sensitive.
Author
Owner

J49 round-2 implementation finished. Branch mesh-relay-220 is rebased on origin/dev 37bbda45554f6e603a6856fc23319e17d8a1b14f, clean, and pushed with lease at 960a2a364d90face69abcd0d55025e612449d3f7. P1 and P2 are separate commits: 964607892 and 960a2a364.

P1 — stale same-second handshake suppressed relay fallback

direct_failed now compares WireGuard handshake seconds and nanoseconds to the endpoint-install SystemTime. Equal or older timestamps allow relay fallback; a later handshake in the same second proves the direct path. J43's a_same_second_handshake_cannot_prove_it_followed_the_endpoint_update failed before the fix and passes now, along with the fresh-same-second and exact-equality cases. The selector-path search found no other endpoint-freshness comparison using seconds-only ordering; start_handshake only checks whether a cryptographic session exists. The contract records the ordering rule.

P2 — aggregate relay DATAGRAM memory bound

The relay now has a 64 MiB global forwarding budget and a 256 KiB per-pair cap, charged in 1 KiB units with 128 bytes per-frame allowance. Forwarding retains the existing shared Bytes buffer, and both reservations stay with it through Quinn's send queue. DATAGRAMs are dropped and counted when a budget or send queue is full. The relay receive queue is 8 KiB per connection and the send ceiling is 128 KiB. At 4,096 hosts, configured receive queues plus the global forwarding budget account for at most 96 MiB of DATAGRAM buffering, excluding fixed connection, control, and link-table state. contracts/mesh-relay.md documents these limits and the drop behavior.

Perf VM infraternal-perf-01-backup01, using perf-run --root in an isolated network namespace, same abuse workload: 256 pairs / 512 hosts / 131,072 attempts. Both quiet checks passed. tc is unavailable, so loopback was unshaped.

Metric Before After
Idle RSS/HWM 26,704 KiB 26,632 KiB
Flood peak RSS/HWM 33,772 KiB 33,156 KiB
Peak over idle 7,068 KiB 6,524 KiB
Flood elapsed 2,164 ms 2,173 ms
Client sends accepted / blocked 60,158 / 70,914 75,789 / 55,283

Feature-on checks on dev-02 through build-slot / nix develop: mesh relay-off 64 passed / 14 ignored; mesh relay-on 73 passed / 14 ignored; relay unit tests 12/12; QUIC loopback tests 4/4. This includes the J43 regression and shared-budget regressions.

The root namespace proof ran through cua sudo and build-slot on dev-02. Direct and relayed 32 MiB streams, the 384 MiB relay-failover stream, and the 16 MiB rebound stream had matching SHA-256 hashes; the old mapping retired. Relay-kill maximum gap was 3,132.8 ms and recovery after relay restart was 551.4 ms. The proof ended with still_loaded: [], empty link/netns/module/sysctl diffs, unchanged: true, and exit 0. Receipt: /home/claude/.cache/codex-scratch/j49-mesh-relay-220/root-proof-current.log.

The first full fast-gate attempt had a system-service query timeout in service_states_are_truthful_in_text_and_json; the same test passed 1/1 on unmodified origin/dev, and it passed in this complete rerun. scripts/verify fast ran on compute01 through build-slot, exit 0. Current origin/dev contains 5418f9357; static builds are report-only. D243 deltas are x86_64 +6,368 B and aarch64 +6,256 B.

Verbatim gate summary:

+---------------------------+--------------+
| Rust gate                 | Result       |
+---------------------------+--------------+
| fmt                       | PASS         |
| VT native fmt             | PASS         |
| CLI plugin compile-out    | PASS         |
| clippy                    | PASS         |
| SSH runtime test helper   | PASS         |
| real-binary fixture paths | PASS         |
| test                      | PASS         |
| SSH inode reuse fixture   | PASS         |
| shim process-per-user     | PASS         |
| cargo-deny                | PASS         |
| gate architecture         | PASS         |
| static builds             | PASS         |
+---------------------------+--------------+

PT_INTERP: absent; size: 21450192 bytes; baseline 21443824; delta +6368 bytes (report only, D243 amended 2026-11-01)
PT_INTERP: absent; size: 19635496 bytes; baseline 19629240; delta +6256 bytes (report only, D243 amended 2026-11-01)

stream differential: 169 streams, 19929 schedules, zero divergences

39 pass
0 fail
59970 expect() calls
Ran 39 tests across 10 files. [9.15s]

EXIT=0

Full gate log: /home/claude/.cache/codex-scratch/j49-mesh-relay-220/verify-fast-r2.log. Perf receipts: /home/claude/.cache/codex-scratch/j49-mesh-relay-220/before.log and after.log. Feature log: /home/claude/.cache/codex-scratch/j49-mesh-relay-220/feature-on-current.log. All J49 target directories were removed after validation. Issue remains open.

J49 round-2 implementation finished. Branch `mesh-relay-220` is rebased on `origin/dev` `37bbda45554f6e603a6856fc23319e17d8a1b14f`, clean, and pushed with lease at `960a2a364d90face69abcd0d55025e612449d3f7`. P1 and P2 are separate commits: `964607892` and `960a2a364`. ### P1 — stale same-second handshake suppressed relay fallback `direct_failed` now compares WireGuard handshake seconds and nanoseconds to the endpoint-install `SystemTime`. Equal or older timestamps allow relay fallback; a later handshake in the same second proves the direct path. J43's `a_same_second_handshake_cannot_prove_it_followed_the_endpoint_update` failed before the fix and passes now, along with the fresh-same-second and exact-equality cases. The selector-path search found no other endpoint-freshness comparison using seconds-only ordering; `start_handshake` only checks whether a cryptographic session exists. The contract records the ordering rule. ### P2 — aggregate relay DATAGRAM memory bound The relay now has a 64 MiB global forwarding budget and a 256 KiB per-pair cap, charged in 1 KiB units with 128 bytes per-frame allowance. Forwarding retains the existing shared `Bytes` buffer, and both reservations stay with it through Quinn's send queue. DATAGRAMs are dropped and counted when a budget or send queue is full. The relay receive queue is 8 KiB per connection and the send ceiling is 128 KiB. At 4,096 hosts, configured receive queues plus the global forwarding budget account for at most 96 MiB of DATAGRAM buffering, excluding fixed connection, control, and link-table state. `contracts/mesh-relay.md` documents these limits and the drop behavior. Perf VM `infraternal-perf-01-backup01`, using `perf-run --root` in an isolated network namespace, same abuse workload: 256 pairs / 512 hosts / 131,072 attempts. Both quiet checks passed. `tc` is unavailable, so loopback was unshaped. | Metric | Before | After | | --- | ---: | ---: | | Idle RSS/HWM | 26,704 KiB | 26,632 KiB | | Flood peak RSS/HWM | 33,772 KiB | 33,156 KiB | | Peak over idle | 7,068 KiB | 6,524 KiB | | Flood elapsed | 2,164 ms | 2,173 ms | | Client sends accepted / blocked | 60,158 / 70,914 | 75,789 / 55,283 | Feature-on checks on dev-02 through `build-slot` / `nix develop`: mesh relay-off 64 passed / 14 ignored; mesh relay-on 73 passed / 14 ignored; relay unit tests 12/12; QUIC loopback tests 4/4. This includes the J43 regression and shared-budget regressions. The root namespace proof ran through `cua` sudo and `build-slot` on dev-02. Direct and relayed 32 MiB streams, the 384 MiB relay-failover stream, and the 16 MiB rebound stream had matching SHA-256 hashes; the old mapping retired. Relay-kill maximum gap was 3,132.8 ms and recovery after relay restart was 551.4 ms. The proof ended with `still_loaded: []`, empty link/netns/module/sysctl diffs, `unchanged: true`, and exit 0. Receipt: `/home/claude/.cache/codex-scratch/j49-mesh-relay-220/root-proof-current.log`. The first full fast-gate attempt had a system-service query timeout in `service_states_are_truthful_in_text_and_json`; the same test passed 1/1 on unmodified `origin/dev`, and it passed in this complete rerun. `scripts/verify fast` ran on compute01 through `build-slot`, exit 0. Current `origin/dev` contains `5418f9357`; static builds are report-only. D243 deltas are x86_64 +6,368 B and aarch64 +6,256 B. Verbatim gate summary: ```text +---------------------------+--------------+ | Rust gate | Result | +---------------------------+--------------+ | fmt | PASS | | VT native fmt | PASS | | CLI plugin compile-out | PASS | | clippy | PASS | | SSH runtime test helper | PASS | | real-binary fixture paths | PASS | | test | PASS | | SSH inode reuse fixture | PASS | | shim process-per-user | PASS | | cargo-deny | PASS | | gate architecture | PASS | | static builds | PASS | +---------------------------+--------------+ PT_INTERP: absent; size: 21450192 bytes; baseline 21443824; delta +6368 bytes (report only, D243 amended 2026-11-01) PT_INTERP: absent; size: 19635496 bytes; baseline 19629240; delta +6256 bytes (report only, D243 amended 2026-11-01) stream differential: 169 streams, 19929 schedules, zero divergences 39 pass 0 fail 59970 expect() calls Ran 39 tests across 10 files. [9.15s] EXIT=0 ``` Full gate log: `/home/claude/.cache/codex-scratch/j49-mesh-relay-220/verify-fast-r2.log`. Perf receipts: `/home/claude/.cache/codex-scratch/j49-mesh-relay-220/before.log` and `after.log`. Feature log: `/home/claude/.cache/codex-scratch/j49-mesh-relay-220/feature-on-current.log`. All J49 target directories were removed after validation. Issue remains open.
Author
Owner

J65 round-2 review starting on review-j65-220, with review base origin/dev 37bbda45554f6e603a6856fc23319e17d8a1b14f and target head 960a2a364d90face69abcd0d55025e612449d3f7.

Plan (estimated 30 minutes): (1) verify J43's freshness and aggregate-queue findings against the fix diff and tests; (2) trace relay admission, forwarding, resource bounds, and failover against D313, D316, and the contract under hostile inputs; (3) post evidence-backed P0–P3 findings or APPROVE here. I will run tests only if needed to prove a concrete finding.

J65 round-2 review starting on `review-j65-220`, with review base `origin/dev` `37bbda45554f6e603a6856fc23319e17d8a1b14f` and target head `960a2a364d90face69abcd0d55025e612449d3f7`. Plan (estimated 30 minutes): (1) verify J43's freshness and aggregate-queue findings against the fix diff and tests; (2) trace relay admission, forwarding, resource bounds, and failover against D313, D316, and the contract under hostile inputs; (3) post evidence-backed P0–P3 findings or APPROVE here. I will run tests only if needed to prove a concrete finding.
Author
Owner

Verdict: HOLD for fixes. Review target mesh-relay-220 at 960a2a364d90face69abcd0d55025e612449d3f7, based on origin/dev 37bbda45554f6e603a6856fc23319e17d8a1b14f.

The two J43 findings are fixed: direct_failed compares the validated kernel seconds+nanos timestamp with the endpoint-install time, and the stale-same-second, fresh-same-second, and equality cases are covered. The forwarding path reserves both global and per-pair units, keeps reservations in the shared Bytes owner, and counts application-level invalid, unlinked, budget-refused, and send-queue-refused datagrams.

  1. P1 — Unbounded pre-authentication handshakes. crates/plugins/relay/src/relay.rs:583-615 spawns one serve task for every Quinn Incoming; the hosts limit is checked only at :341, after the QUIC/TLS handshake, certificate admission and control hello. The 5-second handshake timeout bounds each task's lifetime, not the number alive at once. An unauthenticated sender can hold arbitrary incoming handshake tasks and Quinn state concurrently, bypassing both the host cap and admission callback. Add a bounded in-flight handshake limit before spawning/accepting each task.

  2. P2 — One admitted host can monopolize the global forwarding budget. crates/plugins/relay/src/relay.rs:38-41,57-58,171-184,388-414 has global and per-pair budgets but no per-host aggregate budget. A host may hold 1,024 requests; with 256 mutually requested, backpressured pairs, it can reserve 256 × 256 KiB = the whole 64 MiB global budget. Other pairs then fail the same global reservation and drop until the attacker's queues drain. The memory cap holds, but one peer can deny relay service to unrelated pairs. Add per-host aggregate quota or fair global admission.

  3. P3 — Captured probes can be replayed as a one-for-one reflector. crates/plugins/relay/src/probe.rs:80-96 verifies a request HMAC and immediately creates a reply without tracking transaction IDs; crates/plugins/relay/src/relay.rs:418-432,465-472 sends that reply to the packet's source address. A party that captures a valid live probe can replay it with a spoofed source and make the relay reflect an 84-byte reply. It is not an amplifier (request and reply are equal-sized), and the target node will not accept it as a mapping, but it is still reflection. Add bounded replay suppression or equivalent source/replay validation.

No P0 found. The relay forwards inner WireGuard ciphertext; TLS identity, admission, mutual link requests, and the sending-connection check prevent a third peer from hijacking a link. Slice-1 ordered relay retry and no-failback behavior match the contract; D197 quality selection and UDP-blocked TLS/WebSocket support are explicitly deferred.

Review-only: no tests or scripts/verify fast run; the findings follow directly from these code paths, and the review scope permits tests only when needed to prove a finding. Target head reviewed: `960a2a364d. No code or test files changed.

Verdict: HOLD for fixes. Review target `mesh-relay-220` at `960a2a364d90face69abcd0d55025e612449d3f7`, based on `origin/dev` `37bbda45554f6e603a6856fc23319e17d8a1b14f`. The two J43 findings are fixed: `direct_failed` compares the validated kernel seconds+nanos timestamp with the endpoint-install time, and the stale-same-second, fresh-same-second, and equality cases are covered. The forwarding path reserves both global and per-pair units, keeps reservations in the shared `Bytes` owner, and counts application-level invalid, unlinked, budget-refused, and send-queue-refused datagrams. 1. **P1 — Unbounded pre-authentication handshakes.** `crates/plugins/relay/src/relay.rs:583-615` spawns one `serve` task for every Quinn `Incoming`; the `hosts` limit is checked only at `:341`, after the QUIC/TLS handshake, certificate admission and control hello. The 5-second handshake timeout bounds each task's lifetime, not the number alive at once. An unauthenticated sender can hold arbitrary incoming handshake tasks and Quinn state concurrently, bypassing both the host cap and admission callback. Add a bounded in-flight handshake limit before spawning/accepting each task. 2. **P2 — One admitted host can monopolize the global forwarding budget.** `crates/plugins/relay/src/relay.rs:38-41,57-58,171-184,388-414` has global and per-pair budgets but no per-host aggregate budget. A host may hold 1,024 requests; with 256 mutually requested, backpressured pairs, it can reserve 256 × 256 KiB = the whole 64 MiB global budget. Other pairs then fail the same global reservation and drop until the attacker's queues drain. The memory cap holds, but one peer can deny relay service to unrelated pairs. Add per-host aggregate quota or fair global admission. 3. **P3 — Captured probes can be replayed as a one-for-one reflector.** `crates/plugins/relay/src/probe.rs:80-96` verifies a request HMAC and immediately creates a reply without tracking transaction IDs; `crates/plugins/relay/src/relay.rs:418-432,465-472` sends that reply to the packet's source address. A party that captures a valid live probe can replay it with a spoofed source and make the relay reflect an 84-byte reply. It is not an amplifier (request and reply are equal-sized), and the target node will not accept it as a mapping, but it is still reflection. Add bounded replay suppression or equivalent source/replay validation. No P0 found. The relay forwards inner WireGuard ciphertext; TLS identity, admission, mutual link requests, and the sending-connection check prevent a third peer from hijacking a link. Slice-1 ordered relay retry and no-failback behavior match the contract; D197 quality selection and UDP-blocked TLS/WebSocket support are explicitly deferred. Review-only: no tests or `scripts/verify fast` run; the findings follow directly from these code paths, and the review scope permits tests only when needed to prove a finding. Target head reviewed: `960a2a364d90face69abcd0d55025e612449d3f7. No code or test files changed.
Author
Owner

J65 round-2 fix work starting on mesh-relay-220 at 960a2a364d90face69abcd0d55025e612449d3f7, based on current origin/dev 37bbda45554f6e603a6856fc23319e17d8a1b14f.

Plan (estimated 110 minutes):

  1. P1: add a failing pre-auth admission regression, then enforce a global concurrent limit, per-source-IP limit, and handshake timeout before spawning untrusted connection work; verify and commit atomically.
  2. P2: add a failing test with one host holding 256 backpressured pairs, then add its aggregate forwarding quota; verify and commit atomically.
  3. P3: add a failing captured-probe replay regression, then bounded transaction replay suppression; verify and commit atomically.
  4. Run the touched relay checks and scripts/verify iterate on dev-02 between commits. Measure the handshake flood before and after on the perf VM via perf-run --root, building on dev-02. Run final scripts/verify fast on compute01, post the output and measurements, and remove J65 target directories.

Scope stays within the relay/probe code, directly related tests, and the smallest measurement support needed. No repository-wide infrastructure changes.

J65 round-2 fix work starting on `mesh-relay-220` at `960a2a364d90face69abcd0d55025e612449d3f7`, based on current `origin/dev` `37bbda45554f6e603a6856fc23319e17d8a1b14f`. Plan (estimated 110 minutes): 1. P1: add a failing pre-auth admission regression, then enforce a global concurrent limit, per-source-IP limit, and handshake timeout before spawning untrusted connection work; verify and commit atomically. 2. P2: add a failing test with one host holding 256 backpressured pairs, then add its aggregate forwarding quota; verify and commit atomically. 3. P3: add a failing captured-probe replay regression, then bounded transaction replay suppression; verify and commit atomically. 4. Run the touched relay checks and `scripts/verify iterate` on dev-02 between commits. Measure the handshake flood before and after on the perf VM via `perf-run --root`, building on dev-02. Run final `scripts/verify fast` on compute01, post the output and measurements, and remove J65 target directories. Scope stays within the relay/probe code, directly related tests, and the smallest measurement support needed. No repository-wide infrastructure changes.
Author
Owner

P1 — pre-auth handshake task bound

Committed and pushed ac86fd5cc. accept now reserves one of 256 global and eight per-source-IP slots (defaults) before spawning serve. IPv4-mapped IPv6 sources are canonicalized, excess Incoming connections are refused before task creation, and the permit is released after the authenticated host and IFR1 hello are accepted. The existing five-second TLS and hello deadlines remain in force.

Evidence: relay_bounds_stalled_handshakes_from_one_source_ip failed before the fix with left: 9, right: 8; after the fix the relay crate passed 13 unit tests and 5 loopback tests, and cargo fmt --all -- --check passed.

The dev-02 scripts/verify iterate attempt was stopped before completion when shared disk fell below the 25 GiB floor (4.2 GiB free at stop). Its log is ~/.cache/codex-scratch/j65-mesh-relay-220/p1-iterate.log; the J65 target was removed afterward. No iterate-gate pass is claimed. Continuing with P2; final scripts/verify fast remains assigned to compute01.

### P1 — pre-auth handshake task bound Committed and pushed `ac86fd5cc`. `accept` now reserves one of 256 global and eight per-source-IP slots (defaults) before spawning `serve`. IPv4-mapped IPv6 sources are canonicalized, excess Incoming connections are refused before task creation, and the permit is released after the authenticated host and `IFR1` hello are accepted. The existing five-second TLS and hello deadlines remain in force. Evidence: `relay_bounds_stalled_handshakes_from_one_source_ip` failed before the fix with `left: 9, right: 8`; after the fix the relay crate passed 13 unit tests and 5 loopback tests, and `cargo fmt --all -- --check` passed. The dev-02 `scripts/verify iterate` attempt was stopped before completion when shared disk fell below the 25 GiB floor (4.2 GiB free at stop). Its log is `~/.cache/codex-scratch/j65-mesh-relay-220/p1-iterate.log`; the J65 target was removed afterward. No iterate-gate pass is claimed. Continuing with P2; final `scripts/verify fast` remains assigned to compute01.
Author
Owner

P2 — per-host aggregate queue share

Committed and pushed 5b8c832ed. A forwarded DATAGRAM now reserves the global, source-host and per-pair budgets together, retaining those reservations in the shared Bytes owner. The source-host cap is 4 MiB across its pairs; it is reused across reconnects while older queued buffers still hold reservations. This leaves unrelated source hosts able to use the remaining 60 MiB of the 64 MiB global budget.

Evidence: the 256-pair regression failed before the fix because its unrelated pair could not reserve a global unit. After the fix, relay tests passed 15 unit and 5 loopback tests, including the 256-pair case and reconnect-budget case; cargo fmt --all -- --check and relay all-target clippy with -D warnings passed.

P1 commit hash correction: the clippy follow-up was folded into the same atomic commit and force-pushed with a lease. The current P1 commit is 1ccdd066ac74ad1d12b19551032cf92946b8e9e0 (the earlier ac86fd5cc hash in my prior comment was replaced).

### P2 — per-host aggregate queue share Committed and pushed `5b8c832ed`. A forwarded DATAGRAM now reserves the global, source-host and per-pair budgets together, retaining those reservations in the shared `Bytes` owner. The source-host cap is 4 MiB across its pairs; it is reused across reconnects while older queued buffers still hold reservations. This leaves unrelated source hosts able to use the remaining 60 MiB of the 64 MiB global budget. Evidence: the 256-pair regression failed before the fix because its unrelated pair could not reserve a global unit. After the fix, relay tests passed 15 unit and 5 loopback tests, including the 256-pair case and reconnect-budget case; `cargo fmt --all -- --check` and relay all-target clippy with `-D warnings` passed. P1 commit hash correction: the clippy follow-up was folded into the same atomic commit and force-pushed with a lease. The current P1 commit is `1ccdd066ac74ad1d12b19551032cf92946b8e9e0` (the earlier `ac86fd5cc` hash in my prior comment was replaced).
Author
Owner

J65 resumed from mesh-relay-220 at 5b8c832ed, based exactly on origin/dev 37bbda455. The current HOLD findings are being checked against the pushed P1/P2 commits and local P3 patch. dev-02 fell below its 25 GiB build floor (5 GiB free); no J65 build is running there. I removed only the stale J65 target directory, which raised free space to 11 GiB. I am preparing the P3 red/green reproduction and flood harness while waiting for the shared disk floor to recover. No verification pass is claimed yet.

J65 resumed from mesh-relay-220 at 5b8c832ed, based exactly on origin/dev 37bbda455. The current HOLD findings are being checked against the pushed P1/P2 commits and local P3 patch. dev-02 fell below its 25 GiB build floor (5 GiB free); no J65 build is running there. I removed only the stale J65 target directory, which raised free space to 11 GiB. I am preparing the P3 red/green reproduction and flood harness while waiting for the shared disk floor to recover. No verification pass is claimed yet.
Author
Owner

J65 rebase update: mesh-relay-220 is now based on current origin/dev a26615c50e76ed86f36d25aa094283cf840d5320 and pushed at b9c7c33ecbac72c10d08946b9c5376af528113fc. The three finding commits are P1 7a70bccd7 (pre-auth global/source-IP caps; includes the 1,024-Initial flood driver), P2 89619b35c (per-host queue share), and P3 b9c7c33ec (per-session probe source binding). These hashes supersede earlier J65 hash comments.

P3 RED evidence on dev-02: with only the regression test added to the pre-fix probe.rs, a_captured_request_cannot_be_replayed_to_a_different_source failed at the intended assertion because the relay returned a reply to the spoofed source (RED_EXIT=101). The fix now passes that test. I am rerunning relay and mesh feature-on/off checks on the rebased head; no final gate result is claimed yet.

J65 rebase update: `mesh-relay-220` is now based on current `origin/dev` `a26615c50e76ed86f36d25aa094283cf840d5320` and pushed at `b9c7c33ecbac72c10d08946b9c5376af528113fc`. The three finding commits are P1 `7a70bccd7` (pre-auth global/source-IP caps; includes the 1,024-Initial flood driver), P2 `89619b35c` (per-host queue share), and P3 `b9c7c33ec` (per-session probe source binding). These hashes supersede earlier J65 hash comments. P3 RED evidence on dev-02: with only the regression test added to the pre-fix `probe.rs`, `a_captured_request_cannot_be_replayed_to_a_different_source` failed at the intended assertion because the relay returned a reply to the spoofed source (`RED_EXIT=101`). The fix now passes that test. I am rerunning relay and mesh feature-on/off checks on the rebased head; no final gate result is claimed yet.
Author
Owner

J65 rebased feature-check evidence from a26615c50 head b9c7c33ec: the relay crate passed 17 unit tests and 5 QUIC loopback tests; all-target clippy with -D warnings passed. Mesh agent-only tests passed (38 passed, 13 ignored in the unit binary), and mesh relay-on tests passed (47 passed, 13 ignored in the unit binary); the remaining package integration/doc test binaries in both runs also completed with zero failures. cargo fmt --all -- --check passed. I am building the relay fixture for the final root namespace proof now.

J65 rebased feature-check evidence from `a26615c50` head `b9c7c33ec`: the relay crate passed 17 unit tests and 5 QUIC loopback tests; all-target clippy with `-D warnings` passed. Mesh agent-only tests passed (38 passed, 13 ignored in the unit binary), and mesh relay-on tests passed (47 passed, 13 ignored in the unit binary); the remaining package integration/doc test binaries in both runs also completed with zero failures. `cargo fmt --all -- --check` passed. I am building the relay fixture for the final root namespace proof now.
Author
Owner

J65 continuation from the pushed WIP: branch head remains b9c7c33ec, clean and based on current origin/dev a26615c50. The three findings remain in three separate commits; the review comment's RED evidence and the feature-on/off checks are recorded above.

The dev-02 root namespace proof is still waiting in build-slot. Free disk has fallen to 24 GiB, below its 25 GiB start floor, so the proof has not started. Its queue log is ~/.cache/codex-scratch/j65-mesh-relay-220/root-proof.log.

For the requested P1 measurement, compute01 now has a J65 worktree. The release build of the committed handshake-flood driver at pre-cap parent 850064878 is queued through build-slot; log: ~/.cache/codex-scratch/j65-mesh-relay-220/build-before.log. Next: run that baseline and final-head binaries through perf-run --root, complete the root proof, run scripts/verify fast on compute01, and remove J65 target directories.

J65 continuation from the pushed WIP: branch head remains b9c7c33ec, clean and based on current origin/dev a26615c50. The three findings remain in three separate commits; the review comment's RED evidence and the feature-on/off checks are recorded above. The dev-02 root namespace proof is still waiting in build-slot. Free disk has fallen to 24 GiB, below its 25 GiB start floor, so the proof has not started. Its queue log is ~/.cache/codex-scratch/j65-mesh-relay-220/root-proof.log. For the requested P1 measurement, compute01 now has a J65 worktree. The release build of the committed handshake-flood driver at pre-cap parent 850064878 is queued through build-slot; log: ~/.cache/codex-scratch/j65-mesh-relay-220/build-before.log. Next: run that baseline and final-head binaries through perf-run --root, complete the root proof, run scripts/verify fast on compute01, and remove J65 target directories.
Author
Owner

P1 flood baseline completed on infraternal-perf-01-backup01 using perf-run --root (cpus 1–3, 6 GiB cap, quiet-check receipts retained). The pre-cap relay at 850064878 received 1,024 attempted Initials from 128 loopback source IPs, eight per source, while the clients blackholed server replies and withheld the control hello. Idle RSS/HWM was 8,256 KiB; flood RSS/HWM was 28,064 KiB; peak-over-idle was 19,808 KiB; sample time was 2,003 ms. Perf log: /home/claude/.cache/codex-scratch/j65-mesh-relay-220/perf-before.log.

The final-head release build is queued on compute01 through build-slot. The dev-02 proof is queued through the CUA-sudo/real-UID-preserving wrapper; dev-02 free disk is currently 24 GiB, below the 25 GiB floor. No post-fix flood, root proof, or final gate result is claimed yet.

P1 flood baseline completed on `infraternal-perf-01-backup01` using `perf-run --root` (cpus 1–3, 6 GiB cap, quiet-check receipts retained). The pre-cap relay at 850064878 received 1,024 attempted Initials from 128 loopback source IPs, eight per source, while the clients blackholed server replies and withheld the control hello. Idle RSS/HWM was 8,256 KiB; flood RSS/HWM was 28,064 KiB; peak-over-idle was 19,808 KiB; sample time was 2,003 ms. Perf log: `/home/claude/.cache/codex-scratch/j65-mesh-relay-220/perf-before.log`. The final-head release build is queued on compute01 through build-slot. The dev-02 proof is queued through the CUA-sudo/real-UID-preserving wrapper; dev-02 free disk is currently 24 GiB, below the 25 GiB floor. No post-fix flood, root proof, or final gate result is claimed yet.
Author
Owner

J65 continuation at b9c7c33ec on mesh-relay-220, based on current origin/dev a26615c50; the three fix commits remain pushed separately. RED evidence, feature-on/off checks, and the before/after handshake-flood measurements are recorded above.

The fresh scripts/verify fast run is still active on compute01; log: /home/claude/.cache/codex-scratch/j65-mesh-relay-220/verify-fast.log. The root namespace proof is queued on dev-02. It will run with CUA sudo while a claude-owned build-slot reservation is held; proof log and exit receipt: /home/claude/.cache/codex-scratch/j65-mesh-relay-220/root-proof.log and root-proof.exit. I will post the verbatim gate result and namespace evidence when both complete.

J65 continuation at `b9c7c33ec` on `mesh-relay-220`, based on current `origin/dev` `a26615c50`; the three fix commits remain pushed separately. RED evidence, feature-on/off checks, and the before/after handshake-flood measurements are recorded above. The fresh `scripts/verify fast` run is still active on compute01; log: `/home/claude/.cache/codex-scratch/j65-mesh-relay-220/verify-fast.log`. The root namespace proof is queued on dev-02. It will run with CUA sudo while a claude-owned `build-slot` reservation is held; proof log and exit receipt: `/home/claude/.cache/codex-scratch/j65-mesh-relay-220/root-proof.log` and `root-proof.exit`. I will post the verbatim gate result and namespace evidence when both complete.
Author
Owner

Namespace proof

The root namespace proof passed on dev-02/aarch64 at the current branch head, with CUA sudo under a claude-owned build-slot reservation. Direct and relayed 32 MiB streams, the 384 MiB relay-kill/failover stream, and the 16 MiB rebound stream all had matching SHA-256 hashes. Relay-kill maximum stream gap was 3,107.7 ms; recovery after relay restart was 4,086.8 ms. The old mapping retired. The proof ended with still_loaded: [], empty link/netns/module/sysctl diffs, unchanged: true, and exit 0. Full receipt: /home/claude/.cache/codex-scratch/j65-mesh-relay-220/root-proof.log on dev-02.

The final scripts/verify fast is still active on compute01. Log: /home/claude/.cache/codex-scratch/j65-mesh-relay-220/verify-fast.log. I will post its verbatim output after completion.

### Namespace proof The root namespace proof passed on dev-02/aarch64 at the current branch head, with CUA sudo under a claude-owned `build-slot` reservation. Direct and relayed 32 MiB streams, the 384 MiB relay-kill/failover stream, and the 16 MiB rebound stream all had matching SHA-256 hashes. Relay-kill maximum stream gap was 3,107.7 ms; recovery after relay restart was 4,086.8 ms. The old mapping retired. The proof ended with `still_loaded: []`, empty link/netns/module/sysctl diffs, `unchanged: true`, and exit 0. Full receipt: `/home/claude/.cache/codex-scratch/j65-mesh-relay-220/root-proof.log` on dev-02. The final `scripts/verify fast` is still active on compute01. Log: `/home/claude/.cache/codex-scratch/j65-mesh-relay-220/verify-fast.log`. I will post its verbatim output after completion.
Author
Owner

origin/dev advanced to afbe1c5fe during the queued verification. I rebased mesh-relay-220 onto it and pushed with an explicit lease at 7bbef6359. The three J65 fixes remain separate commits: pre-auth caps ebd2ec6f3, per-host queue share 334ce6700, and probe replay binding 7bbef6359.

The previous full gate and namespace proof passed at b9c7c33ec; that result is superseded by this rebase. I am refreshing the feature-on checks and fixture on dev-02, the root namespace proof, and the final scripts/verify fast on compute01. I will report the new receipts at the rebased head.

`origin/dev` advanced to `afbe1c5fe` during the queued verification. I rebased `mesh-relay-220` onto it and pushed with an explicit lease at `7bbef6359`. The three J65 fixes remain separate commits: pre-auth caps `ebd2ec6f3`, per-host queue share `334ce6700`, and probe replay binding `7bbef6359`. The previous full gate and namespace proof passed at `b9c7c33ec`; that result is superseded by this rebase. I am refreshing the feature-on checks and fixture on dev-02, the root namespace proof, and the final `scripts/verify fast` on compute01. I will report the new receipts at the rebased head.
Author
Owner

J65 rebase update: origin/dev advanced to 65d986191, so I rebased mesh-relay-220 and pushed with lease at 051fdd2df. The three findings remain in separate commits: P1 dd5b9d9a4, P2 1d8f0bb20, P3 051fdd2df.

The compute01 gate queued for the earlier afbe1c5fe base was stopped before entering a slot (exit 143; no gate work ran). I am refreshing relay/mesh feature-on checks and the root namespace proof on dev-02. The replacement compute01 gate is queued with CARGO_BUILD_JOBS=4 and the short test-state-root override. Logs will use feature-checks-final.*, root-proof-final.*, and verify-fast-final.* under ~/.cache/codex-scratch/j65-mesh-relay-220/.

J65 rebase update: `origin/dev` advanced to `65d986191`, so I rebased `mesh-relay-220` and pushed with lease at `051fdd2df`. The three findings remain in separate commits: P1 `dd5b9d9a4`, P2 `1d8f0bb20`, P3 `051fdd2df`. The compute01 gate queued for the earlier `afbe1c5fe` base was stopped before entering a slot (exit 143; no gate work ran). I am refreshing relay/mesh feature-on checks and the root namespace proof on dev-02. The replacement compute01 gate is queued with `CARGO_BUILD_JOBS=4` and the short test-state-root override. Logs will use `feature-checks-final.*`, `root-proof-final.*`, and `verify-fast-final.*` under `~/.cache/codex-scratch/j65-mesh-relay-220/`.
Author
Owner

J65 validation on current head 051fdd2df is progressing: the relay and mesh feature checks, clippy, and relay fixture build finished with FEATURE_CHECKS_FINAL_EXIT=0 on dev-02. The refreshed CUA-sudo namespace proof also passed under a claude-owned build-slot reservation. Its direct and relayed 32 MiB streams, 384 MiB relay-failover stream, and 16 MiB rebound stream all matched SHA-256; the old mapping retired, the relay-failover maximum gap was 3,099.1 ms, recovery after restart was 4,910.8 ms, still_loaded was empty, host link/netns/module/sysctl diffs were empty, and unchanged: true. Full receipt: /home/claude/.cache/codex-scratch/j65-mesh-relay-220/root-proof-final.log on dev-02.

I removed the completed dev-02 J65 target after copying the fixture. The final scripts/verify fast remains queued on compute01 with CARGO_BUILD_JOBS=4 and the short state-root override. Log and exit receipt: /home/claude/.cache/codex-scratch/j65-mesh-relay-220/verify-fast-final.log and .exit. No final gate result claimed yet.

J65 validation on current head `051fdd2df` is progressing: the relay and mesh feature checks, clippy, and relay fixture build finished with `FEATURE_CHECKS_FINAL_EXIT=0` on dev-02. The refreshed CUA-sudo namespace proof also passed under a claude-owned build-slot reservation. Its direct and relayed 32 MiB streams, 384 MiB relay-failover stream, and 16 MiB rebound stream all matched SHA-256; the old mapping retired, the relay-failover maximum gap was 3,099.1 ms, recovery after restart was 4,910.8 ms, `still_loaded` was empty, host link/netns/module/sysctl diffs were empty, and `unchanged: true`. Full receipt: `/home/claude/.cache/codex-scratch/j65-mesh-relay-220/root-proof-final.log` on dev-02. I removed the completed dev-02 J65 target after copying the fixture. The final `scripts/verify fast` remains queued on compute01 with `CARGO_BUILD_JOBS=4` and the short state-root override. Log and exit receipt: `/home/claude/.cache/codex-scratch/j65-mesh-relay-220/verify-fast-final.log` and `.exit`. No final gate result claimed yet.
Author
Owner

Gate follow-up on 051fdd2df (base 65d986191): the first scripts/verify fast exited 1 because service_states_are_truthful_in_text_and_json timed out querying systemd (agent and host reported service manager query timed out). The same test passed 1/1 on an unmodified worktree at current origin/dev 65d986191. The failed gate's other rows passed, including report-only static builds: x86_64 21,655,056 bytes (+211,232); aarch64 19,794,344 bytes (+165,104).

I am rerunning the full gate on 051fdd2df with CARGO_BUILD_JOBS=4 and INFRATERAL_AGENT_TEST_STATE_ROOT=/tmp/j65-verify-state. The rerun log and receipt are /home/claude/.cache/codex-scratch/j65-mesh-relay-220/verify-fast-rerun.log and .exit; the first attempt is preserved as verify-fast-final.log / .exit.

Gate follow-up on `051fdd2df` (base `65d986191`): the first `scripts/verify fast` exited 1 because `service_states_are_truthful_in_text_and_json` timed out querying systemd (`agent` and `host` reported `service manager query timed out`). The same test passed 1/1 on an unmodified worktree at current `origin/dev` `65d986191`. The failed gate's other rows passed, including report-only static builds: x86_64 21,655,056 bytes (+211,232); aarch64 19,794,344 bytes (+165,104). I am rerunning the full gate on `051fdd2df` with `CARGO_BUILD_JOBS=4` and `INFRATERAL_AGENT_TEST_STATE_ROOT=/tmp/j65-verify-state`. The rerun log and receipt are `/home/claude/.cache/codex-scratch/j65-mesh-relay-220/verify-fast-rerun.log` and `.exit`; the first attempt is preserved as `verify-fast-final.log` / `.exit`.
Author
Owner

J65 round-2 fixes are complete on mesh-relay-220 at 051fdd2df5395ec6c1c52ab73f74bcdae90041cb, rebased on current origin/dev 65d9861914e8cd4a734d533d64ed1f4ea5c34119 and pushed with lease. The three findings remain separate commits:

  • P1 dd5b9d9a432c72967a7bff3cc71a1c136cccb5d: reserve up to 256 global and eight canonical source-IP handshake slots before spawning; refuse excess Incoming connections. The five-second TLS and control-hello deadlines bound each admitted handshake. relay_bounds_stalled_handshakes_from_one_source_ip failed before the fix (left: 9, right: 8).
  • P2 1d8f0bb20a57a30aa4ad5b20820ec681852e0965: charge forwarded buffers to a 4 MiB source-host share as well as the 64 MiB global and 256 KiB per-pair budgets. The 256-backpressured-pair test showed an unrelated sender could not reserve before the host quota. DATAGRAMs drop under pressure.
  • P3 051fdd2df5395ec6c1c52ab73f74bcdae90041cb: bind each probe transaction to its first observed source in a bounded 64-entry per-session window; same-source retries work, source changes are refused. The captured-probe replay regression returned a reply to the spoofed source before the fix.

contracts/mesh-relay.md records the limits and probe binding. After rebasing, dev-02 feature checks passed: relay 17 unit + 5 QUIC loopback tests, mesh agent 38 passed / 13 ignored, mesh relay 47 passed / 13 ignored, clippy clean, fixture built (FEATURE_CHECKS_FINAL_EXIT=0).

Handshake flood measurement

Measured on infraternal-perf-01-backup01 with perf-run --root; 1,024 attempted Initials from 128 source IPs, eight per source, while replies were blackholed and the control hello withheld. The measured relay code is byte-identical to the final head for the relay paths.

Metric Before cap After cap
Idle RSS/HWM 8,256 KiB 8,340 KiB
Flood peak RSS/HWM 28,064 KiB 18,556 KiB
Peak over idle 19,808 KiB 10,216 KiB
Flood elapsed 2,003 ms 2,004 ms

Both runs exited 0 with zero steal jiffies.

Root namespace proof

The refreshed CUA-sudo proof passed on dev-02 under a claude-owned build-slot reservation. Direct and relayed 32 MiB streams, the 384 MiB relay-failover stream, and the 16 MiB rebound stream matched SHA-256; the old mapping retired. Maximum failover gap was 3,099.1 ms, relay restart recovery was 4,910.8 ms. still_loaded: [], link/netns/module/sysctl diffs were empty, and unchanged: true. Receipt: /home/claude/.cache/codex-scratch/j65-mesh-relay-220/root-proof-final.log.

Final scripts/verify fast

The first run hit a systemd query timeout in service_states_are_truthful_in_text_and_json; that test passed 1/1 on unmodified current origin/dev 65d986191. The complete rerun on this head exited 0. Static builds are report-only under the amended D243 policy; deltas are +211,232 bytes x86_64 and +165,104 bytes aarch64.

Verbatim gate summary:

PT_INTERP: absent; size: 21655056 bytes; baseline 21443824; delta +211232 bytes (report only, D243 amended 2026-11-01)
PT_INTERP: absent; size: 19794344 bytes; baseline 19629240; delta +165104 bytes (report only, D243 amended 2026-11-01)

+---------------------------+--------------+
| Rust gate                 | Result       |
+---------------------------+--------------+
| fmt                       | PASS         |
| VT native fmt             | PASS         |
| CLI plugin compile-out    | PASS         |
| clippy                    | PASS         |
| SSH runtime test helper   | PASS         |
| real-binary fixture paths | PASS         |
| test                      | PASS         |
| SSH inode reuse fixture   | PASS         |
| shim process-per-user     | PASS         |
| cargo-deny                | PASS         |
| gate architecture         | PASS         |
| static builds             | PASS         |
+---------------------------+--------------+

stream differential: 169 streams, 19929 schedules, zero divergences

39 pass
0 fail
59970 expect() calls
Ran 39 tests across 10 files. [6.24s]

Gate exit receipt: 0. The completed J65 target directories were removed from dev-02 and compute01. Issue remains open.

J65 round-2 fixes are complete on `mesh-relay-220` at `051fdd2df5395ec6c1c52ab73f74bcdae90041cb`, rebased on current `origin/dev` `65d9861914e8cd4a734d533d64ed1f4ea5c34119` and pushed with lease. The three findings remain separate commits: - **P1 `dd5b9d9a432c72967a7bff3cc71a1c136cccb5d`**: reserve up to 256 global and eight canonical source-IP handshake slots before spawning; refuse excess Incoming connections. The five-second TLS and control-hello deadlines bound each admitted handshake. `relay_bounds_stalled_handshakes_from_one_source_ip` failed before the fix (`left: 9, right: 8`). - **P2 `1d8f0bb20a57a30aa4ad5b20820ec681852e0965`**: charge forwarded buffers to a 4 MiB source-host share as well as the 64 MiB global and 256 KiB per-pair budgets. The 256-backpressured-pair test showed an unrelated sender could not reserve before the host quota. DATAGRAMs drop under pressure. - **P3 `051fdd2df5395ec6c1c52ab73f74bcdae90041cb`**: bind each probe transaction to its first observed source in a bounded 64-entry per-session window; same-source retries work, source changes are refused. The captured-probe replay regression returned a reply to the spoofed source before the fix. `contracts/mesh-relay.md` records the limits and probe binding. After rebasing, dev-02 feature checks passed: relay 17 unit + 5 QUIC loopback tests, mesh agent 38 passed / 13 ignored, mesh relay 47 passed / 13 ignored, clippy clean, fixture built (`FEATURE_CHECKS_FINAL_EXIT=0`). ### Handshake flood measurement Measured on `infraternal-perf-01-backup01` with `perf-run --root`; 1,024 attempted Initials from 128 source IPs, eight per source, while replies were blackholed and the control hello withheld. The measured relay code is byte-identical to the final head for the relay paths. | Metric | Before cap | After cap | | --- | ---: | ---: | | Idle RSS/HWM | 8,256 KiB | 8,340 KiB | | Flood peak RSS/HWM | 28,064 KiB | 18,556 KiB | | Peak over idle | 19,808 KiB | 10,216 KiB | | Flood elapsed | 2,003 ms | 2,004 ms | Both runs exited 0 with zero steal jiffies. ### Root namespace proof The refreshed CUA-sudo proof passed on dev-02 under a claude-owned build-slot reservation. Direct and relayed 32 MiB streams, the 384 MiB relay-failover stream, and the 16 MiB rebound stream matched SHA-256; the old mapping retired. Maximum failover gap was 3,099.1 ms, relay restart recovery was 4,910.8 ms. `still_loaded: []`, link/netns/module/sysctl diffs were empty, and `unchanged: true`. Receipt: `/home/claude/.cache/codex-scratch/j65-mesh-relay-220/root-proof-final.log`. ### Final `scripts/verify fast` The first run hit a systemd query timeout in `service_states_are_truthful_in_text_and_json`; that test passed 1/1 on unmodified current `origin/dev` `65d986191`. The complete rerun on this head exited 0. Static builds are report-only under the amended D243 policy; deltas are +211,232 bytes x86_64 and +165,104 bytes aarch64. Verbatim gate summary: ```text PT_INTERP: absent; size: 21655056 bytes; baseline 21443824; delta +211232 bytes (report only, D243 amended 2026-11-01) PT_INTERP: absent; size: 19794344 bytes; baseline 19629240; delta +165104 bytes (report only, D243 amended 2026-11-01) +---------------------------+--------------+ | Rust gate | Result | +---------------------------+--------------+ | fmt | PASS | | VT native fmt | PASS | | CLI plugin compile-out | PASS | | clippy | PASS | | SSH runtime test helper | PASS | | real-binary fixture paths | PASS | | test | PASS | | SSH inode reuse fixture | PASS | | shim process-per-user | PASS | | cargo-deny | PASS | | gate architecture | PASS | | static builds | PASS | +---------------------------+--------------+ stream differential: 169 streams, 19929 schedules, zero divergences 39 pass 0 fail 59970 expect() calls Ran 39 tests across 10 files. [6.24s] ``` Gate exit receipt: `0`. The completed J65 target directories were removed from dev-02 and compute01. Issue remains open.
Author
Owner

J76 round 3 starting on branch mesh-relay-220 at 051fdd2df5, against current origin/dev 0067e12554. Plan (estimated 45 minutes): verify the three round-2 fixes and tests; trace the relay-restart recovery regression and unchanged failover gap against the proof history; check ciphertext-only forwarding, amplification limits, per-host bounds, failover and rebound byte equality; post numbered findings or APPROVE. Review-only; I will run a test only if needed to confirm a concrete finding.

J76 round 3 starting on branch mesh-relay-220 at 051fdd2df5395ec6c1c52ab73f74bcdae90041cb, against current origin/dev 0067e1255407f0f3ed0f1d6c5e5caf51667e00a0. Plan (estimated 45 minutes): verify the three round-2 fixes and tests; trace the relay-restart recovery regression and unchanged failover gap against the proof history; check ciphertext-only forwarding, amplification limits, per-host bounds, failover and rebound byte equality; post numbered findings or APPROVE. Review-only; I will run a test only if needed to confirm a concrete finding.
Author
Owner

Verdict: HOLD for the P2 restart-recovery defect and the P3 probe-replay gap. No P0 or P1 found.

Target: mesh-relay-220 at 051fdd2df5395ec6c1c52ab73f74bcdae90041cb, based on origin/dev 0067e1255407f0f3ed0f1d6c5e5caf51667e00a (merge base 65d9861914e8cd4a734d533d64ed1f4ea5c34119).

  1. P2 — A relay restart can leave every peer waiting almost five seconds to reconnect. crates/plugins/mesh/src/relay_path.rs:50-52,515-531 caps retry backoff at five seconds and sleeps the current backoff after each failed attempt; it resets only after a successful connection. The namespace proof restarted relay 1 after it had already been unavailable during failover. Its recovery result rose from 551.4 ms in J49 to 4,910.8 ms in J65, consistent with the remaining maximum retry sleep. The newly added relay admission cap and timeout do not explain this proof: it has only nodes A and B using relays, behind distinct public NAT addresses, so neither reaches the eight-per-IP cap (scripts/mesh-relay-proof.py:40-50,230-300); the client sends IFR1 immediately after opening its control stream (crates/plugins/relay/src/client.rs:31-48). The three-second relay-kill gap remains about 3.1 s, consistent with the unchanged QUIC idle timeout. Under this review's restart-recovery requirement, the five-second retry ceiling is a defect: a relay restart should not leave all pairs unavailable for about five seconds.

  2. P3 — Probe source binding expires when its transaction is evicted. crates/plugins/relay/src/probe.rs:35-54 drops the oldest source binding when the 64-entry window fills. The test at :265-280 then explicitly rebinds that evicted transaction to a different source. A captured request can therefore again produce a one-for-one 84-byte reflection after 64 newer transactions. Replies remain the same size as requests (:237), so this is not amplification, but the transaction is not bound to its first source for its lifetime as requested.

  3. P3 — The pre-auth timeout has no regression test. The two five-second timeout paths are present at crates/plugins/relay/src/relay.rs:716-730, but crates/plugins/relay/tests/relay.rs:153-173 closes its eight stalled clients itself before the deadline; the limiter unit test at relay.rs:959-981 manually drops permits. No test exercises TLS or hello timeout expiry and permit release. The concurrent/global and per-IP caps are tested; the separate 4 MiB per-host share and reconnect reuse are covered by relay.rs:889-950; probe source binding is covered while its transaction remains in the window.

Other requested checks: the relay preserves the existing DATAGRAM bytes while checking only the link ID (crates/plugins/relay/src/relay.rs:514-528; crates/plugins/mesh/src/relay_path.rs:362-405). Probe requests require the authenticated connection's HMAC key and replies are fixed-size. The recorded J65 namespace proof has byte-matching direct and relayed 32 MiB streams, the 384 MiB relay-failover stream, and the 16 MiB rebound stream; still_loaded was empty and host link/netns/module/sysctl state was unchanged. The per-host forwarding cap is 4 MiB and its 256-pair regression passes in the recorded relay checks.

Review-only: no code changed and no tests or scripts/verify fast were run; the existing J65 feature-check, namespace-proof and gate results are recorded above on #220. Review head: 051fdd2df5395ec6c1c52ab73f74bcdae90041cb.

Verdict: HOLD for the P2 restart-recovery defect and the P3 probe-replay gap. No P0 or P1 found. Target: `mesh-relay-220` at `051fdd2df5395ec6c1c52ab73f74bcdae90041cb`, based on `origin/dev` `0067e1255407f0f3ed0f1d6c5e5caf51667e00a` (merge base `65d9861914e8cd4a734d533d64ed1f4ea5c34119`). 1. **P2 — A relay restart can leave every peer waiting almost five seconds to reconnect.** `crates/plugins/mesh/src/relay_path.rs:50-52,515-531` caps retry backoff at five seconds and sleeps the current backoff after each failed attempt; it resets only after a successful connection. The namespace proof restarted relay 1 after it had already been unavailable during failover. Its recovery result rose from 551.4 ms in J49 to 4,910.8 ms in J65, consistent with the remaining maximum retry sleep. The newly added relay admission cap and timeout do not explain this proof: it has only nodes A and B using relays, behind distinct public NAT addresses, so neither reaches the eight-per-IP cap (`scripts/mesh-relay-proof.py:40-50,230-300`); the client sends `IFR1` immediately after opening its control stream (`crates/plugins/relay/src/client.rs:31-48`). The three-second relay-kill gap remains about 3.1 s, consistent with the unchanged QUIC idle timeout. Under this review's restart-recovery requirement, the five-second retry ceiling is a defect: a relay restart should not leave all pairs unavailable for about five seconds. 2. **P3 — Probe source binding expires when its transaction is evicted.** `crates/plugins/relay/src/probe.rs:35-54` drops the oldest source binding when the 64-entry window fills. The test at `:265-280` then explicitly rebinds that evicted transaction to a different source. A captured request can therefore again produce a one-for-one 84-byte reflection after 64 newer transactions. Replies remain the same size as requests (`:237`), so this is not amplification, but the transaction is not bound to its first source for its lifetime as requested. 3. **P3 — The pre-auth timeout has no regression test.** The two five-second timeout paths are present at `crates/plugins/relay/src/relay.rs:716-730`, but `crates/plugins/relay/tests/relay.rs:153-173` closes its eight stalled clients itself before the deadline; the limiter unit test at `relay.rs:959-981` manually drops permits. No test exercises TLS or hello timeout expiry and permit release. The concurrent/global and per-IP caps are tested; the separate 4 MiB per-host share and reconnect reuse are covered by `relay.rs:889-950`; probe source binding is covered while its transaction remains in the window. Other requested checks: the relay preserves the existing DATAGRAM bytes while checking only the link ID (`crates/plugins/relay/src/relay.rs:514-528`; `crates/plugins/mesh/src/relay_path.rs:362-405`). Probe requests require the authenticated connection's HMAC key and replies are fixed-size. The recorded J65 namespace proof has byte-matching direct and relayed 32 MiB streams, the 384 MiB relay-failover stream, and the 16 MiB rebound stream; `still_loaded` was empty and host link/netns/module/sysctl state was unchanged. The per-host forwarding cap is 4 MiB and its 256-pair regression passes in the recorded relay checks. Review-only: no code changed and no tests or `scripts/verify fast` were run; the existing J65 feature-check, namespace-proof and gate results are recorded above on #220. Review head: `051fdd2df5395ec6c1c52ab73f74bcdae90041cb`.
Author
Owner

J76 fixes starting on mesh-relay-220 at 051fdd2df5; rebasing onto current origin/dev 0067e12554. Plan (estimated 90 minutes): add and watch each requested regression fail; implement relay retry backoff, probe time-window binding, and stalled-handshake timeout/permit-release coverage as three separate commits; rerun the namespace proof; run one complete scripts/verify fast gate; push the final branch head. Keep changes limited to those code paths, tests, and the existing proof.

J76 fixes starting on mesh-relay-220 at 051fdd2df5395ec6c1c52ab73f74bcdae90041cb; rebasing onto current origin/dev 0067e1255407f0f3ed0f1d6c5e5caf51667e00a0. Plan (estimated 90 minutes): add and watch each requested regression fail; implement relay retry backoff, probe time-window binding, and stalled-handshake timeout/permit-release coverage as three separate commits; rerun the namespace proof; run one complete scripts/verify fast gate; push the final branch head. Keep changes limited to those code paths, tests, and the existing proof.
Author
Owner

J76 progress at 45d0f90c0: the probe-window finding is committed and pushed separately. The restart-recovery RED guard is in scripts/mesh-relay-proof.py. The CUA-launched build-slot command is queued on dev-02 (disk now above the 25 GiB floor); all compute01 slots are occupied. Earlier wrappers using the wrong identity were stopped. Waiting for the before-fix proof result before changing retry behavior.

J76 progress at 45d0f90c0: the probe-window finding is committed and pushed separately. The restart-recovery RED guard is in scripts/mesh-relay-proof.py. The CUA-launched build-slot command is queued on dev-02 (disk now above the 25 GiB floor); all compute01 slots are occupied. Earlier wrappers using the wrong identity were stopped. Waiting for the before-fix proof result before changing retry behavior.
Author
Owner

J76 P2 RED evidence on dev-02/aarch64: the namespace proof failed the new 1.5 s guard at 4,964.9 ms, matching the reported five-second backoff. A rootless run left bridge/llc/stp/veth loaded; I verified they had no active references and removed them through CUA sudo, restoring the host. The fix now retries immediately after a healthy relay disconnect, caps retries at 1 s for 30 s, then permits longer delays after continued failures. The updated relay fixture built successfully (BUILD_EXIT=0); the post-fix root proof and timeout test remain queued behind build slots.

J76 P2 RED evidence on dev-02/aarch64: the namespace proof failed the new 1.5 s guard at 4,964.9 ms, matching the reported five-second backoff. A rootless run left bridge/llc/stp/veth loaded; I verified they had no active references and removed them through CUA sudo, restoring the host. The fix now retries immediately after a healthy relay disconnect, caps retries at 1 s for 30 s, then permits longer delays after continued failures. The updated relay fixture built successfully (BUILD_EXIT=0); the post-fix root proof and timeout test remain queued behind build slots.
Author
Owner

J76 P2 retry repair is pushed on mesh-relay-220 at 25a560599, rebased onto origin/dev ad2b2c046.

The namespace regression failed at 4,964.9 ms on the old backoff. A one-second recent-health sleep cap alone still measured 2,292.6 ms. Temporary client timing showed connect attempts timing out at 3.0 s on QUIC's idle timeout before the 1 s retry sleep. The fix keeps the immediate first retry, then uses a 750 ms connect deadline and 250 ms retry cap for 60 seconds after a healthy connection ends; initial and sustained-failure behavior retains the 5 s deadline and exponential backoff.

The final CUA-sudo root namespace proof passed on dev-02 with relay 1 down 54,023.5 ms: restart-to-ready 3.6 ms, ready-to-links 105.4 ms, recovery 588.4 ms. Direct, relayed, failover and rebound streams matched SHA-256. still_loaded was empty; link/netns/module/sysctl diffs were empty and unchanged: true. Receipt: /tmp/j76-mesh-relay-220/proof-slot-60/proof-60.log on dev-02.

Feature-on relay and mesh regression suites passed on the rebased tree. Final scripts/verify fast is running next.

J76 P2 retry repair is pushed on `mesh-relay-220` at `25a560599`, rebased onto `origin/dev` `ad2b2c046`. The namespace regression failed at 4,964.9 ms on the old backoff. A one-second recent-health sleep cap alone still measured 2,292.6 ms. Temporary client timing showed connect attempts timing out at 3.0 s on QUIC's idle timeout before the 1 s retry sleep. The fix keeps the immediate first retry, then uses a 750 ms connect deadline and 250 ms retry cap for 60 seconds after a healthy connection ends; initial and sustained-failure behavior retains the 5 s deadline and exponential backoff. The final CUA-sudo root namespace proof passed on dev-02 with relay 1 down 54,023.5 ms: restart-to-ready 3.6 ms, ready-to-links 105.4 ms, recovery 588.4 ms. Direct, relayed, failover and rebound streams matched SHA-256. `still_loaded` was empty; link/netns/module/sysctl diffs were empty and `unchanged: true`. Receipt: `/tmp/j76-mesh-relay-220/proof-slot-60/proof-60.log` on dev-02. Feature-on relay and mesh regression suites passed on the rebased tree. Final `scripts/verify fast` is running next.
Author
Owner

J76 final review fixes are pushed on mesh-relay-220.

Base: origin/dev d1ec515e44dabccb640801c4a1ef159b32e004fe
Head: 9ae031bc8596be8c9e8d3b4d16b0142ebcb2de5d

Findings, one commit each:

  • P2 retry recovery (9ae031bc8): recently healthy relay retries use a 750 ms connect deadline and 250 ms backoff cap for 60 seconds; initial and sustained failure backoff remains longer. Root namespace proof recovered in 588.4 ms after a 54,023.5 ms relay outage; restart-to-ready was 3.6 ms, ready-to-links 105.4 ms, ping loss 0%, and host state was unchanged.
  • P3 probe replay (e1630d06b): transaction issue time is checked against a 30 second window and an evicted transaction advances a retired-time floor, preventing an old ID from rebinding after 64 newer IDs.
  • P3 stalled handshake (89c42de08): regression coverage confirms a stalled hello is closed at the deadline and releases its permit.

The interrupted earlier run’s SIGKILLed restart test passed on unmodified current dev: 1 passed, 0 failed, in 10.72 s.

scripts/verify fast on compute01, exit 0. Verbatim Rust gate summary:

+---------------------------+--------------+
| Rust gate                 | Result       |
+---------------------------+--------------+
| fmt                       | PASS         |
| VT native fmt             | PASS         |
| CLI plugin compile-out    | PASS         |
| clippy                    | PASS         |
| SSH runtime test helper   | PASS         |
| real-binary fixture paths | PASS         |
| test                      | PASS         |
| SSH inode reuse fixture   | PASS         |
| shim process-per-user     | PASS         |
| cargo-deny                | PASS         |
| gate architecture         | PASS         |
| static builds             | PASS         |
+---------------------------+--------------+

Static size output (report-only):

PT_INTERP: absent; size: 21737552 bytes; baseline 21443824; delta +293728 bytes (report only, D243 amended 2026-11-01)
PT_INTERP: absent; size: 19857576 bytes; baseline 19629240; delta +228336 bytes (report only, D243 amended 2026-11-01)

Namespace proof receipt: /tmp/j76-mesh-relay-220/proof-slot-60/proof-60.log on dev-02. Full gate log: /home/claude/.cache/codex-scratch/j76-mesh-relay-220/verify-fast-final-2.log on compute01.

J76 final review fixes are pushed on `mesh-relay-220`. Base: `origin/dev` `d1ec515e44dabccb640801c4a1ef159b32e004fe` Head: `9ae031bc8596be8c9e8d3b4d16b0142ebcb2de5d` Findings, one commit each: - P2 retry recovery (`9ae031bc8`): recently healthy relay retries use a 750 ms connect deadline and 250 ms backoff cap for 60 seconds; initial and sustained failure backoff remains longer. Root namespace proof recovered in 588.4 ms after a 54,023.5 ms relay outage; restart-to-ready was 3.6 ms, ready-to-links 105.4 ms, ping loss 0%, and host state was unchanged. - P3 probe replay (`e1630d06b`): transaction issue time is checked against a 30 second window and an evicted transaction advances a retired-time floor, preventing an old ID from rebinding after 64 newer IDs. - P3 stalled handshake (`89c42de08`): regression coverage confirms a stalled hello is closed at the deadline and releases its permit. The interrupted earlier run’s SIGKILLed restart test passed on unmodified current dev: 1 passed, 0 failed, in 10.72 s. `scripts/verify fast` on compute01, exit 0. Verbatim Rust gate summary: ``` +---------------------------+--------------+ | Rust gate | Result | +---------------------------+--------------+ | fmt | PASS | | VT native fmt | PASS | | CLI plugin compile-out | PASS | | clippy | PASS | | SSH runtime test helper | PASS | | real-binary fixture paths | PASS | | test | PASS | | SSH inode reuse fixture | PASS | | shim process-per-user | PASS | | cargo-deny | PASS | | gate architecture | PASS | | static builds | PASS | +---------------------------+--------------+ ``` Static size output (report-only): ``` PT_INTERP: absent; size: 21737552 bytes; baseline 21443824; delta +293728 bytes (report only, D243 amended 2026-11-01) PT_INTERP: absent; size: 19857576 bytes; baseline 19629240; delta +228336 bytes (report only, D243 amended 2026-11-01) ``` Namespace proof receipt: `/tmp/j76-mesh-relay-220/proof-slot-60/proof-60.log` on dev-02. Full gate log: `/home/claude/.cache/codex-scratch/j76-mesh-relay-220/verify-fast-final-2.log` on compute01.
Sign in to join this conversation.
No labels
launch-qa
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
kayg/infraternal#220
No description provided.