Skip to content

Latest commit

 

History

History
963 lines (896 loc) · 60.9 KB

File metadata and controls

963 lines (896 loc) · 60.9 KB

wanbond — manual real-link verification checklist

The automated -tags e2e suite runs in netns/netem emulation. This checklist is the manual counterpart, run on the real deployment (Starlink + 5G edge box and a concentrator VPS with a public IP). Each phase appends its own section; run the phase's section after that phase lands. Record date, build (wanbond version), and observed numbers next to each item.

Prerequisites (all phases):

  • Edge box has both uplinks up; router pins source IP A → Starlink, source IP B → 5G (path selection is external to wanbond).
  • Concentrator reachable on its public IP; UDP not blocked end to end.
  • wanbond running both ends from a 0600 config; /metrics reachable on localhost each end.
  • With Amnezia obfuscation enabled, run (cd third_party/amneziawg-go && go test -race ./device/awg -run '^TestJunkCreatorConcurrentUse$' -count=5); simultaneous peer handshakes report no shared ChaCha8 access.

P0 — spike / baseline

  • Tunnel comes up edge ↔ concentrator (WG handshake completes).
  • ping and a TCP transfer pass through the tunnel.
  • Record single-path baseline throughput per uplink (iperf3).
  • Optional: measure per-link bandwidth and RTT for pacing tuning (see install.md §3a if you plan to enable pacing).

The manual items above are automated end to end by the just p0-baseline pre-pilot procedure against the two standing worker machines — see §P0 — automated real-link baseline below. Run that command to capture the baseline report, then read/interpret the numbers by hand; the baseline is INFORMATIONAL (report-only), not a pass/fail gate.

P1 — transparent failover

  • Start a long-lived TCP flow (SSH session or iperf3) over the tunnel.
  • Physically drop the active WAN (unplug / disable the Starlink uplink).
  • Flow survives with NO reset; throughput restored within P1RecoverySeconds.
  • Restore the WAN; no thrash. Repeat for the other uplink.
  • Change the edge public IP on one path (carrier re-address); flow survives.

P2 — aggregation + data-thrift

  • Under saturating load, bonded throughput ≥ P2BondedMinFraction × (sum of per-path throughputs), read from /metrics.
  • While Starlink is healthy and load fits, 5G bytes < P2MeteredMaxByteFraction of total (/metrics).

P3 — fixed-ratio FEC

  • Induce loss on a path; at the configured parity ratio, ≥ P3MinRecoveredFraction of lost DATA frames recovered without retransmit.
  • FEC overhead ≤ P3MaxOverheadFactor × parity ratio (/metrics).

P4 — adaptive FEC

  • Run go test ./internal/bind ./internal/reseq ./internal/telemetry -run 'TestAdaptiveControllerUsesAuthenticatedActiveCarrierDataLoss|TestAdaptiveControllerIgnoresSingleCarrierFeedbackWhileWeighted|TestDataLossFeedback|TestFeedbackOnly|TestMixedVersionFeedbackCadencePreservesBaseRecoveryWindow|TestLossObserverReportsExactFinalizedSequenceRange|TestProbePayload' -count=1 and repeat with -race. This pins final-gap raising, inferred parity-recovered loss, exact outcome conservation, carrier-transition non-influence for final and recovered prior-epoch outcomes, clean dwell/shedding, stale/replay/identity rejection, per-peer isolation, bidirectional legacy recovery negotiation under ordered and reordered delivery, feedback-only evidence preservation and exact priority accounting, and the weighted multi-carrier boundary.
  • Run sudo -E go test -tags e2e -run '^TestP4AdaptiveFEC$' -v ./test/e2e on the slowest supported worker (including a 1-vCPU aarch64 host); each 60-second phase must collect at least 2,000 DATA frames. The iperf client has a 90-second wall-clock limit (60-second transmit interval plus 30-second control/result grace). Fixture setup waits for the holder's network-namespace identity to differ from the test process before configuring loopback; a readiness failure terminates and reaps that holder while reporting its PID, argv, and process state. A later phase timeout reports both daemon logs instead of leaving the phase active indefinitely.
  • Under steady P4SteadyLossRate path loss, adaptive total overhead ≤ the fixed-FEC baseline for equal masking.
  • Post-recovery residual loss ≤ P4ResidualLossMax (/metrics).
  • On an active-backup real link, perform two independent loaded 30-second cycles from a clean M=0 baseline. For each cycle record wanbond_path_loss_ratio, wanbond_fec_eligible_path_loss, wanbond_fec_adaptive_parity, wanbond_fec_recovered_packets_total, and wanbond_fec_residual_loss_ratio: bounded active-carrier DATA loss must make eligible loss and M non-zero; a subsequently missing DATA frame must increment recovery without TCP-equivalent residual loss; after clearing the impairment, M must return to zero only after the configured controller dwell. Preserve both metric captures with the commit/config hashes.

P5 — DPI resistance

  • From a hostile-ish network (e.g. a hotel/guest Wi-Fi), the tunnel connects.
  • Capture the flow; nDPI / Suricata do not classify it as WireGuard or any identified VPN.

P0 — automated real-link baseline (realhosts tier)

The single, repeatable pre-pilot procedure that replaces the manual §P0 steps above. It is a thin orchestration layer over the existing realhosts tier (which drives the two standing worker machines over SSH: the amd64 edge behind symmetric NAT ↔ the aarch64 concentrator on its public IP) — it does NOT re-implement any test logic. One command provisions both ends, natively builds wanbond on each, brings the tunnel up over the real internet path, runs the aggregation + loaded-RTT + link/hub-failover smoke, and TEES a timestamped baseline report.

REPORT-ONLY / NON-BLOCKING (Q19): the orchestrated tests assert liveness only (handshake completed, both paths reached up, every iperf3 sample returned a positive rate, failover recovered) — no Mbit/s or ms threshold gates the run. The emitted numbers are informational input to the operator's pilot-gate decision, which stays a human judgement call, not an automated gate.

Run the baseline

  • From the dev shell (nix develop) at the repo root, run:

    ```
    WANBOND_SSH_KEY=/run/agenix/llm-ssh-key just p0-baseline
    ```
    
    `WANBOND_SSH_KEY` defaults to `/run/agenix/llm-ssh-key`, so on a host where
    that key is already in place `just p0-baseline` alone suffices. Host
    addresses/public IP default to the two standing workers and can be overridden
    with `WANBOND_EDGE_HOST` / `WANBOND_CONC_HOST` / `WANBOND_CONC_PUBLIP`. No
    root is required. The command is NEVER part of `just test` or CI.
    
  • The command orchestrates these EXISTING tests (go test -tags realhosts -run '^(TestRealP0Smoke|TestRealAggregationBufferbloat|TestRealMidTransferWANKill)$' -v) and tees the full -v output to test/realhosts/reports/p0-baseline-<UTC-timestamp>.log (gitignored). A non-zero exit means the run itself could not complete (a host was unreachable or the tunnel never came up) — NOT that a performance number missed a target.

What the baseline report contains

  • TestRealP0Smoke — single-uplink bring-up: WG handshake OK, ping avg RTT (ms), and three iperf3 measurements (single-flow TCP Mbit/s + retransmits, 8×-parallel TCP Mbit/s + retransmits, UDP goodput/loss/jitter). See the === P0 SMOKE RESULTS === block.
  • TestRealAggregationBufferbloat — per-path and bonded throughput and their aggregation ratio, plus the idle-vs-loaded RTT (bufferbloat) delta measured with a ping running inside a saturating transfer.
  • TestRealMidTransferWANKill — mid-transfer LINK-failover and HUB-failover (T57) recovery: the observed gap/switch timings before the flow resumes over the surviving link / standby concentrator.

What stays manual

  • Reading and interpreting the numbers. The command emits measurements; a human decides whether the aggregation ratio, loaded-RTT delta, and failover gaps look acceptable for the intended pilot.
  • The pilot-gate decision itself is a NON-BLOCKING human call. The baseline informs it; it does not automate or gate it. Record the report path, date, and the go/no-go decision alongside the numbers.
  • Exit criterion (Q19): the capped-fixture functional impairment/counter check (netns, TestFixtureImpairment, W2; no throughput or loaded-RTT claim) PLUS this report-only real-link baseline (just p0-baseline, W4) are SUFFICIENT to proceed to a SUPERVISED pilot. The longer soak runs DURING the pilot, NOT as a pre-gate. Full statement: runbook.md §7 Pilot exit criterion.

P1 — scripted real-setup run (Starlink + 5G edge, VPS concentrator)

Scripted counterpart of the P1 section above for the real deployment. Install per docs/install.md first (binary at /usr/local/bin/wanbond, 0600 configs, systemd units enabled, concentrator tunnel-interface firewall ACCEPT in place). Inner addresses below assume concentrator 10.77.0.1, edge 10.77.0.2; adjust to your allowed_ips. Record date, wanbond version output, and observed numbers next to each item.

Setup

  • Concentrator: systemctl start wanbond-concentrator, then systemctl status wanbond-concentrator shows active and journalctl -u wanbond-concentrator -n 20 shows tunnel interface up.
  • Concentrator firewall ordering verified: iptables -S INPUT lists -i wanbond0 -j ACCEPT BEFORE any -j REJECT line (OCI default-REJECT caveat, docs/install.md §5) and a UDP ACCEPT for the listen port.
  • Edge: systemctl start wanbond-edge; status active; journal shows tunnel interface up with both paths.
  • Handshake: edge ping -c 3 10.77.0.1 succeeds.
  • TCP through the tunnel: concentrator iperf3 -s -B 10.77.0.1; edge iperf3 -c 10.77.0.1 -t 5 completes (guards the firewall caveat — if ping passes but iperf3 fails with "No route to host", the REJECT rule is ahead of the tunnel ACCEPT).
  • Both paths live: edge curl -s http://127.0.0.1:9090/metrics | grep wanbond_path shows starlink and 5g.

Failover: drop Starlink

  • Start the long-lived flow: edge iperf3 -c 10.77.0.1 -t 120 (or an interactive SSH session to 10.77.0.1) and, in a second terminal, ping -i 0.2 10.77.0.1.
  • Physically drop Starlink (unplug its ethernet/PoE — a real link drop, not ip link set down).
  • Flow survives with NO reset; ping gap and iperf3 stall ≤ P1RecoverySeconds (3 s). Record the observed gap.
  • Restore Starlink; wait ~30 s; journal shows the path recovering with no up/down thrash (no repeated failover lines).

Failover: drop 5G

  • Repeat the block above dropping the 5G uplink (pull the modem's power or antenna). Same acceptance: no reset, gap ≤ 3 s, clean recovery.

Carrier re-address

  • With the flow running, force a public-IP change on one path (5G: toggle airplane mode / mmcli -m 0 --simple-disconnect && --simple-connect; or power-cycle the Starlink router if it re-NATs). The edge's outbound source may also be changed at the router NAT.
  • Flow survives; concentrator journal shows the path's endpoint roaming to the new address; ping gap ≤ 3 s.

Hub failover: active concentrator goes fully unreachable (T57)

Distinct from the per-uplink drops above: here the concentrator is lost, so NO uplink can reach it and the edge must move to a STANDBY concentrator. Requires a SECOND concentrator VPS reachable from the edge, sharing the peer's SAME WireGuard static key (the standby presents the same peer identity). Configure the edge peer with an ORDERED list — endpoints = ["<hubA ip:port>", "<hubB ip:port>"] (index 0 = hubA active, hubB standby); IP:port only, no hostnames.

  • Bring the tunnel up; confirm traffic flows via hubA (ping -i 0.2 10.77.0.1 steady; hubA journal shows the handshake + the edge endpoint learned).
  • Make hubA fully unreachable from the edge — stop wanbond-concentrator on hubA, OR block its listen_port at hubA's firewall (a REAL hub outage, so every path's liveness to hubA goes DOWN together, not just one uplink).
  • Within the hub-failover budget (all-paths-DOWN detection ≈ DownAfter + the hubFailoverSettle 3 s dwell), the edge journal shows a hub failover: all paths to active concentrator down; switched endpoint line advancing to hubB, and hubB's journal shows a FRESH handshake (a new session — no hub-to-hub state handoff). Record the observed gap.
  • The flow re-establishes over hubB. (A long-lived TCP flow tied to the old session resets — a fresh session is deliberate; a NEW flow, or ping, resumes.)
  • Single-concentrator GUARD: with the edge configured with only ONE endpoint (legacy single endpoint, or a one-element endpoints), repeat the hub outage — the edge must take NO failover action (no hub failover journal line, no endpoint switch); behaviour is identical to pre-T57. Recovery happens only when hubA itself returns.

Startup with a not-yet-assignable path (tolerant bind)

  • Bring one uplink's interface DOWN (so its configured source_addr is not held by any interface), then systemctl restart wanbond-edge. The daemon comes up instead of crash-looping: journal shows the tunnel bound on the surviving uplink and the absent path recorded as deferred / Down; a NEW flow passes end to end over the survivor. Then bring the interface back UP WITHOUT restarting: the background reconcile (T55) re-binds and promotes the deferred path automatically within ~1 s (DefaultReconcileInterval), with no restart — and both paths then carry traffic.
  • With EVERY uplink's source_addr absent, systemctl restart wanbond-edge FAILS fast (journal shows a fatal "no configured path could bind" and the unit enters failed / restart-loops) — no transport means no tunnel.
  • A MALFORMED source_addr in the config still fails at config load with a validation error, distinct from the tolerated not-yet-assignable case.

Teardown / restart discipline

  • systemctl reload wanbond-edge (SIGHUP) with an unchanged config is a no-op: journal logs config reloaded, tunnel stays up, flow unaffected.
  • systemctl restart wanbond-edge recovers the tunnel within seconds; a NEW flow passes end to end afterwards.

P2 — scripted real-setup run (aggregation + data-thrift)

Scripted counterpart of the P2 summary above for the real deployment. Requires the P1 setup already validated (both uplinks up, both daemons running from 0600 configs, /metrics reachable on 127.0.0.1:9090 each end) AND the edge configured with the weighted-aggregation scheduler so bonding engages under load:

[scheduler]
policy = "weighted"
# per_path_capacity_fps sizes the aggregation gate to ~one uplink's WIRE frame
# rate (data + any FEC parity); tune it to the slower uplink's sustained wire
# frame rate (bytes/s ÷ on-wire frame bytes, install.md §3a).

Inner addresses assume concentrator 10.77.0.1, edge 10.77.0.2. THRU() below is curl -s http://127.0.0.1:9090/metrics | grep wanbond_path_throughput; TX(path) is ... | grep wanbond_path_tx_bytes_total | grep <path>. Record date, wanbond version, and observed numbers.

Baseline: per-uplink solo throughput

  • Record each uplink's SOLO saturated throughput: bring the tunnel up with only Starlink configured, run iperf3 -c 10.77.0.1 -t 20, and read the Starlink wanbond_path_throughput_bits_per_second from /metrics. Repeat with only 5G. Record T_starlink and T_5g (Mbit/s, from /metrics).

Aggregation under saturating load

  • Bring the tunnel up with BOTH uplinks. Start a saturating flow: concentrator iperf3 -s -B 10.77.0.1; edge iperf3 -c 10.77.0.1 -t 30.
  • Mid-flow, read BOTH paths' wanbond_path_throughput_bits_per_second from the edge /metrics and sum them: T_bonded. Confirm both paths are non-zero (aggregation engaged, not single-path fallback).
  • Cross-check the far end: the concentrator /metrics shows wanbond_path_rx_bytes_total climbing on BOTH paths.
  • Assert T_bonded ≥ P2BondedMinFraction × (T_starlink + T_5g) (0.85). Record the measured fraction.

Data-thrift: metered path stays idle while primary is healthy

  • With both uplinks healthy, run a SUB-capacity flow that fits Starlink alone (e.g. edge iperf3 -c 10.77.0.1 -t 30 -b <~40% of T_starlink>).
  • Sample wanbond_path_tx_bytes_total for both paths at the start and end of the flow; take the DELTA over the window.
  • Assert the 5G delta is < P2MeteredMaxByteFraction × (Starlink + 5G deltas) (0.01) — the metered uplink carried effectively no bytes. Record the fraction.
  • Confirm wanbond_path_up{path="starlink"} was 1 throughout (the thrift guarantee is conditioned on a healthy primary).

Aggregation teardown discipline

  • Stop the saturating flow; within a few seconds the edge journal / /metrics show egress collapsing back to Starlink only (5G tx flat again).
  • systemctl reload wanbond-edge after changing [metrics] listen: journal logs metrics endpoint rebound; the new address serves /metrics, the old one stops; the tunnel and any running flow are unaffected.

P5 — scripted real-setup run (DPI resistance)

Scripted counterpart of the P5 summary above for the real deployment. This is the manual, real-link mirror of the automated TestP5DPI (netns) check: it confirms that on a real access network the obfuscated wanbond flow is not classified as WireGuard or any identified VPN by nDPI or Suricata, and it exercises the UDP-block limitation (docs/install.md §8) as an understood failure mode — not a wanbond defect. Requires the P1 setup validated (both daemons up from 0600 configs) AND an [amnezia] obfuscation block set IDENTICALLY on both ends (obfuscation ON — plain WireGuard is trivially classified and is NOT what ships). Run the capture from a realistic hostile-ish network (hotel / guest / captive-portal Wi-Fi, or a lab uplink with a DPI appliance in path). Install ndpi (ndpiReader) and suricata on the capture host. Record date, wanbond version, the access-network description, and each tool's verdict.

Positive control FIRST (prove the detectors have teeth)

  • On the capture host, run the shipped positive-control capture through nDPI: ndpiReader -i test/e2e/testdata/plain-wireguard.pcap and confirm the Detected protocols block lists WireGuard (and category VPN). If it does NOT, the tool/parse is broken and every "not classified" result below is vacuous — fix the tooling before trusting the negative checks.
  • (Informational) Run the same capture through Suricata (suricata -r test/e2e/testdata/plain-wireguard.pcap -l ./sur-pos -k none) and note whether eve.json reports app_proto: wireguard. The stock Suricata config ships no WireGuard app-layer parser, so failed/unknown here is EXPECTED — nDPI carries the WireGuard-specific positive control; Suricata provides the app-layer/anomaly negative check.

Connect + capture the obfuscated wanbond flow

  • From the hostile-ish network, bring the tunnel up (edge systemctl start wanbond-edge); confirm handshake: edge ping -c 3 10.77.0.1 succeeds. If the network blocks UDP wholesale, the handshake will NOT complete — see the UDP-block step below; that is the documented limitation, not a bug.
  • Capture the outer WAN UDP while driving representative traffic (a bulk transfer + interactive traffic for ~30 s): on the edge uplink interface, tcpdump -i <wan-if> -n -p -U -w wanbond.pcap 'udp port 51820' (adjust the port to your wireguard.listen_port / concentrator endpoint). Confirm wanbond.pcap is non-empty.

nDPI — negative assertion (the requirement)

  • ndpiReader -v 2 -i wanbond.pcap; on the per-flow line for the wanbond flow the [Confidence: …] field is NOT a payload/content match to WireGuard/VPN — a [proto: …/Unknown] (or QUIC/DNS/etc.) is fine. Ignore a Confidence: Match by port "WireGuard/VPN" label if you captured on port 51820 — that is a port guess, not a payload classification (see docs/install.md §Limitations); to remove the ambiguity, deploy/capture on a non-registered UDP port so nDPI cannot port-guess. A WireGuard/VPN label with Confidence: DPI (a PAYLOAD match) is a requirement-6 DEFECT — file it; do not rationalise it away.

Suricata — negative assertion

  • suricata -r wanbond.pcap -l ./sur-neg -k none; inspect ./sur-neg/eve.json: no flow's app_proto and no alert.signature/alert.category names WireGuard or a VPN. Record the observed app_proto (expected: failed/unknown). A WireGuard/VPN app-proto or alert is a requirement-6 DEFECT.

UDP-block limitation (understood failure mode, not a defect)

  • On a network (or a test firewall rule) that blocks UDP wholesale, confirm the tunnel FAILS to connect (no handshake, ping 10.77.0.1 fails) and the edge journal shows only outbound handshake attempts with no response. Confirm this is the EXPECTED behaviour: wanbond has no TCP/TLS fallback transport (explicit non-goal, docs/install.md §8). Record that the flow does not silently downgrade to an unobfuscated or plaintext fallback (there is none — it simply does not connect).
  • Where a UDP-allowing network is available again, confirm the tunnel reconnects once UDP egress is restored (no manual intervention beyond the network change).

D65 — pacing field validation (bufferbloat control)

Defect D65 identifies bufferbloat (standing queue build-up under sustained load) on last-mile links (observed on Starlink, D65) that caps single-flow TCP throughput at ~3.67 Mbps against a link independently capable of ≥6.9 Mbps. Pacing (bounded per-(peer,path) exact-byte shapers, enabled via [scheduler] pacing_enabled = true) applies backpressure instead of pacer-induced loss, bounds the queue so loaded RTT stays near idle baseline, and lets single-flow TCP use learned delivered capacity. link_bandwidth supplies the initial active-backup seed; only an explicit link_bandwidth_limit supplies a safety ceiling. This section validates pacing's effectiveness on the real deployment (Pi4-edge/Starlink/o3 topology).

IMPORTANT: The netns/e2e fixture test suite MUST NOT assert absolute throughput thresholds (e.g., "single-flow TCP ≥ X Mbps"). This is a manual/real-host tier validation only, because the netns fixture is CPU-bound and cannot build the standing queues pacing is designed to prevent. Absolute throughput assertions belong in this section (real links), never in automated netns e2e tests. TestFixtureImpairment and any similar capped-fixture netns tests remain throughput-measurement report-only (informational) with no pass/fail gate on absolute numbers (see design.md pacing section).

Setup and prerequisites

  • Edge and concentrator both have the tunnel up with both uplinks alive (per §P1 setup). Both daemons running from 0600 configs, /metrics reachable on 127.0.0.1:9090 each end.
  • Record date, wanbond version output, current build (git log --oneline -1).
  • Measure and record the idle RTT and measured throughput per uplink (instructions in install.md §3a); these set the initial pacing seed/budget (link_bandwidth / link_rtt), not an absolute-goodput floor. Record any explicit link_bandwidth_limit separately.

T324 active-backup closed-loop field acceptance

This is the current Q91 acceptance and supersedes T304's fixed 8Mbit/5Mbit condition matrix and absolute 5.6Mbit clause below. Retain the older section as historical exact-byte-shaper evidence.

  • Before deployment, run the non-privileged gate and nix build. On an ARM64 Linux host, run TestLinuxTUNAQMReconciliationContract and TestLinuxTUNAQMNativeTUNFullMTUHandoffContract: the in-memory dummy and real privileged Linux adapter must pass the same topology/rate/queue/drift-restoration contract, and the native TUN must prove zero-drop full-MTU transient handoff and visible finite-overload placement.
  • Record both endpoint commit IDs, binary SHA-256 hashes, config hashes, service states, TUN link details, tc -j qdisc, tc class, and a full /metrics scrape before the candidate starts.
  • On both edge and concentrator, confirm active-backup+pacing starts only after exact readback: HTB root and one bfifo child with L=limit=max(P*U,ceil(R*Q)) bytes, where P is the peer count, U=gso_max_size, Q=20ms, and R is aggregate ingress. Confirm its persistent service bound is at most max(Q,P*U/R). With nominal complete-batch service budget D=20ms, per-peer rate r, maximum WireGuard datagram W=Mmax+32, conservatively rounded complete-batch service time T<=max(D,W/r), current/maximum MTUs Mcur/Mmax, and exact installed burst G, calculate A=ceil(R*T)+G, H=ceil(A/Mcur), and J=ceil(A/20). Confirm wanbond0 txqueuelen>=J+1; a larger observed baseline is promoted to the target and retained as the live-interface high-water. Separately confirm per-peer engine admission B+C, where B is the maximum current active outer-shaper DATA budget and C is the complete GSO batch below. Confirm HTB burst=cburst=G>=gso_max_size and exact target/actual burst, epoch, rate, ring capacity, and queue-limit readback. wanbond_tun_aqm_actual_fresh must be 1. Record ((H+1)*Mmax+L)/R; conditional on the T<=max(D,W/r) reader-service precondition, this full-MTU-valued transient handoff plus byte-leaf service bound must remain sub-second. Do not value all J slots at Mmax: an arbitrary reader stall lies outside this invariant.
  • Restart each endpoint with no offered inner traffic. Startup must install and expose fresh TUN-AQM target/actual metrics without waiting for a TUN packet; an idle engine read must not block ptr-ring occupancy readback.
  • From per-peer ingress rate r, maximum configured inner MTU Mmax, and current inner MTU Mcur, calculate S=max(1,min(128,floor(65536/Mcur),floor(r*20ms/(Mmax+32)))). Confirm the link target/actual GSO readback is exactly gso_max_segs=S, gso_max_size=S*Mcur, and the per-peer engine byte limit is B+S*(Mmax+32). The complete batch C=S*(Mmax+32) must have service time no greater than 20 ms whenever floor(r*20ms/(Mmax+32))>=1. Below that threshold, confirm S=1 and conservatively rounded T=(Mmax+32)/r>20ms; the additional B preserves one ACK-clocked BDP.
  • Run two independent synchronized field cycles. Each cycle contains one 30-second Pi→o3 TCP upload and one 30-second o3→Pi TCP download (iperf3 -R from the Pi). For each leg, start timestamped inner and outer pings before traffic; scrape controller/TUN-AQM/engine/shaper/FEC/ resequencer metrics plus tc -s and ip -s link at t=0, at least once per second, and at leg end. Preserve iperf JSON with one-second intervals and retransmits. Quiesce traffic between legs so each direction receives an unambiguous boundary.
  • In every leg, wanbond_path_congestion_outer_wire_bytes_total advances by actual successful outer IP+UDP/frame bytes, while inner_data_bytes_total advances only for native DATA. Reconcile their ratio with FEC/control/probe overhead; do not infer capacity from inner payload alone.
  • Base RTT and queue delay reset on the first A→B or B→A carrier generation. Carrier epochs increase monotonically; delayed/stale/replayed/wrong-peer DATA-loss feedback never changes the new epoch's target. Once current authenticated feedback has been adopted, stale feedback cannot raise the target or cause a loss-based decrease; only current local queue delay may still drive a fail-closed decrease after continuous qualification.
  • Queue delay at least max(base RTT/2,10ms)+4*wanbond_path_jitter_seconds under continuous load for one elapsed second reduces the target. A newly accepted fresh authenticated DATA-loss report of at least 0.5% also reduces it exactly once, even when the report arrives after a later unloaded byte interval; adjacent above-threshold reports from the same loss episode must hold the target. One continuous second of fresh below-threshold evidence must rearm a later episode. Stale evidence, counter regression, and pending settlement caused by an outer-rate change must break that clean dwell without clearing the current episode. Ingress-only expansion or headroom settlement must not break it: after one loss response, vary expansion while supplying fresh zero-loss feedback for one second and confirm outer additive recovery resumes. Repeat with pressure preempting the outer retarget; after exact combined-target readback settles and the next headroom-only change, clean dwell must resume. Fresh loss received on the carrier-initialization sample must remain actionable on the next observation. Fresh loss received while counters regress or another target settles must remain pending and apply one decrease once a valid observation or settlement permits a retarget. RTTVAR and dwell qualify only delay and must not suppress the first eligible fresh-loss response; sustained queue delay may still reduce during an active loss episode. A no-loss variable-RTT trace whose delay remains below that threshold must not reduce the target. A single crossing followed by a below-threshold or unloaded observation must not reduce it; counter regression, carrier transition, and pending retarget settlement must also reset the dwell. Sustained delay must reduce after the continuous second. One congested decision multiplies the prior target by exactly 0.85; a partially offered/emitted interval must not impose a second delivery-derived cap. Clean emitted service permits bounded additive increase above the seed. With link_bandwidth_limit set, the target never exceeds that explicit ceiling; with it omitted, confirm a clean loaded target can grow beyond link_bandwidth. Confirm the TUN target follows the learned outer/inner ratio. After first driving the ratio above 2.5, reduce actual expansion to 1.1 while native DATA saturates the installed ingress target but outer service remains below half its target; the ratio must decline and the ingress target must rise. Exact kernel readback follows the target with an in-place bfifo change: packet/drop counters must remain monotonic and a correct live leaf must never be deleted/re-added. The first eligible fresh authenticated-loss tick must decrease promptly; a discontinuity or pending target must retain it until the next valid, settled tick. Before another target change, retarget_pending must remain 1 until installed_fresh=1 for the same rate/epoch and at least max(1s, active base RTT) has elapsed; target_changes must not advance during that wait. Repeated exact readbacks must not restart the interval: under sustained queue-delay congestion, the next delay decision occurs promptly after its original deadline. Adjacent loss reports from the same episode continue to hold. A stale/mismatched readback re-arms the interval, and a carrier-epoch transition cancels the prior wait.
  • Confirm ingress-only local service headroom starts at 0.95 without changing the outer target. A loaded interval whose per-controller engine admission wait occupies at least half the interval must multiply headroom by 0.85 before link drops rise. Ptr-ring occupancy remains diagnostic: loaded ring occupancy with zero or sub-threshold admission wait, and every unloaded/opposite-direction interval, must not reduce headroom. Unchanged cumulative counters or a non-advancing interval must not replay the decrease. The first loaded local-pressure decrease may preempt settlement for an unrelated outer-capacity retarget; repeated pressure must wait for exact ingress rate/epoch readback and its own max(1s, active base RTT) settlement. Recovery remains settlement-gated. Recovery adds 0.01 only after three consecutive loaded, clean, settled intervals. Idle intervals do not recover. A carrier epoch resets headroom to 0.95.
  • During a capacity shrink, rate_fresh may become 1 while actual_fresh=0 only when a corresponding queue-limit, GSO-limit, or engine-admission deferred gauge is 1. The installed queue limit must remain at least the desired limit until byte backlog fits; GSO shrink waits for zero TUN backlog, with the aggregate leaf held at its installed value and the normalized HTB burst at least the installed link GSO maximum. Engine admission must remain at its old value while GSO shrink is deferred. After exact smaller-GSO readback, attempt the engine shrink; if any peer's retained bytes do not fit, restore the old downstream capacity envelope and leave admission deferred. While that engine shrink remains deferred, target_engine_admission_limit_bytes must show the desired bound, actual_engine_admission_limit_bytes and actual downstream capacity must remain at the installed envelope, the desired rate/epoch must apply, actual_fresh must be 0, and rate_fresh must be 1. After both queues drain, downstream capacity must read back before the engine value shrinks. The online ptr-ring target and actual must retain the maximum of their observed interface baseline, later larger readbacks, and derived J+1 across lower rate/MTU targets, and reset only on interface recreation. On growth, capacity must read back before the engine value rises. All deferred gauges must return to zero after drain, with exact target/actual readback and no new qdisc or link drop.
  • Do not use TestP1Failover to validate the preceding transition. Its generated config omits [scheduler], so pacing defaults off and TUN AQM never starts. Use an active-backup fixture with pacing enabled and per-path shapers, then confirm the TUN AQM gauges are present before interpreting transition order.
  • For a drained GSO reduction, verify the smaller GSO limits write and read back before any HTB burst or bfifo reduction. Inject one queued packet at the GSO-write seam: the subsequent occupancy read must retain the old aggregate leaf and burst for that reconciliation. Drain it and verify a later reconciliation applies the exact desired leaf and burst.
  • Exercise a mixed transition from gso_max_size/gso_max_segs=64000/64 to 64170/46. Despite the segment-count decrease, verify leaf and burst grow to at least 64,170 bytes before the combined GSO write. A segment-only change at fixed maximum byte size must not trigger the byte-capacity shrink sequence.
  • Confirm the plaintext bfifo target equals max(peerCount*gso_max_size,ceil(ingress_target*20ms)) and its persistent service time does not exceed max(20ms,peerCount*gso_max_size/ingress_target). Holding ingress rate, peer count, and GSO size fixed while doubling the engine admission window must not change the leaf limit. At 40,000 B/s with one 1,284-byte GSO quantum, read back 1,284 bytes and at most 32.1 ms, not the prior 7,200-byte/180 ms engine-derived window.
  • Exercise independent native handoff phases on a fresh queue: H full-MTU packets and ceil(A/29) minimum legal UDP-over-IPv4 TUN packets over one bounded complete-batch service interval must each leave link and qdisc drops unchanged with ring readback at least J+1. The unit contract must separately prove the exact 20-byte slot boundary. A deliberate overload must increase at least one captured link/qdisc counter and report the layer. Do not claim arbitrary-stall losslessness from the bounded result.
  • For each single ACK-clocked TCP leg, the byte-bounded bfifo leaf incurs zero local drops and its backlog returns to zero after the leg. An offered burst no larger than the calculated service backlog must also incur zero local drops. Overload beyond the explicit queue limit may tail-drop, but every such drop must advance wanbond_tun_aqm_drops_total. Confirm that metric against the maximum leaf/root value from tc -j -s qdisc; a non-stat tc -j read is insufficient.
  • The reproduced failure does not recur: the peer engine queue must not reach its 1,024-container limit, wanbond_engine_admission_retained_bytes must stay at or below wanbond_engine_admission_limit_bytes, and wanbond_engine_admission_oversize_batches_total must stay zero after fresh GSO readback. Shaped-FEC ownership must remain covered by that same retained reservation through terminal emission; do not interpret the simultaneously visible FEC-owned bytes as an independent capacity. Record both wanbond_engine_{encryption,peer}_queue_high_water_containers to identify the exact queue owner. Hidden inner RTT must not grow into seconds while outer RTT stays near baseline, and TUN/shaper/socket/FEC accounting must show no unexplained loss. Evaluate receiver goodput, retransmits, AQM ECN/drops, authenticated loss, and queue-delay response together. Q91 has no fixed absolute throughput floor.
  • Stop/restart one endpoint and confirm a fresh controller/qdisc actual state appears before service returns. Restore the original binaries and configs if the candidate will not remain deployed; otherwise record that the candidate remains installed.

Historical T304 synchronized RPi4-to-o3 field acceptance

Run on the RPi4 edge with Starlink active, 5G standby, active-backup policy, and adaptive FEC enabled. Preserve the starting state before the first cycle:

Before the real-link cycles, validate the T309/T318/T323 FEC sender-owner invariants:

  • Run go test -tags failfirst ./internal/bind -run '^TestFailFirstFEC' -count=1 and the same command with -race; both pass. These deterministic checks assert that open-group DATA/PARITY stays hidden, exact deadline dispatch stays within the bind-local 10ms grace, an expired group wins over queued admissions, a 257-frame Send publishes one owner batch and preserves exact payload/sequence order, writer-prefix failure consumes no suffix sequence numbers, shaped and exclusive unshaped-recovery serial sub-K sends acknowledge owned caller buffers before terminal group service, uncontracted direct sends stay synchronous, post-ack failures retire only the exact generation, and Close rejects unowned suffixes while joining every acknowledged owner-side completion.

  • Run go test ./internal/bind -run 'TestProductionBatch128CappedShapedSerialAdmissionFillsGroups|TestFECSendStreamsBeyondOwnerMailboxCapacity|TestMultipathFECDeadlineEmitsPartialGroupParity' -count=1; the capped production batch-128 fixture, 257-buffer offload batch, and underfilled deadline group all pass. The production fixture must report exact size-closed K+M fill, zero deadline decisions/misses and terminal errors, accepted=emitted conservation, and nonzero configured-vs-observed byte-rate/count evidence.

  • During one Down/Up cycle with a deliberately underfilled FEC group, capture the outer UDP stream. Confirm no DATA/PARITY from the old group appears after the old transport generation closes; the first post-Up group starts from a fresh FEC sender generation.

  • Record the edge and o3 commit IDs, binary hashes, config hashes, service states, and the original pacing declarations. The last step must restore and re-hash this exact state.

  • Use one 30-second edge-to-o3 TCP upload per tunnel leg, discarding the first 5 seconds as warmup. During the whole leg collect timestamped loaded ping, one-second iperf3 intervals/retransmits, FEC counters, and every wanbond_path_shaper_* and wanbond_engine_* series. Take synchronized counter scrapes at t=0, the final-window boundary t=10s, and the leg end t=30s (or continuously sample with timestamps at least this precisely). Compute the final-20-second outer rate and counter deltas strictly from t=10s through t=30s; retain the t=0 scrape for whole-leg error/cancellation checks.

  • Run three cycles with condition order rotated to reduce order bias:

    | cycle | first | second | third |
    |---|---|---|---|
    | 1 | pacing off | `8Mbit` | `5Mbit` |
    | 2 | `8Mbit` | `5Mbit` | pacing off |
    | 3 | `5Mbit` | pacing off | `8Mbit` |
    
    Pacing-off uses the direct complete-batch path. Each paced condition sets
    the active Starlink `link_bandwidth` to the named cap and keeps valid
    declarations on every path; wait for both paths and the WG session to
    return UP before the 5-second warmup.
    
  • Immediately before and after every 8Mbit or 5Mbit tunnel leg, stop tunnel load and measure direct Starlink upload to o3 in the same direction. Treat that bracket as a valid raw pair only when both raw measurements are at least 1.03 * cap and abs(raw_before-raw_after)/max(raw_before,raw_after) <= 0.15. A raw-invalid interval is inconclusive and cannot close the criterion. Repeat as needed until at least two valid raw pairs per cap remain.

  • Capture iperf3 --json receiver bytes/retransmits and timestamped ping -i 0.1 samples so loaded RTT p95 and retransmits per GiB of receiver payload can be calculated, rather than inferred from summary averages.

For each cap, at least two raw-valid paired cycles must satisfy all of these predeclared gates:

  • Over each paced leg's final 20 seconds, total outer egress — shaped emitted bytes plus successful direct outer-priority bytes — stays within ±5% of the declared cap.
  • No ordinary queue loss occurs: no scheduler-shedding record appears, and deltas for admission-canceled datagrams, terminal shaped-call errors, and asynchronous generic/EMSGSIZE writer errors are all zero.
  • TCP receiver goodput is at least 70% of emitted DATA bytes. Reconcile DATA, FEC parity, inner control, and direct outer-priority bytes rather than treating parity as application goodput.
  • Each paced loaded-RTT p95 is at most 60% of its cycle's unpaced loaded-RTT p95.
  • Across the retained valid runs, median retransmits/GiB at 5Mbit are at most 1.25× median retransmits/GiB at 8Mbit.
  • While the paced upload fills the DATA budget, confirm every configured path remains live and wanbond_path_probe_send_errors_total does not increase. Generated authenticated PROBE/echo frames reserve retained priority capacity and use the same serialized path writer as DATA/control/recovery. The counter includes unexpected socket-write failures for locally-originated ordinary and PMTU PROBE frames: an ordinary failure is counted but not returned to a caller, while an unexpected PMTU failure is counted and returned to discovery. Expected PMTU EMSGSIZE too-large verdicts and reactive reflected-echo write failures are excluded. A padded PMTU request substitutes in an eligible local probe slot; the following slot must be ordinary liveness before another PMTU attempt. PMTU does not add a second local producer, while reactive echo replies remain immediate. Confirm the PMTU timestamp begins in its selected slot (cadence wait does not inflate the RTT sample).
  • During a paced transfer, remove and re-add the standby path through the normal config reload. Confirm the transfer on the retained active path continues, the removed path emits no post-removal datagrams, and its replacement begins with zero wanbond_path_shaper_* counters (a fresh shaper/socket generation).
  • With pacing off, confirm curl -s http://127.0.0.1:9090/metrics | grep wanbond_path_shaper_ returns no series. Enable pacing and confirm the series appear for each paced path with fixed path (and, where applicable, peer) labels.
  • During a paced saturated transfer, confirm shaper_data_budget_bytes >= shaper_max_datagram_bytes (B>=Lmax), shaper_control_reserve_bytes = shaper_max_datagram_bytes (C=Lmax), and shaper_queue_budget_bytes = shaper_data_budget_bytes + shaper_control_reserve_bytes (Q=B+C). Also confirm shaper_queue_data_bytes <= shaper_data_budget_bytes, shaper_queue_control_bytes <= shaper_control_reserve_bytes, and shaper_queue_bytes <= shaper_queue_budget_bytes, with shaper_priority_retained_bytes <= shaper_priority_burst_bytes, shaper_fec_group_owned_bytes equal to either zero or the configured Fgroup, and shaper_memory_retained_bytes <= shaper_memory_bound_bytes (Mtotal=B+C+P+Fgroup+Lio). At full B/Q, shaper_admission_waits_total and shaper_admission_wait_seconds_total should rise while asynchronous generic/EMSGSIZE errors and canceled datagrams remain flat.
  • Read shaper_priority_debt_bytes (P0), shaper_rate_bytes_per_second (R), shaper_priority_rate_bytes_per_second (Rp), and shaper_priority_burst_bytes (Pburst). Confirm the exported shaper_priority_delay_bound_seconds equals (P0+Pburst)/(R-Rp), including on the configured path with the largest Rp/R ratio.
  • After traffic quiesces, reconcile accepted_bytes = emitted_bytes + async_write_error_bytes + async_write_emsgsize_bytes + queue_bytes + in_flight_bytes. Compare emitted parity bytes with wanbond_fec_repair_bytes_total; direct generated PROBE/echo bytes remain separately visible in shaper_outer_priority_bytes_total. Repeat during load: accepted bytes linearize at queue reservation, so a pending-copy placeholder appears in both accepted_bytes and queue_bytes; cancellation counts only buffers that never reserved capacity.
  • With FEC active on a naturally single-path group, saturate B/C/P and confirm the complete native DATA+parity tranche finishes before the derived A and before 250 ms for first-, middle-, and last-DATA loss. Confirm later probe/echo arrivals do not enter the tranche and a second group backpressures. Mixed-path/shared-socket groups must report the finite contract disabled and retain the conservative fallback.
  • Capture ordinary unpadded PROBEs during FEC+pacing operation. Confirm each contract-bearing OFFER/ACK is 102 outer bytes (75-byte base plus the canonical 27-byte payload), remains within P=2*Lmax, and increments shaper_outer_priority_bytes_total by its exact wire size. Repeat with either FEC or pacing off and confirm ordinary probes remain the legacy 75-byte form; padded PMTU probes must carry no contract. While a conservative FEC gap is armed with no acknowledged receiver venue, send legacy probes faster than 250 ms and confirm they neither advance the receiver generation nor move the original deadline. After acknowledging a venue, confirm the first legacy probe revokes it and later legacy probes remain idempotent. Repeat while the ACK echo write is blocked after admission: the first legacy probe must invalidate that admission, and the stale write completion must not restore a venue. Fail an ACK write terminally and confirm the next legacy probe moves neither generation nor deadline. With two concurrent admissions, fail one and confirm the survivor remains revocable; fail both and confirm no revocable evidence remains.
  • During a saturated transfer, remove/re-add the standby path. Confirm DATA/inner-control pauses while PROBEs continue, the old staged FEC group completes, no old-service write appears during the 250 ms quiet interval, and the retained path resumes either after the fresh authenticated ACK or at the 250 ms legacy fallback. OuterSeq and FEC GroupID must remain monotonic. Replay the old OFFER and an inconsistent same-identity OFFER; neither may produce an ACK or re-enable fast recovery.
  • Drop the first OFFER, the first renewal, and a later renewal at the 200 ms probe cadence. Confirm DATA resumes conservatively at 250 ms without retiring the live OFFER, a later exact ACK succeeds only while at least 250 ms of its own validity remains, and a lost renewal disables fast recovery before the prior lease becomes unsafe. With an incomplete receiver FEC group, accept a higher same-session ContractID carrying the identical service value and hold its ACK write. Confirm the prior ACK-completed venue, expiry, topology generation, and any live fast deadline remain unchanged while DATA-loss identity advances. Complete the ACK and confirm the new expiry becomes eligible; fail it and confirm the old evidence expires at its original time. Confirm the group still recovers. With active and standby venues, complete the standby renewal ACK first and confirm the active venue retains its old fast window until its own renewal ACK or original expiry. Repeat with a changed value or new SessionID and confirm the incomplete group clears before ACK. Hold an old OFFER's socket write across a ContractID rotation and confirm its completion cannot authorize the new identity.
  • With active-backup FEC and an exact successfully emitted ACK, record A and the authenticated SRTT and RTTVAR of the current DATA carrier. An idle Up backup must neither inflate H nor gate freshness. Lose one DATA frame on the ACKed composite path/source and confirm the receiver releases at W=min(250ms,A+clamp(max(SRTT+4*RTTVAR),10ms,250ms)): repair at W-1ns fills exactly once in order, while repair at W loses the gap and releases the successor. Repeat with no ACK, wrong path/source, a second delivering key, absent/stale RTT, less than 250 ms contract/RTT validity, A>=250ms, weighted policy, rebaseline, and membership/session/service transitions; every case must use or re-arm a fresh 250 ms fallback. Hold an old ACK completion and an old window refresh across each transition, source roam, resequencer replacement, and teardown; neither may restore fast evidence. Force a bounded-window advance that exposes a second gap near the first gap's expiry and confirm the second gap receives a fresh full hold with strictly ascending, exactly-once delivery.
  • Pause one recovery-window refresh after it snapshots a primary-only ACK venue and lower RTT. Complete a standby ACK and record a newer higher authenticated RTT sample, publish the newer refresh, then release the old refresh. Confirm the standby venue remains usable and H retains the new maximum RTT. Repeat with the old refresh paused after publication-revision reservation to cover reversed resequencer commit order. If a gap was already armed before the same-topology update, confirm its exact deadline does not change and the new venue/RTT applies only to the next gap.
  • Pause each topology transition after the coordinator generation advances but before the explicit resequencer publication: service change, SessionID adoption, membership add/remove, same-key roam, rebaseline, resequencer replacement, and teardown. At the old W boundary, concurrent receive/Pop must not release; it must observe the authoritative generation and set the fallback deadline to the topology transition instant plus 250 ms, not the observation instant plus 250 ms. Pause receive until after that bound and confirm immediate release. Burst several advances while receive remains parked and confirm their wake coalesces to the latest coherent generation/time without allocating another timer or spinning; Close must still prevent post-close delivery. Repeat with FEC off and confirm its existing non-FEC gap deadline remains unchanged.
  • Change same-name pacing/FEC service inputs (R/Rp/B/C/P/Lmax/Kdata/Mmax/I) and reload. Confirm the daemon warns that the change remains unapplied until restart and the running service retains its old ContractID. After a daemon restart, confirm the peer adopts a new authenticated SessionID. Separately force an engine Bind Close→Open within one process and confirm ContractID rotates while SessionID, OuterSeq, and FEC GroupID remain monotonic.
  • Delay a FEC deadline decision beyond the dispatch grace. Confirm the current group completes, the next DATA group blocks immediately, and a fresh OFFER appears only after the asynchronous drain plus 250 ms quiet interval; Close during that interval must cancel the waiter. Reopen before releasing an old-generation deadline worker and confirm it neither invalidates nor rotates the replacement contract. Repeat through two rapid Close/Open generations, queue a current-generation miss while the stale worker releases ownership, and confirm the current request still reaches an exact ACK or the 250 ms fallback rather than leaving DATA blocked.
  • Force recovery deadline-install, clear, and running-writer failures. Confirm each exact socket generation immediately rejects admission, disappears from its peer/scheduler/remote view, and quiesces on Close; every already-accepted owner-side terminal completion must preserve the originating cause (a shaped caller already acknowledged at ownership is not retroactively failed), and accepted bytes must reconcile exactly into emitted plus generic or EMSGSIZE terminal bytes with no retained remainder. For an interrupted in-flight syscall, confirm completion reports the published cause while call/byte metrics retain the actual syscall error class; then reopen and confirm a delayed stale failure cannot retire the new generation.
  • During probe saturation, distinguish bounded priority outcomes: wanbond_path_probe_priority_coalesced_total may rise for skipped ordinary cadences, wanbond_path_pmtu_admission_canceled_total only when generation close cancels a wait for P before probe generation (not an ErrClosed from generation or writer after reservation), and wanbond_path_echo_priority_overflow_total only for non-blocking reactive echo drops. None should coincide with receive-loop blocking.
  • Cycle the edge daemon Down/Up once with pacing enabled. Confirm no send remains blocked, the tunnel reconnects, and the journal contains no post-close socket-write errors from the prior generation. Repeat several cycles when validating a lifecycle change; goroutine count should return to its pre-cycle baseline.

For interpreting a transient, let P0 denote generated-priority debt at the start of a send call. With one coincident post-call Pburst=2*Lmax, sustained generated priority no greater than Rp=Pburst/200ms, and configured byte rate R>Rp, admission is bounded by Dp=(P0+Pburst)/(R-Rp) (not P0/R). Once admitted, local egress adds at most Q/R+Lmax/R. For an exclusive single-path FEC recovery cut, additionally use A=I=10ms; it must remain below 250 ms. The prefix B+C+P runs before any successor DATA can arm a receiver gap and therefore does not enter A. Therefore call-to-receiver delivery is bounded by Dp+Q/R+Lmax/R plus the active resequencer hold for a missing lower outer sequence. Record any observed inner-control call and check it against that complete bound. These bounds cover the built-in PROBE/echo producer, including PMTU probes that occupy ordinary local cadence slots. Priority arrivals in the half-open interval [call, call+Dp) update the registered waiter under the shaper lock even when its goroutine does not observe the change until the former deadline; once it matures, an exact-boundary debit changes future reservations but cannot revoke the current call's eligibility, so admission needs no extra timer or packet step. Do not rely on these bounds if a future authenticated outer CONTROL producer sustains traffic beyond the declared Rp/Pburst model; that condition constitutes explicit overload.

Canonical record notation: D=250ms; G=10ms; F=1200ms; B/C/P/Fgroup/Lio/Mtotal; R/Rp/I; Sdevice; H=clamp(max(SRTT+4*RTTVAR),10ms,D) over qualified fresh DATA carriers only; W=min(D,A+H); Ecompletion. Record SessionID restart versus same-process ContractID rotation separately and verify OuterSeq continuity across rotation. Require the exact authenticated ACK and A+H<D for fast recovery; otherwise require installed W=D and record the labelled fallback, including saturated. Validate geometry from config/contract state—there is no zero-parity inference.

  • With pacing enabled on the edge and disabled on a single-peer concentrator, confirm both directions publish and ACK A=10ms, report fast_eligible=1 when A+H<D, and install W<250ms. A shared multi-peer concentrator socket must continue to publish disabled.

  • Under deterministic or recorded sparse loss, confirm several gaps whose successors arrived within one W release at their observation-based deadlines rather than accumulating one fresh W per gap. A successor first observed later must retain its remaining recovery interval.

  • During the run, capture FEC staged/decision/deadline series, recovery contract status/events/reasons/freshness for both bounded sender/receiver directions, receiver RTT age/H/W, shaper outer-priority outcomes/cut/high-water series, and resequencer arm/deadline/wake/fill series. Confirm the monitor reports the same current values.

  • Confirm group_decisions_total reconciles with resolved staged groups, each active cut's datagram membership reconciles with its socket-call delta, and aggregate accepted/emitted/error bytes remain the cumulative authority while DATA/control queue gauges report current pressure.

T304 result record

Record every cycle, including invalid raw brackets; do not silently discard an interval. For each leg retain the raw before/after values, raw-valid verdict, final-20-second outer rate, receiver goodput/emitted-DATA ratio, loaded RTT p95, retransmits/GiB, queue maxima, error/cancellation deltas, and DATA/parity/ priority reconciliation. Summarize how many raw-valid pairs remain at each cap and the medians used for the 5Mbit versus 8Mbit retransmit comparison.

The field gate passes only when at least two raw-valid pairs per cap meet every threshold above. A below-cap or unstable raw bracket is inconclusive, not a product failure and not a passing sample.

Record and close-out

  • Date: _____________
  • Starting edge/o3 commits, binary hashes, config hashes, service states: _____________
  • Idle RTT per uplink (from Step 1 measurement): _____________
  • Measured throughput per uplink (from Step 1 measurement): _____________
  • Cycle 1 (off -> 8 -> 5) report/artifact path: _____________
  • Cycle 2 (8 -> 5 -> off) report/artifact path: _____________
  • Cycle 3 (5 -> off -> 8) report/artifact path: _____________
  • Valid raw pairs: 8Mbit _____ / 5Mbit _____ (each must be >=2).
  • Threshold table and exact-byte reconciliation: PASS / FAIL / INCONCLUSIVE
  • Restore original binaries/configs/service states on edge and o3; record final hashes and confirm they equal the starting hashes: _____________
  • Go/no-go decision:
    • T304 exact field gate: PASS / FAIL / INCONCLUSIVE
    • Configuration/envelope valid (B>=Lmax, C=Lmax, Q=B+C, Rp<R, exact Pburst/P0/Dp): YES / NO
    • Proceeding with pacing enabled for field deployment? YES / NO
    • Notes: _____________