• Decision: Close CAPTURE-04 by fixing its three actual root causes — an ALPN-blind tshark display filter, a Decryption Secrets Block emitted after the packet blocks it is supposed to decrypt, and tshark's default refusal to reassemble out-of-order TCP segments — and not by migrating the capture backend to a mmap'd AF_PACKET ring. Concretely: (1) a new exported capture.AppDataFilter = "http or http2" (internal/capture/pairing.go) replaces the bare -Y http at every call site; (2) capture.Writer.EmbedKeylog now only records keylog lines and Close re-emits the capture as SHB → IDB → DSB followed by the existing packet blocks copied byte-for-byte into a 0600 temp file and atomically renamed, buffering no packets in memory; (3) capture.DecryptedAppData always passes -o tcp.reassemble_out_of_order:TRUE. TestSmoke_OfflineValidationDecryptsPlaintext's 5×-retry-then-t.Skipf block is deleted and replaced with a hard assertion. AD-012's "in-process gopacket backend segment loss" diagnosis is recorded as a misdiagnosis, and Feature 03's Phase 6 (T15–T21, the gopacket/afpacket migration drafted on that diagnosis) is withdrawn.
  • Reason: Each cause was isolated by live testing on a real rootful Podman pod, and each is sufficient on its own to make a complete, correctly-keyed capture decrypt to nothing:
    • Filter. The app's curl negotiates HTTP/2 via ALPN. tshark dissects h2 with a separate http2 dissector that -Y http never matches, so an h2 session yields zero matching frames even when the capture is perfect — indistinguishable from total capture or decryption failure. This accounted for 100% of the systematic failure.
    • DSB placement. EmbedKeylog ran during Close and appended the DSB as the file's last block (observed: block 138 of 139, after EPBs 2–137). pcapng scopes a DSB to the blocks that follow it and tshark reads a capture strictly sequentially, so those secrets arrived too late to decrypt anything. The documented "self-decrypting capture" had therefore never worked.
    • Out-of-order segments. Roughly a third of sessions recorded all their TCP segments but out of sequence (confirmed via IP IDs: the server sent them in order). tshark's default reassembly abandons such a stream at the first gap, which reads exactly like loss.
    • The afpacket evidence. A tcpdump run simultaneously in the same netns — which is an mmap'd AF_PACKET ring, precisely what gopacket/afpacket would provide — recorded the same reordering on 6 of 8 streams and decrypted only 5/8 by default, 8/8 with the reassembly option. The reordering is therefore a property of the capture point, not of the non-mmap socket, and the afpacket migration would not have fixed CAPTURE-04.
  • Trade-off: Close now rewrites the capture file (a full sequential copy through a temp file and a rename) whenever secrets were embedded, so graceful shutdown costs one extra pass over the capture and briefly needs disk headroom for a second copy; the packet blocks stream through and are never held in memory. AppDataFilter matches two dissectors rather than one, so the offline-validation filter is deliberately broader than a single-protocol assertion. The out-of-order reassembly option trades tshark memory for completeness. None of this improves raw capture throughput: if a future workload does hit genuine kernel-side drops, an AF_PACKET ring remains the right answer — but it must be justified by measured drop counters, not inferred from a decrypt failure, which is exactly the inference AD-012 made and this decision reverses.
  • Scope: Feature 03 — internal/capture/{pairing.go,pcapng.go} and their tests (pairing_it_test.go, pcapng_test.go, tcpdump_it_test.go), plus deploy/podman/smoke_e2e_test.go. No change to the capture backend, cmd/app, the eBPF programs or the harness scripts.
  • Date: 2026-09-19
  • Status: active. Evidence: make build clean; make lint 0 issues; unit -race 55 passed / 0 failed; integration -race (root) 74 passed / 0 failed / 2 skipped (both host-side and pre-existing: connect4 PROG_TEST_RUN kernel gap, tcpdump-parity AppArmor signal-deny); e2e on a live rootful pod 6 passed / 0 failed / 0 skipped, run 3 consecutive times. A new e2e test, TestSmoke_RetainedCaptureSelfDecryptsFromEmbeddedSecrets, proves self-decryption against an empty keylog file. Every new test was confirmed discriminating (each fails against the pre-fix code).