Skip to content

Ported DCQCN checker onto TraceSpec with mechanized replay soundness#98

Merged
baochunli merged 2 commits into
mainfrom
dcqcn-tracespec
Jul 4, 2026
Merged

Ported DCQCN checker onto TraceSpec with mechanized replay soundness#98
baochunli merged 2 commits into
mainfrom
dcqcn-tracespec

Conversation

@baochunli

Copy link
Copy Markdown
Collaborator

Summary

Ports the DCQCN checker from its hand-rolled recursive replay loop onto the generic TraceSpec interface, matching the existing AQM and PFC structure, with byte-identical observable behavior. Follow-up to #97, which mechanized the generic replay lemmas.

DCQCN is the flagship protocol of the LeanGuard paper (largest benchmark trace, generation campaign, schema examples), so bringing it inside the mechanized scope directly strengthens the paper's answer to the SIGCOMM reviewers' mechanization question.

Changes

  • lean/LeanGuard/DcqcnEventLog.lean (only file changed, +73/−25):
    • New structure ReplayState bundling the loop-threaded state (Global plus lastKey), so the in-loop key-monotonicity re-check is preserved verbatim and becomes part of the mechanized transition relation.
    • stepRow : ReplayState → Row → Except String ReplayState extracted from the old go loop body; per-row order (covTick → recordCover → key check → step) and error precedence preserved exactly.
    • traceSpec : TraceSpec instance and observeCoverage observer, driven by TraceSpec.replayWithObserverM.
    • Three instantiation theorems mirroring the AQM/PFC analogues at equal strength: replayCanonicalRowsWithCoverage_sound, replayCanonicalRows_preserves, checkRowsWithCoverage_sound. No sorry/admit/new axioms; #print axioms shows only propext, Classical.choice, Quot.sound.

Verification

  • All seven checker executables build (lake build).
  • Byte-identical regression: real traces generated from configs/dcqcn_simple.toml, dcqcn_1s.toml, dcqcn_multi.toml plus six corrupted variants (alpha snapshot mismatch, duplicate canonical key, missing required field, unmatched cnp_recv, interval-gate violation, frozen-parameter change) and a shuffled-rows ACCEPT case; stdout, stderr, exit codes, and --coverage-out JSON compared before vs. after — empty diff across all 13 traces, including aqm_dcqcn_check cross-layer runs.
  • An independent review agent regenerated both sides of the baseline from scratch (rebuilding main and the branch) and confirmed byte-identical outputs, audited the theorem statements against the AQM/PFC analogues for weakening (none), and verified the stepRow semantics against the old loop.
  • Fixture scripts pass: run-aqm-fixtures.sh (38), run-aqm-coverage-fixtures.sh (8), run-cubic-fixtures.sh (5).
  • Public API unchanged: checkRowsWithCoverage, checkRows, step, parseCsv, Row/Kind/Ecn; AqmDcqcnMain.lean needed no edits.
  • The native_decide canonicalization example still compiles.

🤖 Generated with Claude Code

baochunli and others added 2 commits July 3, 2026 06:06
Replace the hand-rolled recursive replay loop in checkRowsWithCoverage
with the mechanized TraceSpec instance used by the AQM and PFC checkers:

- ReplayState bundles the per-endpoint source/sink maps and pending CNP
  map (Global) with the last canonical key, so the in-loop global key
  monotonicity re-check ("global key went backwards") is preserved
  verbatim inside the new stepRow transition function.
- stepRow / traceSpec / observeCoverage / replayCanonicalRowsWithCoverage
  mirror the AqmEventLog structure; coverage recording moves into the
  replayWithObserverM observer, ticking and hitting the same coverpoints
  in the same order.
- Instantiation theorems replayCanonicalRowsWithCoverage_sound,
  replayCanonicalRows_preserves, and checkRowsWithCoverage_sound are
  near-verbatim applications of the generic TraceSpec lemmas.

Observable behavior is byte-identical: stdout, stderr, exit codes, and
--coverage-out JSON match the previous binaries on three simulator-
generated traces (dcqcn_simple, dcqcn_1s, dcqcn_multi), a shuffled-rows
accept case, six corrupted REJECT variants, and the aqm_dcqcn_check
cross-layer runs over the same traces.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add replayWithObserverM_agrees to the generic TraceSpec interface: for
any spec, observer, initial state, rows, and coverage state,

  Except.mapError Prod.fst
      ((replayWithObserverM spec observe s rows cov).map Prod.fst) =
    replayM spec s rows

i.e. instrumented replay and plain replay agree on the verdict, the
final replay state, and the error message; the observer only threads
the coverage component. This upgrades the one-directional
replayWithObserverM_sound story to full verdict equivalence. Proof is
by induction on rows; #print axioms reports [propext, Quot.sound].

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@baochunli

Copy link
Copy Markdown
Collaborator Author

Added commit b911225: a generic kernel-checked lemma replayWithObserverM_agrees proving that attaching a coverage observer changes neither the replay verdict, the final state, nor the error message (a single equation over Except, axioms propext/Quot.sound only). This backs the paper's Section III-D claim of coverage-observer non-interference. Independently reviewed; all seven checkers rebuild and the byte-identical baseline still holds.

@baochunli baochunli changed the title feat(leanguard): port DCQCN checker onto TraceSpec with mechanized replay soundness Ported DCQCN checker onto TraceSpec with mechanized replay soundness Jul 4, 2026
@baochunli
baochunli merged commit 5ef01ef into main Jul 4, 2026
8 checks passed
@baochunli
baochunli deleted the dcqcn-tracespec branch July 4, 2026 02:53
baochunli added a commit that referenced this pull request Jul 4, 2026
…ace. (#99)

* Port CUBIC checker onto the generic TraceSpec interface

Replace the hand-rolled checkRowsWithCoverage replay loop with a ReplayState wrapper and a TraceSpec instantiation, mirroring the DCQCN port (#98). The existing step/recordCover/key/canonicalizeRows semantics are reused unchanged, and the per-row global-key monotonicity check is preserved inside stepRow. Soundness and preservation lemmas delegate to the generic TraceSpec proofs, so no new proof obligations are introduced; the build elaborates them.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Port DRR checker onto the generic TraceSpec interface

Replace the checkRowsWithCoverage replay loop with a ReplayState wrapper and a TraceSpec instantiation, following the DCQCN template. step is fed via toEvent and recordCover reads the pre-step Global, exactly as before; the global-key monotonicity check moves into stepRow. Soundness/preservation lemmas delegate to the generic TraceSpec proofs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Port WFQ checker onto the generic TraceSpec interface

Replace the checkRowsWithCoverage replay loop with a ReplayState wrapper and a TraceSpec instantiation. WFQ's coverage observer previously threaded a distinct-class accumulator (seenClasses) that lived outside both Global and CoverageState. To keep observeCoverage self-contained and uniform with the other five checkers, thread it through a generic 'seen : Std.HashSet Nat' scratch field added to the shared CoverageState (with covSeen and a covMerge union). The field is never serialized, so coverage reports and golden fixtures are unchanged. class_count_ge_2 now gates on cov.seen.size >= 2, equivalent to the old distinct-list length; the now-dead listContains helper is removed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant