(scratch) test: track RFC 4787, and state its requirements as executable contracts - #1741
Draft
daniel-noland wants to merge 220 commits into
Draft
(scratch) test: track RFC 4787, and state its requirements as executable contracts#1741daniel-noland wants to merge 220 commits into
daniel-noland wants to merge 220 commits into
Conversation
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Comment |
❌ 2 Tests Failed:
View the top 2 failed test(s) by shortest run time
To view more test analytics, go to the Test Analytics Dashboard |
daniel-noland
force-pushed
the
pr/daniel-noland/spec-compliance
branch
from
August 20, 2026 02:32
cd0205c to
604bbfe
Compare
Cachix pays for itself only on outputs that are slow to build and reusable across revisions. Per-revision workspace and image outputs are neither, so later commits mark them volatile and skip their substitute lookups. The wrapping is separated from the decisions that use it because nixfmt reindents an entire binding when its expression is parenthesised, so folding these eight sites into the commits that need them buries roughly 630 lines of pure reindentation in four otherwise small diffs. `source-volatile` is introduced and applied here in one place; every commit that follows is legible without `git show -w`. Co-authored-by: Codex <codex@openai.com> Signed-off-by: Daniel Noland <daniel@githedgehog.com>
Per-revision workspace and image outputs consume cache transfer and storage while rarely substituting, crowding out slower native dependencies that are reusable. Mark source-volatile outputs consistently, skip their substitute lookups, and exclude OCI assembly paths that could pull them back through a closure. Co-authored-by: Codex <codex@openai.com> Signed-off-by: Daniel Noland <daniel@githedgehog.com>
Workspace derivations remain identical across workflow-only edits, reruns, merge-queue runs, and post-merge pushes. Marking them source-volatile forced those runs to rebuild the workspace despite unchanged inputs. Keep the marker on per-revision image assembly, but let identical workspace builds substitute. Different flags and sysroots still produce distinct store paths. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-authored-by: Codex <codex@openai.com> Signed-off-by: Daniel Noland <daniel@githedgehog.com>
`just build` creates gitignored out-links under `results`, but `lib.cleanSource` does not honor gitignore. Their store-path targets gave local builds a source hash different from CI. Exclude the directory so a previous local build cannot prevent reuse of otherwise identical cached outputs. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-authored-by: Codex <codex@openai.com> Signed-off-by: Daniel Noland <daniel@githedgehog.com>
Every crane derivation opted out of dependency artifacts, so each package rebuilt hundreds of third-party crates and the standard library whenever workspace source changed. Build shared production and test dependency artifacts instead. Separate flag sets preserve Cargo fingerprints, while the platform-aware package list avoids pulling excluded, WASI-incompatible dependencies into cross builds. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-authored-by: Codex <codex@openai.com> Signed-off-by: Daniel Noland <daniel@githedgehog.com>
Embedding the source store path in RUSTFLAGS changed the compilation identity of every dependency whenever workspace source changed, defeating the shared dependency build. Use a stable relative prefix instead. The Bolero fix makes an absolute path unnecessary and avoids the global source symlink that raced between worktrees. Resolve that prefix against the worktree for coverage and filter reports to workspace sources; otherwise llvm-cov silently includes the standard library and native dependencies. Co-authored-by: Codex <codex@openai.com> Signed-off-by: Daniel Noland <daniel@githedgehog.com>
A queued merge blocks everything behind it, so deep runs should finish faster even when that temporarily uses more of the shared lab pool. Run merge-queue and push matrices four entries at a time while pull requests remain serial and cannot crowd the queue out. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-authored-by: Codex <codex@openai.com> Signed-off-by: Daniel Noland <daniel@githedgehog.com>
The ci:-vlab label remained in use after VLAB became opt-in, but the workflow no longer read it. That left no way to request all deep checks except the lab matrix. Honor the subtractive label for the entire VLAB matrix, including merge-ready runs, just as ci:-upgrade is honored. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-authored-by: Codex <codex@openai.com> Signed-off-by: Daniel Noland <daniel@githedgehog.com>
Transient registry errors occasionally discard a push after the expensive build has completed. Let skopeo retry individual blobs, then retry whole idempotent skopeo and oras pushes only for known recoverable transport and status errors. Stream output and announce retries so degradation remains visible. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-authored-by: Codex <codex@openai.com> Signed-off-by: Daniel Noland <daniel@githedgehog.com>
The git-derived VERSION changed the shared dependency derivation on every commit even though third-party crates and the standard library do not consume it. Use a constant version for dependency builds while preserving the real value for workspace consumers, keeping artifacts reusable across revisions. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-authored-by: Codex <codex@openai.com> Signed-off-by: Daniel Noland <daniel@githedgehog.com>
The shared dependency build is only worth having if it is actually reused. Twice now something per-commit has leaked into it -- once the workspace source path, once the git version string -- and both times the build still succeeded. The only symptom was a slow cache miss, which nobody notices. Add a CI check for each leak, using the cheapest method that can see it. The workspace source is a store path, so "does the dependency build depend on it?" is a question about the derivation graph. Instantiate the derivation once and look at its inputs. That answer is exact, and it names the offending path and derivation rather than reporting only that some hash moved. It also avoids the alternative, which is to edit a tracked file, run the build, and restore the file from a shell trap. The git version is a different shape of problem: it reaches the derivation as an environment variable and never as an input path, so no graph walk can see it. That one needs two instantiations and a comparison. Both flag sets, production and test, are checked independently. `src` is exported so the graph question can be asked from outside. The check reports through the function's exit status, so a detected mismatch cannot be swallowed by a command-substitution subshell. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-authored-by: Codex <codex@openai.com> Signed-off-by: Daniel Noland <daniel@githedgehog.com>
Clippy and doctests bypassed Nix and rebuilt dependencies in empty runner workspaces, while the existing documentation derivation was unused and had silently rotted. Run all three through Nix so they share dependency artifacts and match local CI entry points. Keep clippy on all targets, execute doctests in the sandbox, and supply rustdoc with the same cfg declarations as rustc. Activating those paths exposed broken documentation links, an unpatched test-runner shebang, and source filters whose escaped regexes matched unrelated files; repair those prerequisites as part of making the checks real. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-authored-by: Codex <codex@openai.com> Signed-off-by: Daniel Noland <daniel@githedgehog.com>
Forty-one per-package clippy derivations all invalidated together because they shared the workspace source, yet each paid the fixed cost of unpacking dependency artifacts. Lint the platform-aware workspace package list in one derivation while retaining per-package targets for focused local use. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-authored-by: Codex <codex@openai.com> Signed-off-by: Daniel Noland <daniel@githedgehog.com>
The lab cgroup provides ten cores, but jobs used only eight because containers cannot discover that limit reliably. Give large derivations all ten cores. Split test_each into two five-core jobs because its many small package derivations cannot saturate the budget serially. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-authored-by: Codex <codex@openai.com> Signed-off-by: Daniel Noland <daniel@githedgehog.com>
Coverage instrumentation made one counter-heavy concurrency test dominate the suite even though more iterations reached no additional lines. The fuzz check also repeated optimized compilation already covered elsewhere. Reduce that test only under coverage instrumentation and drop fuzz from the ordinary check matrix; sanitizers and fuzz-specific jobs retain the heavier exercise. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-authored-by: Codex <codex@openai.com> Signed-off-by: Daniel Noland <daniel@githedgehog.com>
The workflow honors ci:-vlab, but its README listed ci:-upgrade as the only subtractive label. Document the available opt-out. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> Co-authored-by: Codex <codex@openai.com> Signed-off-by: Daniel Noland <daniel@githedgehog.com>
The root formatting recipe covers Rust but not Nix, allowing default.nix to drift unnoticed during this stack. Format it and add a focused nixfmt check. Older unformatted files under nix remain outside the check to avoid unrelated churn. Co-authored-by: Codex <codex@openai.com> Signed-off-by: Daniel Noland <daniel@githedgehog.com>
The source filter admitted all Markdown and JSON, so editing prose or a gitignored editor configuration changed every workspace derivation even though builds read none of it. Allow only Markdown included by crate documentation and remove unused JSON and Just filters. Omitting a real include now fails loudly at compile time, while unrelated files no longer defeat substitution. Co-authored-by: Codex <codex@openai.com> Signed-off-by: Daniel Noland <daniel@githedgehog.com>
Adding nixfmt to `just lint` did not add it to the workflow, whose lint steps and failure aggregation are maintained separately. Either list can drift silently and leave a check unenforced. Run nixfmt in CI and add a guard that keeps the recipe dependencies, workflow steps, and aggregated outcomes aligned. Co-authored-by: Codex <codex@openai.com> Signed-off-by: Daniel Noland <daniel@githedgehog.com>
Several container derivations lacked the source-volatile marker, allowing per-revision images and dockerTools assembly paths to reach Cachix. Mark every image and check their realized closures against the actual push filter. Match dockerTools artifacts by shape so newly added or nested images are covered without another name list. Keep the current denylist self-checking; converting cache uploads to a stricter allowlist remains a separate behavioral change. Co-authored-by: Codex <codex@openai.com> Signed-off-by: Daniel Noland <daniel@githedgehog.com>
We recently introduced a flag FlowInfoFlags::INITIATOR to mark, in a flow pair, which flow initiated the connection. In flow-filter's tests, the helper function attach_flow() does not account for this, and always create the flow as a forward flow initiating the connection. This is not currently an issue, because flow-filter's code does not leverage this flag yet, but it will be. In preparation for using that flat in flow-filter, adjust tests to attach an initiating or a reply flow to packets, depending on the case. Signed-off-by: Quentin Monnet <qmo@qmon.net>
In the flow-filter stage, in the case of traffic for masquerade or port-forwarding with outdated flow information attached, we try to revalidate the flow based on flow information. To do so, we have specific, gated entries in the flow-filter context tables, and we run each lookup (remote then local side) in possibly two steps: once with the gate activated, in case we're dealing with reply traffic, and once without it, in case of forward traffic. Now that we added a flag to flow information to tell whether each flow in a pair is the initiating, or reply-direction flow, we don't actually need to do these two-step lookups ever: we can infer the direction (forward or reply) from the packet from the presence or absence of the flag. Let's do this, and remove the unnecessary lookups. We also need to slightly adjust the related fuzz tests. Signed-off-by: Quentin Monnet <qmo@qmon.net>
Debug binaries retained the complete Rust toolchain through their standard-library source paths, adding roughly 2.4 GB to the closure. They also carried a sizable DWARF index that neither packaged debugger consumes. Point those paths at the much smaller rust-src component, omit unused prebuilt documentation, and remove .debug_names. Source browsing and symbols remain available while the resulting diagnostic images become practical to store and transfer. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Daniel Noland <daniel@githedgehog.com>
A core collected from the lab is useful only with the exact unstripped binaries and sources that produced it. A general debugging toolbox cannot reconstruct that relationship after the release has moved on. Provide a purpose-built gdb image alongside each build and teach it Rust's standard-library types without retaining rustc. This keeps post-mortem debugging reproducible while avoiding unrelated live-debugging tools. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Daniel Noland <daniel@githedgehog.com>
Post-mortem inspection and live debugging need different tools. The core viewer cannot offer an editor-driven session, while bugstalker understands Rust layouts and can expose the running dataplane through the Debug Adapter Protocol. Track bugstalker upstream for its current remote DAP support and package it separately with the matching binaries and sources. Keeping the image single-purpose avoids making every diagnostic artifact carry every debugger. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> Signed-off-by: Daniel Noland <daniel@githedgehog.com>
Two things already lean on this. `bolero` diagnoses by shrinking and replaying, so a pipeline that answered differently the second time would shrink towards nothing and report a case that does not fail. And it is what makes recording every case unnecessary: a failure can be re-run with as much instrumentation as it takes. Verified across processes too, by digesting a fixed scenario: 3525008ad91c215a four times. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Inline generate-and-judge works while there is one conversation at a time; it cannot be interleaved, because the loop producing packets would also have to be in the middle of judging them. Proven against the most demanding existing property rather than a new easy one, so that the trait's shape is decided by what is already needed. Both of that property's break tests still fail it, now carrying the conversation's history rather than a line number. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…sfied The first property here about superposition rather than about a stage, and it asserts nothing new: each conversation knows what it sent, so the joint claim is that every one of them was satisfied. A poll draws from several loads rather than one, because the shape that matters is a reply for one conversation sharing a burst with another's request -- and the harness counts how often that actually happened, since a schedule that never mixed would pass while testing what we already had. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A request/response load can never offer a scheduler more than one packet, so a run of them is a run of singleton bursts however generous the schedule. A blast is what makes `take` mean anything, and what puts several packets of one flow into a burst alongside somebody else's traffic. The interleaving guards now count what each burst actually carried rather than what its poll named, which is the difference between a coverage guard and one that cannot fail. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Hand-aimed addresses work while the fixture is fixed and stop the moment it is not: a generated configuration exposes prefixes nobody wrote down, and traffic aimed by hand would miss every one of them. The derivation reads what a configuration offers, not what the dataplane should do with it; each load still judges only its own traffic. Counting derived loads per flavour rather than in total, because skipping an entire expose kind is the likeliest way for this to be quietly wrong. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Implements the operation algebra for the overlay, which the design note recorded as still unbuilt. The per-kind draw counters are load-bearing rather than decoration: they caught two separate ways the vocabulary went dead while every property stayed green. Both are written down where the mistake was made. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Closes the loop the config algebra was built for: the configuration, the traffic it implies, and the order that traffic runs in are now one draw. Three harness assumptions that a written fixture had been holding up failed here, one of them at about a run in ten. Each is a case where the configuration already knew the answer and the derivation had hard-coded it; the general form is in development/code/property-testing.md. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
First step of the multi-worker work: find out whether the pipeline can be driven inside a model-checked execution at all. It cannot, and the obstacle is not DPDK's opacity but one process-global lock, which the module docs set out. The three-case lock test exists to make that attributable rather than to assert anything interesting on its own. Its failing third case is written out rather than run: shuttle aborts the process instead of failing a test, so a live one would take the suite down with it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A `concurrency::sync` mutex in a `static` typechecks under loom and shuttle and then aborts the process, because the primitive belongs to the execution that created it and a `OnceLock` outlives every execution. `OnceLock` is what the concurrency crate recommends for the separate problem that `Mutex::new` is not `const fn` there, so the advice and the hazard point in opposite directions and nothing says so. This unblocks driving the pipeline under shuttle: every `Fabric` builds ACL contexts and so took this lock. Revisit if loom and shuttle ever make `Mutex::new` const. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The facade documented `OnceLock` as the workaround for `Mutex::new` not being `const fn` under the model checkers. It resolves that compile error and leaves a worse one, which nothing said: the second execution to take the lock aborts the process. `dpdk` followed the advice and was the only instance. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A pipeline per thread needs one reader per thread. The readers this already published cannot serve: they hold `NonNull` table pointers and a `Cell` counter, and the fib readers cache `Rc<UnsafeCell<FibGroup>>`, so none of them is `Send` or `Sync` -- nor is `RouterTables`. A factory crosses the boundary and each thread makes its own reader, which is how `start_router` feeds its workers. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…and a worker A `Fabric` interleaved writer creation with stage assembly, so one configuration could only ever produce one pipeline and no property here could be about more than one worker. Which of the three parts may cross a thread boundary, and why only that one, is on `Blueprint`. The `nat` and `routing` re-exports are part of the same change: a blueprint has to name the reader-factory types in a field, and outside those crates they could not be named at all.
…ator The first property here that runs in the arrangement production runs in: two pipelines, one allocator, one flow table. Break-tested against a lost update in the port-block claim. The measurement that says shuttle rather than the sanitizer is what catches it -- and what shuttle still cannot see -- is recorded on the test.
`table_name` promised process uniqueness and, under a model checker, did not deliver it. The counter was the concurrency facade's atomic, which belongs to the execution that created it; `rte_acl`'s registry is process-global and resets for nobody. Found by `dataplane::packet_processor::fuzz::model`, which lowers many configurations per process and so was the first thing to notice.
bolero becomes the outer loop over the same draw the single-threaded generated property uses, so a shape reachable on one thread is reachable on two by construction. Measured against a deliberate allocator defect: this property does not catch it and the targeted one does. Both the measurement and the reason are on the test, because "the generated property covers that too" is the assumption it refutes.
Tests can now hold a recording that dumps the spans and events leading to a panic. It cannot be asserted on: there is no way to read the trace back into the program, and the module explains at length why that is the feature rather than a missing one. Three things about it were measured rather than assumed, and are written down where the next person will need them: which targets drown a dump under a model checker, that `#[instrument]` on the packet path captures whole `Packet` values, and that a scoped subscriber cannot exist under loom or shuttle at all.
The arrangement receive-side steering normally puts the gateway in, and the one no property here has ever been in: a reply's tuple is the request's reversed, so the return traffic of a flow opened on one worker routinely lands on another. The oracle is the existing `Conversation::judge_reply`, reused rather than restated. What the break test does and does not establish is recorded on the test, because it does not isolate what the property is named for.
Both handlers re-read the same `RwLock`, and so does the `Display` behind the `logfmt()` in their debug lines, so the guard made those recursive acquisitions. `parking_lot` gives no recursion guarantee, and writers on this lock are ordinary data-path traffic; one of the two acquisitions being a log line means the window only opens when tracing is turned up. Found by the new model property below, which shuttle refused outright. Real threads had passed it every time.
Production's third thread. Every property before this froze the tables before sending a packet, so left-right under `FibTableReader` -- and the per-thread `Rc<UnsafeCell<FibGroup>>` cache behind it -- had never been asked to publish while a reader was mid-lookup. `Fleet` stops owning the `RouterTables` to make it possible: a fleet is shared by reference with every worker, so whoever wants to change a route cannot also be borrowing it through one.
The route-churn property states a frame condition, so it can only ever say that publishing did no harm. Aiming the churn at the route under test needs a claim with a lower bound as well, and the fib's in-place next-hop update -- which writes through an `UnsafeCell` every route already points at -- is the part of it whose safety argument had never been exercised.
The overlay half of config-apply-under-load. `Fleet::reconfigure` re-stores through the writers a running worker already reads, which is what production does and what rebuilding a fleet would not be. Written twice: the first shape overlapped nothing on the plain backend and passed for that reason. See the doc comment -- rounds are what make the passing case mean something, and the two discarded break tests are recorded because each looked conclusive and was not.
The design note proposed checking completeness against real configurations. That answers the wrong question -- the algebra's address plan is a function of its handles, so no real configuration is expressible and the report is "0% reachable". Per degree of freedom instead, with the fuzzer supplying the evidence and the survey's exhaustive destructuring stopping the build if the schema grows a field nobody classified. Reports thirteen of twenty-seven degrees of freedom fixed. Nothing acted on yet; the note names the four worth doing first.
… touch The frame condition over a configuration that actually changes, with the frame taken from the algebra rather than guessed at. `X => A.X` from one draw split at its last operation. Round 1 races the change and is counted, not asserted: it fails about one run in twenty, and the doc comment records what was measured and what is still unknown. Rounds after it race a re-enactment and carry the full claim. Two harness faults found on the way and fixed here, both of which made earlier properties in this module prove less than they appeared to: a worker panicking between barriers hung the run instead of failing it, and the genid was pinned at 1 across enactments, which told the nat allocator no configuration ever changed and manufactured two symptoms that are not the dataplane's.
`Fleet` published neither: the nat allocator was told generation one on every enactment, and the stages were never told at all, because `Blueprint::worker` built its pipeline without `set_data`. The second also meant the flow revalidation path -- the code that exists for a configuration changing under live flows -- was unreachable from every property in this module. Also separates the two workers' five-tuples by disjoint halves of the port space rather than by an offset, which two drawn ports could cancel; `enact` performs config-apply one step at a time, for attributing a disturbance to one; and the ordering now follows `mgmt`, where the generation is published last on purpose. Found by `just fuzz` on this target. The random engine `cargo test` runs cannot get here: it was the coverage-guided corpus that produced the crashing inputs these were diagnosed from.
Prompted by `-Cunsafe-allow-abi-mismatch=sanitizer` looking suspicious, which it is. Nothing changed; the note records what each path covers and names the experiment that decides whether the flag is vestigial or is hiding something.
`-Cunsafe-allow-abi-mismatch=sanitizer` disabled rustc's check that every crate agrees about sanitizer flags -- the check that refuses a half-instrumented link. `tests.pkg.dataplane` builds and links without it under `--argstr sanitize thread`, so there was nothing to excuse: `std` is rebuilt from source by `-Zbuild-std` under the same sanitizer as the rest. The sysroot now records what it was built for, and `just fuzz` refuses when that disagrees with the sanitizer it was asked for. The two knobs are independent and a mismatch produced a binary whose green run meant nothing.
…s against A short burst right after the barrier measured when the allocator swap landed rather than what it did: `Everything` spends about a millisecond rebuilding rte_acl before reaching the allocator, by which time the burst was over, so it looked fifty times safer than the masquerade step alone. Sustained, the two are the same order. Also models the router-config await `mgmt` performs between the allocator swap and the generation publish, which turns out to change nothing -- the doc comment records that and what thread sanitizer said, which was nothing, on a build that instruments dpdk and rebuilds std.
The transient the step instrument attributed to the masquerade store is a dataplane defect, not a harness one: an allocator swap can give one public tuple to two live flows, and a reply then reaches the wrong tenant conversation. `set_randomize(false)` was the last deviation this harness had from `mgmt::apply_masquerade_config` and was the obvious suspect. It is not the answer -- the instrument now runs both settings, with the running allocator primed so the swap under test is random-to-random. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`Flavour` has no static-nat or port-forwarding member, so those two rows measured a writer storing an empty table. Recorded because the table is cited elsewhere as evidence that only the masquerade store disturbs traffic. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
daniel-noland
force-pushed
the
pr/daniel-noland/spec-compliance
branch
from
August 23, 2026 19:27
976db44 to
d49c061
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Scratch PR. Parking the specification-compliance line of work so it is pushed and reviewable;
not proposed for merge in this shape. Stacked on #1738.
What is here
Nine commits, continuing the duvet work already in the stack (RFC 4884 and RFC 5382 landed earlier).
Tracking RFC 4787 — the UDP counterpart of RFC 5382, 14 numbered requirements landing on the
same masquerade code that already carries RFC 5382 citations. Cited as the individual RFC, never as
BCP 127: that composite concatenates RFC 4787, RFC 6888 and RFC 7857, whose section numbers collide,
and duvet silently keeps only the last — 42 requirements where the three separately yield 129.
Findings recorded, not fixed. REQ-1 (mapping is address-and-port-dependent), REQ-8 (filtering is
stricter than either branch the RFC offers) and REQ-9 (no hairpinning) are one decision, not three:
this gateway is deliberately not UNSAF-traversal-friendly, or it is not deliberate and is a much
larger piece of work. REQ-13 is the same shape — this dataplane originates no ICMP error at all, and
has no MTU anywhere on the datapath to originate one from. All
todorather thanexception,because nobody has ruled.
Executable contracts.
nat/src/masquerade/contract.rsstates RFC 4787 REQ-12 as aRequirementthe implementation calls from a debug-only assertion and the test citedtype=testcalls directly, so the two citations are provably about the same predicate.
SPECandIDareconstand checked against duvet's extracted requirements at compile time: a citation naming arequirement its specification does not state now fails the build with
E0080.Errata. The corpus's
inline-errata/was audited. RFC 4787, 5382, 5508, 6888 and 7857 have noerrata of any status; RFC 4884 has one, and it does not touch us.
Open, and deliberately not done here
.duvet/snapshot is not regenerated — duvet is not on PATH outside the dev shell, andhand-editing the regression gate would defeat it. RFC 4787 REQ-12 should move to
[!MUST,implementation,test]on the nextduvet report.just duvetrecipe yet.development/code/spec-compliance.mdcarries a live open-questions list, expected to grow.time, only
.duvet/which is committed.Checks
cargo test -p dataplane-nat210 pass, 1 ignored. clippy and fmt clean ondataplane-natanddataplane.🤖 Generated with Claude Code