feat(analyze): --coverage-gaps flag + CoverageGapsSummary tri-state report (STORY-154)#355
Conversation
…_state stub (mod story_154/_unit)
Adds the STORY-154 Red Gate per TDD protocol (BC-2.12.023 v1.2 + BC-2.12.024 v1.1):
src/main.rs:
- ProtocolGapState enum { KnownUnsupported, Unknown, KnownSupported } with #[allow(dead_code)]
- lookup_protocol_state(TransportProto, u16) stub — todo!() body, always panics
- mod story_154_unit (4 unit tests, all FAIL via todo!() panic):
test_BC_2_12_024_bacnet_known_unsupported_unit (Udp, 47808) → KnownUnsupported
test_BC_2_12_024_tcp_47808_is_unknown_unit (Tcp, 47808) → Unknown (transport mismatch)
test_BC_2_12_024_unknown_port_state_unit (Tcp, 9600) → Unknown
test_BC_2_12_024_known_supported_is_bug_signal_unit (Tcp, 502) → KnownSupported (unit-only; EC-154-11)
tests/integration_tests.rs:
- mod story_154 (16 tests):
13 RED GATE — fail with clap exit-code 2 ("unexpected argument '--coverage-gaps'"):
test_BC_2_12_023_coverage_gaps_counts_unclassified
test_BC_2_12_023_coverage_gaps_flag_produces_section
test_BC_2_12_023_json_coverage_gaps_key
test_BC_2_12_024_l2_caveat_always_present
test_BC_2_12_024_port102_footnote_on_tcp102_traffic
test_BC_2_12_024_port102_footnote_absent_without_tcp102
test_BC_2_12_024_port102_note_names_all_four (DF-CANONICAL-FRAME-HOLDOUT-001)
test_BC_2_12_024_bacnet_known_unsupported (DF-CANONICAL-FRAME-HOLDOUT-001)
test_BC_2_12_024_unknown_port_state
test_BC_2_12_024_tcp_47808_is_unknown
test_BC_2_12_024_tcp_53_is_unknown (EC-154-14 / STORY-154-DNS53-TCP-GAP-001)
test_BC_2_12_024_tcp_502_absent_from_gap_report (EC-154-11 / F-F3P14-001)
test_BC_2_12_024_json_has_caveat_field
3 GREEN REGRESSION GUARDS (pass before + after implementation):
test_BC_2_12_023_all_without_coverage_gaps
test_BC_2_12_023_protocols_coverage_gaps_error
test_BC_2_12_023_no_coverage_gaps_no_section
Fixture gaps noted with F4-FIXTURE-NEED-001 (TCP/102, UDP/47808, TCP/47808,
TCP/9600, TCP/53); placeholder uses http-ooo.pcap (tests fail at clap level
during Red Gate, content assertions matter only in Green phase).
cargo build --tests: clean
cargo clippy --all-targets -- -D warnings: clean
cargo fmt --check: clean
Existing suite (story_152): 28/28 GREEN, unaffected
…eport (STORY-154)
Implements BC-2.12.023 v1.2 + BC-2.12.024 v1.1 (ADR-012 Decisions 2, 3a/3b, 8, 9).
## Changes
**src/cli.rs (AC-154-001)**
- Add `--coverage-gaps: bool` to `Analyze` subcommand; NOT in --all group
- Doc comment is user-facing only — no internal IDs (PG-HELP-PROVENANCE-CLI-DOC-001)
**src/main.rs (AC-154-002/003/004/005/006/007)**
- Wire `*coverage_gaps` from `Commands::Analyze { ..., coverage_gaps, ... }` destructure
- Apply `.with_coverage_gaps(coverage_gaps)` builder on `StreamDispatcher` (STORY-153 pattern)
- Hoist `dns_analyzer.can_decode()` to `classified_by_dns` binding (STORY-154-CAN-DECODE-HOIST-001 / F-W67-L3)
- Add `L2_CAVEAT_TEXT` constant (BC-2.12.024 Invariant 1/3; always present)
- Add `PORT_102_NOTE` constant (conditional on TCP/102 non-zero count; names all 4 protocols)
- Add `ProtocolGapState` enum: KnownUnsupported / Unknown / KnownSupported (ADR-012 Decision 2)
- Implement `lookup_protocol_state()`: transport-aware KNOWN_PROTOCOLS catalog lookup;
supportedness derived from canonical_ports ∩ SUPPORTED_PORTS (BC-2.18.003 / AC-151-005)
- Add `collect_all_gaps()`: merges TCP + UDP gap maps
- Add `render_coverage_gaps_terminal()`: appended AFTER Findings (ADR-012 Decision 9);
always includes L2 caveat; per-entry tri-state; PORT_102_NOTE adjacent to TCP/102
- Add `inject_coverage_gaps_json()`: adds "coverage_gaps" object to JSON output
(schema: caveat_l2 + entries[{transport,port,count,state,name?,collision_note?}])
- Gate both renderers on `coverage_gaps` flag; CSV path unchanged
**tests/integration_tests.rs (AC-154-001 through AC-154-008)**
- Update 6 tests with crafted fixtures and --http flag (TCP reassembly + analyzer-present guard)
**tests/fixtures/ (F4-FIXTURE-NEED-001)**
- gap-tcp102.pcap (94B): single TCP SYN → port 102 (S7comm/MMS collision port)
- gap-udp47808.pcap (82B): single UDP datagram → port 47808 (BACnet/IP known-unsupported)
- gap-tcp47808.pcap (94B): single TCP SYN → port 47808 (transport mismatch → unknown)
- gap-tcp9600.pcap (94B): single TCP SYN → port 9600 (no catalog match → unknown)
- gap-tcp53.pcap (94B): single TCP SYN → port 53 (DNS UDP-only → TCP/53 unknown)
## Test Results
- 20 story_154 tests (16 integration + 4 unit): ALL PASS
- Full regression: cargo test --all-targets — all pass (zero failures)
- cargo clippy --all-targets -- -D warnings: clean
- cargo fmt --check: clean
## Architecture Compliance
- --coverage-gaps NOT in --all group (BC-2.12.023 Invariant 1)
- CoverageGapsSummary is additive named section, NEVER Finding structs (ADR-012 Decision 9)
- analyze without --coverage-gaps: output byte-identical to pre-feature behavior (AC-154-003)
- L2_CAVEAT_TEXT always present (BC-2.12.024 Invariant 1)
- PORT_102_NOTE conditional on (Tcp,102) count > 0 (BC-2.12.024 Invariant 2)
- JSON object form { caveat_l2, entries } NOT flat dict (BC-2.12.023 PC-3 v1.2 corrected form)
- TCP/102: omit name field, use collision_note (BC-2.12.024 PC-5 / DF-CANONICAL-FRAME-HOLDOUT-001)
- BACnet/IP (Udp,47808) → known-unsupported (DF-CANONICAL-FRAME-HOLDOUT-001)
- (Tcp,47808) → unknown (transport mismatch; BC-2.12.024 EC-009)
… sweep HIGH-1 (DF-KANI-NONVACUITY-001): test_BC_2_12_024_tcp_502_absent_from_gap_report now passes --modbus so the TcpReassembler IS built and classify() actually runs on the TCP/502 flow. Rule-5 routes port-502 → DispatchTarget::Modbus (not None) → absence of 502 in the gap report is now non-vacuous. If Rule 5 regressed, this test fails. MEDIUM-1: test_BC_2_12_023_coverage_gaps_counts_unclassified now uses GAP_TCP9600_FIXTURE with --http instead of http-ooo.pcap without an analyzer. Adds assertion that at least one gap entry (count=) is present, satisfying AC-154-002 ≥1 entry. LOW-1: test_BC_2_12_024_port102_footnote_absent_without_tcp102 now uses GAP_TCP9600_FIXTURE with --http. Anchors the absence assertion with a non-vacuous presence check: TCP/9600 entry (count=) must appear first, proving gap machinery is active before asserting PORT_102_NOTE strings are absent. SIBLING-SWEEP (DF-SIBLING-SWEEP-001): All story_154 TCP-gap tests audited. Every test asserting a TCP gap entry (port102, tcp47808, tcp9600, tcp53) already had --http; the two tests newly fixed above (502-absent, port102-absent) now also have an analyzer flag. UDP test (bacnet/47808) correctly has no analyzer flag (decode-loop path). HIGH-2 (DF-GREEN-DOC-TENSE-SWEEP): Swept all RED-GATE / todo!() / "until GREEN" / "passes before implementation" language in src/main.rs and tests/integration_tests.rs STORY-154 code. Before: 27 matching lines. After: 0 matching lines. All doc comments rewritten to present-tense GREEN regression guard language. No fixture changes required; no new internal CLI help IDs added.
…itive coverage MEDIUM-1 (BC-2.12.024 PC-5 / AC-154-007): - test_BC_2_12_024_json_entry_bacnet_schema: assert (UDP,47808) entry has transport=="UDP", port==47808 as integer, count>=1, state=="known-unsupported", name=="BACnet/IP", no collision_note field (non-vacuous: fails if name omitted or state wrong) - test_BC_2_12_024_json_entry_port102_collision_note: assert (TCP,102) entry has state=="known-unsupported", collision_note names S7comm/IEC 61850 MMS/ICCP, no name field - test_BC_2_12_024_json_entry_unknown_state: assert (TCP,9600) entry has state=="unknown", no name field LOW-1 (EC-154-4 / EC-154-7): - test_BC_2_12_024_empty_entries_message: assert empty-state message "No unclassified port gaps detected." and L2 caveat both present when analyze finds no unclassified gaps LOW-2 (AC-154-003 / AC-154-008 / Architecture Compliance Rule 3): - test_BC_2_12_023_coverage_gaps_purely_additive: assert stdout(--http) is byte-identical prefix of stdout(--http --coverage-gaps); proves the section is purely appended, no prior output mutated DF-SIBLING-SWEEP-001: test_BC_2_12_023_json_coverage_gaps_key only checks the top-level "coverage_gaps" key exists — could be strengthened to verify entries/caveat_l2 fields. Noted; not changed (new dedicated tests cover schema).
Review Cycle 1 Triage — STORY-154Both AI reviews returned APPROVE. No BLOCKING or HIGH/CRITICAL findings. Convergence achieved in 1 cycle. PR Review Triage (vsdd-factory:pr-reviewer)
PR review verdict: APPROVE (merge after considering M1 — see below) Security Review Triage (vsdd-factory:security-reviewer)
Security verdict: APPROVE. 0 CRITICAL, 0 HIGH, 0 MEDIUM. Supply chain unchanged, 0 unsafe blocks. Convergence Summary
Status: CONVERGED — 0 blocking findings. All remaining items are LOW/INFO deferred follow-ups. M1 AssessmentM1 (MEDIUM): |
…E (all E-21 stories merged), develop cad7024 Wave-69 wave-level adversarial convergence 3/3 ACHIEVED on develop cad7024 (0 P0/CRITICAL/HIGH/mis-anchor). STORY-154 delivered via PR #355 (squash cad7024). All 4 E-21 stories merged: STORY-151+153 Wave-67 b285feb, STORY-152 Wave-68 0e700a9, STORY-154 Wave-69 cad7024. Integration gate GREEN: fmt/clippy -D warnings/test --all-targets, 85 suites, 0 failures. stories_delivered 97→98. STATE.md changes: - phase: F4-delta-implementation → F4-holdout-evaluation - develop_head: 0e700a9... → cad7024... - stories_delivered: 97 → 98 - Phase Progress F4 row: IN PROGRESS (D-371) → DONE (D-372) with all 3 wave SHAs - Phase Progress: added F4 holdout-evaluation PENDING row - Current Phase Steps: +D-372 / archived D-367 to burst-log (last-5 rule) - Decisions Log: +D-372 - Backlog: STORY-153-RUNANALYZE-DOC-STALE-001, STORY-154-LOOKUP-ARP-DEADCLAUSE-001 added; BC-2.12.024-PC4-PHANTOM-SUPPORTED-001 extended (3 occurrence sites noted) - Session Resume Checkpoint: updated to D-372 / F4 holdout scope Count-propagation sweep: updated STATE.md only. stories_delivered=97 removed from frontmatter/metadata/resume- checkpoint; no other index files track delivered-count as a literal. ARCH-INDEX.md/BC-INDEX.md/VP-INDEX.md/prd.md not affected (no 97-stories literal there — confirmed via grep).
…ad-code, test hardening (#356) ## Fix: F6 Pre-Hardening Cleanup — 4 LOW/non-blocking F5 adversarial findings **Source:** Phase F5 scoped-adversarial review (feature-protocol-coverage / E-21) **Phase:** F6 pre-hardening cleanup **Severity:** LOW (all 4 findings non-blocking) **Stories:** STORY-152, STORY-153, STORY-154 **Mode:** feature (fix-PR delivery — no demo recording; no user-facing behavior change)    This PR resolves 4 LOW-severity findings carried from the F5 scoped-adversarial review pass against the feature-protocol-coverage epic (E-21, STORY-152/153/154). Changes are internal only: a stale doc-comment correction, a dead-code removal with proof comment, a new integration test for an untested `--all --coverage-gaps` combination, and tightened assertions on 3 weak `contains("unknown")` checks. No public API or user-observable behavior is altered. --- ## Architecture Changes ```mermaid graph TD main["src/main.rs"] tests["tests/integration_tests.rs"] doc_fix["doc-comment corrected\n(STORY-153-RUNANALYZE-DOC-STALE-001)"] dead_code["ARP dead clause removed\n(STORY-154-LOOKUP-ARP-DEADCLAUSE-001)"] new_test["new combined --all --coverage-gaps test\n(STORY-154-ALL-COVERAGEGAPS-TEST-001)"] tightened["3 weak assertions tightened\n(STORY-152/154-WEAK-UNKNOWN-ASSERT-001)"] main --> doc_fix main --> dead_code tests --> new_test tests --> tightened style doc_fix fill:#87CEEB style dead_code fill:#87CEEB style new_test fill:#90EE90 style tightened fill:#90EE90 ``` No architecture changes. All edits are cosmetic (doc-comment), provably-dead-code removal, and test hardening within existing modules. --- ## Story Dependencies ```mermaid graph LR S152["STORY-152\n✅ merged #353"] --> FIX["fix/f6-hardening-cleanup\n🔨 this PR"] S153["STORY-153\n✅ merged #352"] --> FIX S154["STORY-154\n✅ merged #355"] --> FIX style FIX fill:#FFD700 ``` All upstream stories are already merged into `develop`. No downstream blockers. --- ## Spec Traceability ```mermaid flowchart LR BC153["BC-2.05.010\nrun_analyze coverage_gaps param"] BC154["BC-2.12.023\nCoverageGapsSummary invariants"] BC154b["BC-2.12.024\nprotocol gap state"] F1["STORY-153-RUNANALYZE-\nDOC-STALE-001"] F2["STORY-154-LOOKUP-\nARP-DEADCLAUSE-001"] F3["STORY-154-ALL-\nCOVERAGEGAPS-TEST-001"] F4["STORY-152/154-WEAK-\nUNKNOWN-ASSERT-001"] BC153 --> F1 BC154 --> F2 BC154 --> F3 BC154b --> F4 F1 --> SRC1["src/main.rs:193\ndoc-comment corrected"] F2 --> SRC2["src/main.rs:1063\nARP disjunct removed"] F3 --> SRC3["tests/integration_tests.rs\ntest_BC_2_12_023_all_with_coverage_gaps_combination()"] F4 --> SRC4["tests/integration_tests.rs\n3 assertions tightened (lines ~1574,1606,1647)"] ``` --- ## What Changed ### Finding 1: STORY-153-RUNANALYZE-DOC-STALE-001 (commit `7fbb57c`) **File:** `src/main.rs` — `run_analyze()` doc-comment on `coverage_gaps` param **Before:** Stale scaffold comment described the parameter as a pre-ship stub wired to `false` from main() pending STORY-154. **After:** Comment now accurately describes the shipped behavior — the param activates the per-packet UDP gap counter and appends `CoverageGapsSummary` output (AC-154-002/003/007; ADR-012 Decision 9), wired via `*coverage_gaps` from `Commands::Analyze` destructure. --- ### Finding 2: STORY-154-LOOKUP-ARP-DEADCLAUSE-001 (commit `0fdaa29`) **File:** `src/main.rs` — `lookup_protocol_state()` match guard **Removed:** `|| p.name == "ARP"` disjunct in the `Some(p) if ...` guard. **Proof of death:** The enclosing `find()` predicate filters on `p.transport == catalog_transport` where `catalog_transport` is `Transport::Tcp` or `Transport::Udp` (from the packet's network layer). The ARP catalog entry has `transport = Transport::LinkLayer` and `canonical_ports = []`, so it can never pass the `find()` predicate and be returned as `Some(p)`. The `|| p.name == "ARP"` clause in the match guard was therefore unreachable on all possible inputs. A proof comment documents this reasoning in-place. --- ### Finding 3: STORY-154-ALL-COVERAGEGAPS-TEST-001 (commit `abc048e`) **File:** `tests/integration_tests.rs` — new test `test_BC_2_12_023_all_with_coverage_gaps_combination()` **Added:** Integration test covering the `analyze --all --coverage-gaps` combination, which was previously untested. Verifies (1) exit 0, (2) `CoverageGapsSummary` section present, (3) TCP/9600 row shows state `unknown`. The `--all` and `--coverage-gaps` flags are orthogonal (clap config); the test guards against any future clap conflict or orthogonality regression. --- ### Finding 4: STORY-152/154-WEAK-UNKNOWN-ASSERT-001 (commit `f90dfb8`) **File:** `tests/integration_tests.rs` — 3 assertions tightened **Before:** Three tests used `stdout.contains("unknown")` — a substring match that would pass if "unknown" appeared anywhere in stdout for any reason. **After:** Each assertion now uses a line-level check: ```rust let tcp<port>_row_is_unknown = stdout .lines() .any(|l| l.contains("TCP/<port>") && l.ends_with("unknown")); ``` This ties the port identity to the state value, preventing false passes from incidental "unknown" substrings in error messages, headers, or other output rows. Applies to: - TCP/9600 state check (BC-2.12.024 PC-4) - TCP/47808 state check (BC-2.12.024 EC-009 — BACnet/IP transport mismatch) - TCP/53 state check (BC-2.12.024 EC-010 / STORY-154-DNS53-TCP-GAP-001) --- ## Test Evidence | Metric | Value | Status | |--------|-------|--------| | Full test suite | 85 binaries, 0 failures | PASS | | New tests added | 1 (STORY-154-ALL-COVERAGEGAPS-TEST-001) | PASS | | Modified assertions | 3 tightened (STORY-152/154-WEAK-UNKNOWN-ASSERT-001) | PASS | | `cargo clippy --all-targets -- -D warnings` | Clean | PASS | | `cargo fmt --check` | Clean | PASS | ### New Test | Test | BC | Result | |------|----|--------| | `test_BC_2_12_023_all_with_coverage_gaps_combination()` | BC-2.12.023 Invariant 1, PC-1 | PASS | --- ## Holdout Evaluation N/A — evaluated at wave gate. No new behavioral contracts introduced. --- ## Adversarial Review These 4 findings originate from the F5 scoped-adversarial review pass (Phase F5). All are LOW severity / non-blocking. This PR resolves them before the F6 formal-hardening phase. | Finding ID | Category | Severity | Resolution | |------------|----------|----------|------------| | STORY-153-RUNANALYZE-DOC-STALE-001 | docs/comment accuracy | LOW | Doc-comment corrected | | STORY-154-LOOKUP-ARP-DEADCLAUSE-001 | dead code | LOW | ARP disjunct removed + proof comment | | STORY-154-ALL-COVERAGEGAPS-TEST-001 | test coverage gap | LOW | New integration test added | | STORY-152/154-WEAK-UNKNOWN-ASSERT-001 | test quality | LOW | 3 assertions tightened | --- ## Security Review **Verdict: APPROVE — no findings** ```mermaid graph LR Critical["Critical: 0"] High["High: 0"] Medium["Medium: 0"] Low["Low: 0"] style Critical fill:#90EE90 style High fill:#90EE90 style Medium fill:#90EE90 style Low fill:#90EE90 ``` OWASP Top 10 sweep: all N/A. Diff touches only internal match-guard logic and tests; no new input paths, no auth/authz, no dependencies, no network, no serialization. ARP dead-code removal is structurally sound — `find()` predicate eliminates `Transport::LinkLayer` entries before the removed disjunct could be evaluated. Test assertion tightening improves coverage properties; no security checks weakened. --- ## Risk Assessment ### Blast Radius - **Systems affected:** None (internal-only changes) - **User impact:** None (no behavior change; doc-comment + dead-code + test hardening) - **Data impact:** None - **Risk Level:** LOW ### Performance Impact No performance impact. The removed ARP disjunct was dead code in a match guard; its removal has no runtime effect. No new hot paths introduced. ### Feature Flags None. No feature flags used or required. --- ## Traceability | Finding | BC | Commit | File | Status | |---------|----|----|------|--------| | STORY-153-RUNANALYZE-DOC-STALE-001 | BC-2.05.010 | `7fbb57c` | `src/main.rs:193` | RESOLVED | | STORY-154-LOOKUP-ARP-DEADCLAUSE-001 | BC-2.12.023 | `0fdaa29` | `src/main.rs:1063` | RESOLVED | | STORY-154-ALL-COVERAGEGAPS-TEST-001 | BC-2.12.023 Inv.1/PC-1 | `abc048e` | `tests/integration_tests.rs` | RESOLVED | | STORY-152/154-WEAK-UNKNOWN-ASSERT-001 | BC-2.12.024 PC-4/EC-009/EC-010 | `f90dfb8` | `tests/integration_tests.rs` | RESOLVED | --- ## AI Pipeline Metadata <details> <summary><strong>Pipeline Details</strong></summary> ```yaml ai-generated: true pipeline-mode: feature factory-version: "1.0.0-rc.21" pipeline-stages: fix-pr-delivery: in-progress demo-recording: skipped (no behavior change) wave-integration-gate: skipped (fix PR — merges individually) fix-source: F5 scoped-adversarial review (E-21 feature-protocol-coverage) findings-resolved: 4 findings-severity: LOW models-used: pr-manager: claude-sonnet-4-6 security-reviewer: dispatched pr-reviewer: dispatched generated-at: "2026-07-04T00:00:00Z" ``` </details> --- ## Pre-Merge Checklist - [ ] All CI status checks passing - [x] No restricted files changed (only src/main.rs, tests/integration_tests.rs) - [x] No behavior change — docs/dead-code/test-hardening only - [x] All 4 F5 adversarial findings addressed - [ ] Security review: PENDING - [ ] PR reviewer: PENDING - [x] No demo recording required (no user-observable behavior change) - [x] No wave integration gate (fix PR — merges individually per autonomy level 4)
…ion for E-21 delta (#357) # F6 Formal Hardening — VP-041/042/043 Kani + Fuzz + Mutation (E-21 Protocol-Coverage Delta) **Epic:** E-21 — Protocol Coverage Catalog & Gap Reporting (STORY-151/152/153/154) **Mode:** feature (F6 targeted-hardening layer) **Phase:** F6 — VERDICT: PASS     F6 targeted-hardening layer over the E-21 protocol-coverage delta (STORY-151/152/153/154). No production code was changed; this PR adds only `#[cfg(kani)]` harnesses, fuzz targets, and mutation-killing tests. The implementation has already passed TDD, holdout (mean 1.00), and F5 adversarial convergence. This PR formalises the mathematical and dynamic safety proofs. --- ## Architecture Changes ```mermaid graph TD protocols["src/protocols.rs\n(KNOWN_PROTOCOLS catalog)"] dispatcher["src/dispatcher.rs\n(gap accumulation)"] main["src/main.rs\n(collect_all_gaps / inject_json)"] protocols -->|VP-041 partition| kani_proofs["#[cfg(kani)]\nkani_proofs blocks"] dispatcher -->|VP-042 counters\nVP-043 udp_gap_key| kani_proofs main -->|mutation-kill tests| dispatcher_tests["tests/dispatcher_tests.rs"] fuzz["fuzz/fuzz_targets/\nfuzz_coverage_gap_classify.rs"] -->|exercises VP-041/042/043| dispatcher fuzz --> protocols fuzz --> main style kani_proofs fill:#90EE90 style fuzz fill:#90EE90 style dispatcher_tests fill:#90EE90 ``` **No production code changed.** All additions are `#[cfg(kani)]`-gated or in `tests/` / `fuzz/fuzz_targets/`. The diff is purely additive verification scaffolding. --- ## Story Dependencies ```mermaid graph LR S151["STORY-151\n(KNOWN_PROTOCOLS catalog)"] --> E21["E-21 F6\nthis PR"] S152["STORY-152\n(protocols subcommand)"] --> E21 S153["STORY-153\n(unclassified gap counters)"] --> E21 S154["STORY-154\n(--coverage-gaps flag)"] --> E21 style E21 fill:#FFD700 ``` All four E-21 stories are merged to develop (PRs #351-#355). This PR is the terminal F6 hardening step; no story is blocked by it. --- ## Spec Traceability ```mermaid flowchart LR VP041["VP-041\nKNOWN_PROTOCOLS partition\n(completeness + disjointness)"] VP042["VP-042\nDispatcher accumulation\n(counter safety, service-port key)"] VP043["VP-043\nudp_gap_key seam\n(gate + key + direction symmetry)"] VP041 -->|justified-deferred to proptest| proptest["designated proptests\n(re-verified green)"] VP041 -->|inline justification| deferral["CBMC intractable\nVec+&str >12min"] VP042 -->|Kani harnesses| vp042a["vp042_min_port_key_symmetric\n(F-F3P11-001)"] VP042 -->|Kani harnesses| vp042b["vp042_saturating_counter_monotonic\n(EC-153-10)"] VP042 -->|proptest designated| vp042prop["HashMap accumulation\n(RandomState/getrandom)"] VP043 -->|Kani harnesses| vp043a["vp043_udp_gap_key_gate_and_key"] VP043 -->|Kani harnesses| vp043b["vp043_udp_gap_key_direction_symmetric"] VP043 -->|Kani harnesses| vp043c["vp043_udp_gap_key_non_udp_none"] vp042a --> src_dispatcher["src/dispatcher.rs\n#[cfg(kani)] kani_proofs"] vp042b --> src_dispatcher vp043a --> src_dispatcher vp043b --> src_dispatcher vp043c --> src_dispatcher proptest --> src_protocols["src/protocols.rs\n#[cfg(kani)] kani_proofs"] ``` --- ## Test Evidence ### Coverage Summary | Metric | Value | Threshold | Status | |--------|-------|-----------|--------| | Full-tree `cargo test` | 85 test binaries, 0 failed | 0 failures | PASS | | Kani harnesses (new) | 5 harnesses, 0/103 checks failed | 0 failures | PASS | | VP-041 Kani deferral | justified (CBMC intractable) | documented | PASS | | Fuzz duration | 301 s, 2,049,292 execs | >= 300 s | PASS | | Fuzz crashes | 0 | 0 | PASS | | Delta mutation kill rate | 100% (34/34 viable) | >= 95% | PASS | | `cargo clippy -D warnings` | 0 warnings | 0 | PASS | | `cargo fmt --check` | clean | clean | PASS | | `cargo deny check` | advisories/bans/licenses/sources ok | all ok | PASS | ### Test Flow ```mermaid graph LR Regression["85 test binaries\n(cargo test --all-targets)"] Kani["5 Kani harnesses\n0/103 checks failed"] Fuzz["fuzz_coverage_gap_classify\n2,049,292 execs / 301s"] Mutants["cargo-mutants E-21 delta\n34/34 viable killed"] Regression -->|"0 failures"| Pass1["PASS"] Kani -->|"VERIFICATION: SUCCESSFUL"| Pass2["PASS"] Fuzz -->|"0 crashes"| Pass3["PASS"] Mutants -->|"100% kill rate"| Pass4["PASS"] style Pass1 fill:#90EE90 style Pass2 fill:#90EE90 style Pass3 fill:#90EE90 style Pass4 fill:#90EE90 ``` <details> <summary><strong>Detailed Test Results</strong></summary> ### New Tests Added (This PR) | File | Tests | Purpose | |------|-------|---------| | `src/dispatcher.rs` (`#[cfg(kani)]`) | `vp042_min_port_key_symmetric`, `vp042_saturating_counter_monotonic`, `vp043_udp_gap_key_gate_and_key`, `vp043_udp_gap_key_direction_symmetric`, `vp043_udp_gap_key_non_udp_none` | Kani formal proofs for VP-042 and VP-043 | | `src/protocols.rs` (`#[cfg(kani)]`) | VP-041 justified-deferral + inline rationale | Documents why catalog partition stays on proptest | | `fuzz/fuzz_targets/fuzz_coverage_gap_classify.rs` | `fuzz_coverage_gap_classify` | Dynamic safety fuzz target for VP-041/042/043 surfaces | | `tests/dispatcher_tests.rs` | `f6_collect_all_gaps_preserves_tcp_count`, `f6_inject_json_tcp_non102_*`, `f6_unclassified_counts_with_only_enip_analyzer`, `f6_unclassified_counts_with_only_dnp3_analyzer` | Mutation-killing tests for 3 E-21 delta survivors | | `src/main.rs` | `f6_collect_all_gaps_preserves_tcp_count`, `f6_inject_json_tcp_non102_name_no_collision` | Additional mutation killers for `collect_all_gaps` / `inject_coverage_gaps_json` | ### Kani Harnesses Detail | Harness | Property | BCs | Checks | Result | |---------|----------|-----|--------|--------| | `vp043_udp_gap_key_gate_and_key` | DNS gate (port 53) + min-port key correctness | BC-3.53.001 | 0/N failed | VERIFICATION: SUCCESSFUL | | `vp043_udp_gap_key_direction_symmetric` | Query/response bucket collapse — same key regardless of direction | BC-3.53.002 | 0/N failed | VERIFICATION: SUCCESSFUL | | `vp043_udp_gap_key_non_udp_none` | UDP-only seam exclusion — non-UDP returns None | BC-3.53.003 | 0/N failed | VERIFICATION: SUCCESSFUL | | `vp042_min_port_key_symmetric` | Service-port key symmetric (F-F3P11-001) | BC-3.52.001 | 0/N failed | VERIFICATION: SUCCESSFUL | | `vp042_saturating_counter_monotonic` | Saturating increment never panics, counter monotone (EC-153-10) | BC-3.52.002 | 0/N failed | VERIFICATION: SUCCESSFUL | **Aggregate: 5 harnesses, VERIFICATION: SUCCESSFUL, 0 of 103 checks failed.** ### VP-041 Kani Justified-Deferral VP-041 (KNOWN_PROTOCOLS catalog — completeness + disjointness of the `is_known_protocol` / `classify_protocol` partition) was explicitly evaluated for Kani tractability: - The property has **no symbolic input** — the catalog is a compile-time constant `&[(&str, &str)]`. BMC degenerates to a concrete execution already covered by the designated proptests. - Vec+&str modeling in CBMC was found **intractable**: a trial run exceeded 12 minutes without convergence. CBMC's string model incurs exponential blowup over string length for the 100+ catalog entries. - **Decision:** retain the designated proptests (already re-verified green at develop HEAD) as the proof obligation; document the Kani deferral inline in `src/protocols.rs` with full rationale. This matches the pattern used for VP-004 HashMap accumulation (documented inline in `src/dispatcher.rs`). ### Fuzz Target: `fuzz_coverage_gap_classify` The harness exercises three surfaces in a single run: 1. `classify_protocol(name)` exhaustiveness oracle (VP-041) 2. `CoverageGapDispatcher::on_data` + `on_flow_close` dual-gate paths (VP-042) 3. `udp_gap_key(src, dst, is_udp)` gate + key contract (VP-043) Run: 2,049,292 executions in 301s (~6,808 exec/s). 0 crashes, 0 OOM, 0 timeouts. No artifact written to `fuzz/artifacts/fuzz_coverage_gap_classify/`. ### Mutation Testing — E-21 Delta Scope: `--file src/protocols.rs --file src/dispatcher.rs --file src/main.rs` (delta functions only). All 3 genuine survivors pinned by new tests: | Survivor | Location | Mutation | Killing Test | |----------|----------|----------|--------------| | `collect_all_gaps +=/*=` | `main.rs:1088` | `*=` zeroes TCP count | `f6_collect_all_gaps_preserves_tcp_count` | | `inject_coverage_gaps_json &&/\|\|` | `main.rs:1224` | `\|\|` short-circuits name preservation | `f6_inject_json_tcp_non102_name_no_collision` | | `on_flow_close guard \|\|/&&` | `dispatcher.rs:464` | `&&` eliminates EtherNet/IP or DNP3 disjunct | `f6_unclassified_counts_with_only_{enip,dnp3}_analyzer` | **Post-fix delta kill rate: 100% (34/34 viable delta mutants).** Remaining 2 survivors (`run_analyze` ReassemblyConfig field deletions in `main.rs`) are pre-existing, out-of-delta `main()` integration-glue — LOW-tier, not delta-introduced. </details> --- ## Holdout Evaluation N/A — evaluated at wave gate (mean satisfaction 1.00 at F4/E-21 holdout). This PR contains only test/verification artifacts; no production paths changed. --- ## Adversarial Review N/A — evaluated at Phase F5. F5 adversarial convergence achieved with 0 remaining blocking findings. This PR is the F6 post-convergence hardening layer; adversarial review already passed for the E-21 implementation. --- ## Security Review ```mermaid graph LR Critical["Critical: 0"] High["High: 0"] Medium["Medium: 0"] Low["Low: 0"] style Critical fill:#90EE90 style High fill:#90EE90 style Medium fill:#90EE90 style Low fill:#90EE90 ``` <details> <summary><strong>Security Scan Details (pre-PR, develop HEAD 3727578)</strong></summary> ### Dependency Audit - `cargo audit`: exit 0 — no vulnerabilities - `cargo deny check`: advisories ok, bans ok, licenses ok, sources ok (exit 0) ### Code Surface This PR adds only: - `#[cfg(kani)]`-gated harnesses (dead in normal builds, not in binary) - `fuzz/fuzz_targets/` (separate binary crate, not in production build) - Integration tests in `tests/` (test binary only, `#[cfg(test)]`-equivalent) - `fuzz/Cargo.lock` bump (no new dependency added) No new production code surface. No new unsafe blocks. No new dependencies introduced to the main crate. Attack surface delta: zero. </details> --- ## Risk Assessment & Deployment ### Blast Radius - **Systems affected:** None — test-only additions, no production binary change - **User impact:** None — no production behavior change - **Data impact:** None - **Risk Level:** MINIMAL (test/verification artifacts only) ### Performance Impact | Metric | Before | After | Delta | |--------|--------|-------|-------| | Production binary | unchanged | unchanged | 0 | | CI time | baseline | +fuzz-build gate | minor | `#[cfg(kani)]` harnesses compile only under `cargo kani`, not `cargo build`. Fuzz targets compile only under `cargo +nightly fuzz build`. --- ## Traceability | VP | BC | Method | Harness / Test | Status | |----|-----|--------|----------------|--------| | VP-041 | BC-3.51.001 (partition completeness) | proptest (re-verified) + Kani justified-deferred | designated proptests green | PROVEN | | VP-042 | BC-3.52.001 (service-port key) | Kani | `vp042_min_port_key_symmetric` | PROVEN | | VP-042 | BC-3.52.002 (counter monotone/no-panic) | Kani | `vp042_saturating_counter_monotonic` | PROVEN | | VP-043 | BC-3.53.001 (DNS gate + key correctness) | Kani | `vp043_udp_gap_key_gate_and_key` | PROVEN | | VP-043 | BC-3.53.002 (direction symmetry) | Kani | `vp043_udp_gap_key_direction_symmetric` | PROVEN | | VP-043 | BC-3.53.003 (UDP-only exclusion) | Kani | `vp043_udp_gap_key_non_udp_none` | PROVEN | | E-21 delta | mutation | cargo-mutants | 3 new tests in dispatcher_tests.rs + main.rs | 100% kill | | E-21 delta | fuzz | libFuzzer | `fuzz_coverage_gap_classify` 2.05M execs | 0 crashes | --- ## AI Pipeline Metadata <details> <summary><strong>Pipeline Details</strong></summary> ```yaml ai-generated: true pipeline-mode: feature phase: F6-targeted-hardening factory-version: "1.0.0-rc.21" pipeline-stages: spec-crystallization: completed (E-21) story-decomposition: completed (STORY-151..154) tdd-implementation: completed (PRs #351-#355) holdout-evaluation: completed (mean 1.00) adversarial-review: completed (F5 CONVERGED) formal-verification: completed (this PR) convergence: achieved f6-verdict: PASS f6-hardening-scope: E-21 delta (protocols.rs / dispatcher.rs / main.rs) f6-kani-harnesses: 5 f6-kani-checks: "0/103 failed" f6-fuzz-execs: 2049292 f6-fuzz-duration-s: 301 f6-mutation-delta-kill-rate: "100% (34/34)" models-used: builder: claude-sonnet-4-6 generated-at: "2026-07-04T00:00:00Z" ``` </details> --- ## Pre-Merge Checklist - [ ] All CI status checks passing (test, clippy, fmt, deny, audit, fuzz-build, semantic-PR, action-pin-gate) - [ ] 0 critical/high security findings - [ ] Kani proofs verified (VERIFICATION: SUCCESSFUL, 0 checks failed) - [ ] Fuzz run clean (0 crashes, >= 300s) - [ ] Delta mutation kill rate 100% - [ ] No production code changed (test/proof/fuzz artifacts only) - [ ] Branch deletion verified post-merge
[STORY-154]
--coverage-gapsOpt-In Flag +CoverageGapsSummaryTri-State Report + Mandatory Caveats (BC-2.12.023 + BC-2.12.024)Epic: E-21 — feature-protocol-coverage (FINAL F4 story, Wave 69)
Mode: feature
Convergence: CONVERGED after 3 consecutive fresh-context adversarial passes on a5f8e52 (0 P0/CRITICAL/HIGH/mis-anchor)
This PR delivers the final story of the E-21 feature-protocol-coverage feature wave. It adds an
opt-in
--coverage-gapsCLI flag to theanalyzesubcommand that causes wirerust to track andreport unclassified TCP flows and UDP packets by (transport, port), presented as a
CoverageGapsSummarynamed section with tri-state classification(
known-unsupported/unknown/known-supported), a mandatory L2/multicast limitationcaveat that is always present, and a conditional port-102 collision footnote naming all four
protocols (S7comm, S7comm-plus, IEC 61850 MMS, ICCP/TASE.2) that share TCP/102 via
ISO-on-TCP/TPKT. The design is purely additive —
--coverage-gapsis independent of--all; all existingFindingentries andAnalysisSummaryoutput are unchanged.The PR diff is exactly 8 files:
src/cli.rs(+4),src/main.rs(+438),tests/integration_tests.rs(+827), and 5 crafted pcap fixtures in
tests/fixtures/required by the integration tests(these are legitimate committed test data, not demo artifacts).
Architecture Changes
graph TD CLI["src/cli.rs\nAnalyze struct\n+coverage_gaps: bool"] MAIN["src/main.rs\nrun_analyze()"] DISP["src/dispatcher.rs\nStreamDispatcher\n(STORY-153 builder)"] PROTO["src/protocols.rs\nKNOWN_PROTOCOLS catalog\n(STORY-151)"] ENUM["ProtocolGapState\n{KnownUnsupported,Unknown,KnownSupported}"] LOOKUP["lookup_protocol_state()\ntransport-aware pure fn"] RENDER["render_coverage_gaps_summary()\neffectful stdout fn"] JSON["render_coverage_gaps_summary_json()\nJSON output path"] CONST_L2["L2_CAVEAT_TEXT const\nalways-present caveat"] CONST_102["PORT_102_NOTE const\nconditional collision footnote"] CLI -->|coverage_gaps: bool| MAIN MAIN -->|with_coverage_gaps(bool)| DISP MAIN --> LOOKUP LOOKUP -->|catalog lookup| PROTO LOOKUP -->|tri-state result| ENUM MAIN --> RENDER MAIN --> JSON RENDER --> CONST_L2 RENDER --> CONST_102 style CLI fill:#90EE90 style ENUM fill:#90EE90 style LOOKUP fill:#90EE90 style RENDER fill:#90EE90 style JSON fill:#90EE90 style CONST_L2 fill:#90EE90 style CONST_102 fill:#90EE90Architecture Decision Record — ADR-012 (relevant decisions)
ADR-012: Protocol Coverage Catalog — Coverage Gap Report Decisions
Decision 2 (Tri-state classification): Each gap entry is classified using a Suricata-derived vocabulary:
known-unsupported(catalog match but not dissected),unknown(no catalog match),known-supported(catalog match AND dissected — BUG signal). Classification is transport-aware:(Tcp, 47808)isunknowneven though BACnet/IP is catalogued, because BACnet isUdponly. LinkLayer entries never match port-keyed lookups.Decision 3a (L2 caveat): The L2/multicast caveat is always present — L2 protocols have no TCP/UDP port and are structurally absent from the gap report. The constant is not configurable.
Decision 3b (Port-102 footnote): The TCP/102 collision footnote (naming S7comm, S7comm-plus, IEC 61850 MMS, ICCP/TASE.2) is row-specific and conditional on a non-zero count for
(Tcp, 102).Decision 8 (
--coverage-gapsopt-in): The flag is NOT in the--allexpansion group. The two flags are independent.--allselects all analyzers;--coverage-gapsenables gap detection.Decision 9 (Named section after findings):
CoverageGapsSummaryis appended after allFindingentries. It is NOT aFindingitself (that would pollute the MITRE-severity pipeline with infrastructure data).Consequences:
classify()Rule 5 always routes TCP/502 toDispatchTarget::Modbus—(Tcp, 502)can never appear in the gap report via the analyze pipeline.known-supportedis assertable only at the pure-function/unit level.with_coverage_gaps(bool)builder (from STORY-153) preserves all existingStreamDispatcher::new()call sites with zero blast radius.Story Dependencies
graph LR S151["STORY-151\n✅ MERGED\nKNOWN_PROTOCOLS catalog"] S152["STORY-152\n✅ MERGED\nprotocols subcommand"] S153["STORY-153\n✅ MERGED\nunclassified counters\n+ with_coverage_gaps builder"] S154["STORY-154\n🟡 this PR\n--coverage-gaps flag\n+ CoverageGapsSummary"] S151 --> S154 S152 --> S154 S153 --> S154 style S154 fill:#FFD700 style S151 fill:#90EE90 style S152 fill:#90EE90 style S153 fill:#90EE90All three dependency PRs (STORY-151, STORY-152, STORY-153) are merged into
develop.STORY-154 blocks nothing — it is the final story in Wave 69 / E-21.
Spec Traceability
flowchart LR BC023["BC-2.12.023 v1.2\n--coverage-gaps opt-in\nCoverageGapsSummary\nJSON schema"] BC024["BC-2.12.024 v1.1\nMandatory caveats\nL2/multicast limitation\nPort-102 collision\nTri-state classification"] ADR012["ADR-012\nDecisions 2,3a,3b,8,9"] BC023 --> AC001["AC-154-001\n--coverage-gaps flag\nnot in --all group"] BC023 --> AC002["AC-154-002\nwiring via\nwith_coverage_gaps builder"] BC023 --> AC003["AC-154-003\nCoverageGapsSummary\nappended ONLY when set"] BC024 --> AC004["AC-154-004\nMandatory L2 caveat\nalways present"] BC024 --> AC005["AC-154-005\nPort-102 collision\nfootnote conditional"] BC024 --> AC006["AC-154-006\nTri-state classification\ntransport-aware"] BC023 --> AC007["AC-154-007\nJSON schema:\ncoverage_gaps object"] BC024 --> AC008["AC-154-008\nExit 0; purely additive\nexisting output unchanged"] AC001 --> T001["test_BC_2_12_023_all_without_coverage_gaps\ntest_BC_2_12_023_protocols_coverage_gaps_error"] AC002 --> T002["test_BC_2_12_023_coverage_gaps_counts_unclassified"] AC003 --> T003["test_BC_2_12_023_coverage_gaps_flag_produces_section\ntest_BC_2_12_023_no_coverage_gaps_no_section\ntest_BC_2_12_023_coverage_gaps_purely_additive"] AC004 --> T004["test_BC_2_12_024_l2_caveat_always_present\ntest_BC_2_12_024_empty_entries_message"] AC005 --> T005["test_BC_2_12_024_port102_footnote_on_tcp102_traffic\ntest_BC_2_12_024_port102_footnote_absent_without_tcp102\ntest_BC_2_12_024_port102_note_names_all_four"] AC006 --> T006["test_BC_2_12_024_bacnet_known_unsupported\ntest_BC_2_12_024_unknown_port_state\ntest_BC_2_12_024_tcp_47808_is_unknown\ntest_BC_2_12_024_tcp_502_absent_from_gap_report"] AC007 --> T007["test_BC_2_12_023_json_coverage_gaps_key\ntest_BC_2_12_024_json_has_caveat_field\ntest_BC_2_12_024_json_entry_bacnet_schema\ntest_BC_2_12_024_json_entry_port102_collision_note\ntest_BC_2_12_024_json_entry_unknown_state"] AC008 --> T008["test_BC_2_12_023_coverage_gaps_purely_additive"] T001 --> SRC["src/cli.rs\nsrc/main.rs\ntests/integration_tests.rs"] T002 --> SRC T003 --> SRC T004 --> SRC T005 --> SRC T006 --> SRC T007 --> SRC T008 --> SRCTest Evidence
Coverage Summary
Test Flow
graph LR Integration["21 Integration Tests\nmod story_154\n(tests/integration_tests.rs)"] Unit["4 Unit Tests\nmod story_154_unit\n(src/main.rs inline)"] Fmt["cargo fmt --check"] Clippy["cargo clippy -D warnings"] AllTargets["cargo test --all-targets\nfull regression"] Integration -->|CLI-reachable paths| Pass1["PASS"] Unit -->|lookup_protocol_state()\nbinary-private pure fn| Pass2["PASS"] Fmt --> Pass3["CLEAN"] Clippy --> Pass4["CLEAN"] AllTargets --> Pass5["ALL GREEN"] style Pass1 fill:#90EE90 style Pass2 fill:#90EE90 style Pass3 fill:#90EE90 style Pass4 fill:#90EE90 style Pass5 fill:#90EE90Test Function List (STORY-154)
Integration tests —
mod story_154intests/integration_tests.rstest_BC_2_12_023_all_without_coverage_gapstest_BC_2_12_023_protocols_coverage_gaps_errortest_BC_2_12_023_coverage_gaps_counts_unclassifiedtest_BC_2_12_023_coverage_gaps_flag_produces_sectiontest_BC_2_12_023_no_coverage_gaps_no_sectiontest_BC_2_12_024_l2_caveat_always_presenttest_BC_2_12_024_empty_entries_messagetest_BC_2_12_024_port102_footnote_on_tcp102_traffictest_BC_2_12_024_port102_footnote_absent_without_tcp102test_BC_2_12_024_port102_note_names_all_fourtest_BC_2_12_024_bacnet_known_unsupportedtest_BC_2_12_024_unknown_port_statetest_BC_2_12_024_tcp_47808_is_unknowntest_BC_2_12_024_tcp_502_absent_from_gap_reporttest_BC_2_12_024_json_has_caveat_fieldtest_BC_2_12_023_json_coverage_gaps_keytest_BC_2_12_024_json_entry_bacnet_schematest_BC_2_12_024_json_entry_port102_collision_notetest_BC_2_12_024_json_entry_unknown_statetest_BC_2_12_023_coverage_gaps_purely_additivetest_BC_2_12_024_tcp_53_is_unknownUnit tests —
mod story_154_unitinline insrc/main.rstest_BC_2_12_024_bacnet_known_unsupported_unittest_BC_2_12_024_tcp_47808_is_unknown_unittest_BC_2_12_024_unknown_port_state_unittest_BC_2_12_024_known_supported_is_bug_signal_unitDemo Evidence
Visual evidence is at
docs/demo-evidence/STORY-154/on this branch (5 per-AC VHS GIF+WebM recordings +evidence-report.md). These files are intentionally UNTRACKED — the PR diff contains only the 8 code and fixture files listed in the title.--allopt-in independence +--coverage-gapssection)docs/demo-evidence/STORY-154/ac-154-001-opt-in-all.gifdocs/demo-evidence/STORY-154/ac-154-006-bacnet-known-unsupported.gifdocs/demo-evidence/STORY-154/ac-154-005-tcp102-collision.gifdocs/demo-evidence/STORY-154/ac-154-006-tcp9600-unknown.gifcoverage_gapsobject schema)docs/demo-evidence/STORY-154/ac-154-007-json.gifHoldout Evaluation
N/A — evaluated at wave gate (Wave 69 / E-21 feature-protocol-coverage).
Adversarial Review
Convergence: 3 consecutive fresh-context clean adversarial passes on a5f8e52. 0 P0/CRITICAL/HIGH/mis-anchor. Canonical values (BACnet/IP UDP/47808, TCP/102 four-protocol collision) independently verified per DF-CANONICAL-FRAME-HOLDOUT-001. Tri-state transport-awareness,
can_decodehoist, render name re-lookup, and help-provenance all verified clean.Deferred non-blocking LOW items (tracked in STATE.md; not merge-blockers):
STORY-154-ALL-COVERAGEGAPS-TEST-001:--all --coverage-gapscombined test not presentTESTCOUNT-COMMENT: comment count inconsistency in test fileWEAK-UNKNOWN-ASSERT: some unknown-state assertions are weakBC-2.12.024-PC4-PHANTOM-SUPPORTED-001→ deferred to phase-5 adversarialSecurity Review
Pending dispatch in step 4 — will be updated after security-reviewer completes.
graph LR Critical["Critical: pending"] High["High: pending"] Medium["Medium: pending"] Low["Low: pending"]Risk Assessment & Deployment
Blast Radius
src/cli.rs(Analyze struct only —protocolssubcommand untouched),src/main.rs(additive: new types, new functions, new call-site wiring; no existing code paths changed),tests/integration_tests.rs(new test module appended)--coverage-gapsmust be explicitly set). Users NOT using--coverage-gapssee zero behavioral change.analyze --allbehavior is byte-identical to pre-feature.CoverageGapsSummaryis a transient stdout/JSON output — it does not modify any file, database, or configuration.Performance Impact
analyzewithout--coverage-gapsanalyze --coverage-gapsunclassified_port_counts+udp_unclassified_countsonly when flag setRollback Instructions
Immediate rollback (< 2 min):
This PR has no feature flags and no database migrations. Rollback is a single revert commit.
The flag is opt-in — users not passing
--coverage-gapsare completely unaffected before rollback.Verification after rollback:
cargo buildcompiles withoutcoverage_gapsfield inAnalyzecargo test --all-targetspasses (story_154 tests will be gone)wirerust analyze <pcap>output identical to pre-feature behaviorFeature Flags
N/A —
--coverage-gapsis itself a CLI flag (opt-in). No code-level feature flags.Traceability
test_BC_2_12_023_protocols_coverage_gaps_errortest_BC_2_12_023_all_without_coverage_gapstest_BC_2_12_023_json_coverage_gaps_keytest_BC_2_12_023_all_without_coverage_gapstest_BC_2_12_023_coverage_gaps_purely_additivetest_BC_2_12_023_coverage_gaps_purely_additivetest_BC_2_12_024_l2_caveat_always_presenttest_BC_2_12_024_port102_footnote_on_tcp102_traffictest_BC_2_12_024_port102_footnote_absent_without_tcp102test_BC_2_12_024_tcp_47808_is_unknowntest_BC_2_12_024_json_entry_bacnet_schematest_BC_2_12_024_l2_caveat_always_presenttest_BC_2_12_024_port102_footnote_absent_without_tcp102test_BC_2_12_024_l2_caveat_always_presenttest_BC_2_12_024_bacnet_known_unsupported+ unit teststest_BC_2_12_023_all_without_coverage_gapstest_BC_2_12_023_coverage_gaps_purely_additivetest_BC_2_12_024_bacnet_known_unsupported+_unittest_BC_2_12_024_port102_note_names_all_fourtest_BC_2_12_024_tcp_502_absent_from_gap_reporttest_BC_2_12_024_tcp_47808_is_unknowntest_BC_2_12_024_tcp_53_is_unknownFull VSDD Contract Chain
Note on Test Fixtures
The 5 files in
tests/fixtures/gap-*.pcapare crafted minimal pcap files required by theintegration tests — they are legitimate committed test data, not demo artifacts:
gap-tcp102.pcap— minimal pcap with a TCP/102 flow (triggers port-102 footnote)gap-tcp47808.pcap— minimal pcap with a TCP/47808 flow (transport-mismatch → unknown)gap-tcp53.pcap— minimal pcap with a TCP/53 flow (DNS is UDP-only → unknown)gap-tcp9600.pcap— minimal pcap with a TCP/9600 flow (no catalog match → unknown)gap-udp47808.pcap— minimal pcap with a UDP/47808 flow (BACnet/IP → known-unsupported)Demo evidence (VHS GIF+WebM recordings) is at
docs/demo-evidence/STORY-154/and isintentionally UNTRACKED — it does NOT appear in this PR's diff.
AI Pipeline Metadata
Pipeline Details
Pre-Merge Checklist
src/cli.rs(+4),src/main.rs(+438),tests/integration_tests.rs(+827), + 5 pcap fixturesgit revert(no migrations, no flags)