forge: check — weave the ADTs reachable through woven spec fns, and name harnesses after what they check (closes #92) - #95
Merged
Conversation
…#92) No file declaring more than one ADT had ever certified. The per-item ADT weave seeded `reachable_adt_deps` with `[item] + fn_deps`, while every arm of `item_subprogram` weaves `item_spec_items` as well — the referrer set was a strict subset of what the sub-program contained, so an ADT reachable only through a woven spec fn was omitted from the emitted Verus and the run failed closed on an unresolvable type. Three surfaces of the one defect: 1. a checked `struct`/`enum`: `collect_item_adt_refs` is inert on an ADT decl (field types are followed by the type-graph fixed point instead), so `adt_deps` came out empty for EVERY ADT item while the arm wove the file's whole `spec_items`. A spec fn naming a second ADT dangled -> L0 on E0425 -> `project assurance: FAILED`. 2. a checked `fn` naming no ADT that a woven spec fn takes: the solver-vacuity harness failed to elaborate, which `vacuity_solver::interpret_summary` refuses as undetermined -> a `ForgeError` aborting the run. 3. a checked `spec fn`: already correct, which is why the single-ADT corpus never exercised the gap. A single ADT masks all of it: the only ADT is the checked item, which the ADT arm pushes itself, so the decl is present by construction. Same under-approximated-closure class as the `reachable_spec_fn_deps` body-only omission recorded in proof-backends.md's increment-(i) note. The seed now extends with `item_spec_items` for every item kind (replaced, not extended, for `Item::SpecFn` — #71's distinct per-spec-fn sub-program), and the ADT arm drops the checked item from `adt_deps` before pushing it so a spec fn naming that ADT cannot produce a duplicate definition (E0428). DESIGN SOURCES THIS ITERATION: - .design/forge/check.md (REQ-1 per-item pipeline, REQ-5 L3 iff 0 errors) - .design/basis/09-option-result.md, .design/basis/11-ergonomics.md, .design/basis/12-mutual-recursion.md, .design/lower/boundary-composition.md, .design/verified/proof-backends.md (the six docs routing check.rs) - thermite-design.md §5.3 (the isolated per-item sub-program), §6 (the ladder) - reference: conformance/sum.th, conformance/composition/cases.json REQ STATUS: - REQ-1 (pipeline orchestration) SHIPPED — unchanged; this corrects its input. - REQ-5 (level determination) SHIPPED — unchanged; L3 iff verus reports 0 errors. The ADT items were never un-provable, only un-emitted. R-HONEST-4: the correction is recorded as an Amendment in .design/forge/check.md. VERIFICATION: cargo test -p forge: 807 passed, 2 failed — both pre-existing and environmental (strat_differential needs the Lean spine built locally; it fails identically on unmodified check.rs and does not reference it). new pin forge/tests/divergence_multi_adt_subprogram.rs: 4 passed. Confirmed a real pin — 3 of the 4 FAIL on pre-fix code (two assertion failures + the ForgeError abort); the 4th is the R-CHAR-3 oracle (a spare decl cannot change what is provable about its siblings, so every shared item's level must equal the single-ADT twin's) and holds on both sides by construction. conformance: multi_adt.th — pre-fix `Role`/`Action` both L0/E0425 and project FAILED; post-fix all five items L3, matching the hand-derived multi_adt.cert.json oracle subset. cargo clippy -p forge --all-targets -- -D warnings: PASS cargo fmt --all --check: PASS doc-drift: PASS (six check.rs-governing docs re-pinned) req-status / req-registry / control-plane: PASS verus 0.2026.05.24.ecee80a (the ci.yml pin) Closes #92 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The diagnostics half of #92. `run_verus` took its scratch-dir/`.rs` stem from `program.items.first()`, and `item_subprogram` weaves ADT decls and spec fns ahead of the checked item (which it pushes last), so any item with something woven before it filed its diagnostics under a sibling's name: spec fn helper(n: u64) -> u64 { n } fn bad(x: u64) -> u64 ens result == helper(x) + 1 { x } // cannot hold before: [FAIL] postcondition not satisfied @ helper_check.rs:13:9 after: [FAIL] postcondition not satisfied @ bad_check.rs:13:9 `helper` certifies L3 and has nothing to do with the failure. This is not cosmetic: it is what made #92 read as two defects. The reporter saw E0425 under `/tmp/forge_is_owner_check_*/is_owner_check.rs` while `is_owner` certified L3, and concluded a harness that failed to compile could coexist with an L3 on the same item — that the L3 refusal path had a hole. It does not. The failing harness belonged to `Unused` (correctly L0); only its NAME pointed at `is_owner`. REQ-4's "the failed obligation and its source position" is not satisfied by a position under another item's filename. `run_verus` now takes the checked item's name as a `subject` parameter and no longer receives the sub-program at all — a harness that cannot see the woven set cannot name one of its members by accident. All seven call sites already had the subject in scope (it is what they pass to `assemble_certificate`). The equivalence-probe site's single-item `Program` wrapper existed only to feed the old label machinery, so it and its `f.clone()` are removed. Amendment item 6's `<stem>.rs`-inside-`forge_<stem>_<pid>_<n>/` scheme and the AC-4 no-`.` crate-stem property are unchanged — only the source of `<stem>` moved. DESIGN SOURCES THIS ITERATION: - .design/forge/check.md (REQ-2 scratch/crate-stem, REQ-4 per-obligation position, AC-4 crate-name gotcha, Amendment item 6 scratch naming) - reference: conformance/sum.th REQ STATUS: - REQ-2 (verus invocation, temp file, crate-name gotcha) SHIPPED — the no-`.` stem property is unchanged; only the stem's source moved. - REQ-4 (per-obligation results + counterexamples) SHIPPED — a failing obligation's position now identifies its own subject. R-HONEST-4: recorded in .design/forge/check.md's #92 Amendment. VERIFICATION: new pin forge/tests/divergence_harness_names_checked_item.rs: 2 passed. Confirmed discriminating — pre-fix it FAILS with "got `helper_check.rs:13:9`"; post-fix it passes. cargo clippy -p forge --all-targets -- -D warnings: PASS cargo fmt --all --check: PASS doc-drift: PASS (six check.rs-governing docs re-pinned) req-status / req-registry: PASS cargo test -p forge: in progress at commit time (39/95 binaries, 0 failures); the preceding commit's full run was 807 passed / 2 pre-existing environmental failures (strat_differential needs the Lean spine built). verus 0.2026.05.24.ecee80a (the ci.yml pin) Refs #92 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.
Closes #92. Two commits: the weave fix, and the diagnostics defect that made #92 look like two bugs.
1. Root cause — one defect, three surfaces
Not in
build_vacuity_harness, andreachable_adt_deps/collect_item_adt_refsare both correct. The bug is the referrer seed incheck_file_with_options:Every arm of
item_subprogramweavesitem_spec_items, but only theSpecFnarm seeded them as referrers. The referrer set was a strict subset of what the sub-program contained, so an ADT reachable only through a woven spec fn was never emitted. The pre-existing comment said so out loud: "TheItem::Fnpath is unchanged — its ADT referrers stay[item] + fn_depsexactly as before."Struct/Enum[item]— andcollect_item_adt_refsis inert on ADT decls, soadt_depswas always emptyE0425→ L0 →project assurance: FAILEDFn[item] + fn_depsForgeErroraborts the runSpecFnWhy one ADT masked it: the only ADT is the checked item, which the ADT arm pushes itself. Same under-approximated-closure class
proof-backends.mdrecords forreachable_spec_fn_depswalkingdecl.bodywithoutdecl.dec— and, as that note predicts, the pipeline fails closed (a Verus error, never a silent certification).Fix: seed the referrers with everything woven —
referrers.extend(item_spec_items)for every item kind, replaced rather than extended forItem::SpecFn(preserving #71's distinct per-spec-fn sub-program). TheStruct/Enumarm drops the checked item fromadt_depsbefore pushing it, so a spec fn naming that ADT cannot emit a duplicate definition (E0428).2. The diagnostics defect that caused the misdiagnosis
run_verustook its scratch stem fromprogram.items.first(). Sinceitem_subprogramweaves ADT decls and spec fns ahead of the checked item, any item with something woven before it filed diagnostics under a sibling's name:helpercertifies L3 and had nothing to do with the failure — it was just woven first.This is what made #92 read as two bugs. The reporter saw
E0425underis_owner_check.rswhileis_ownercertified L3, and concluded a harness that failed to compile could coexist with an L3 on the same item — that the refusal path had a hole. It does not. The failing harness belonged toUnused(correctly L0); only its name pointed atis_owner.run_verusnow takes the checked item's name as asubjectparameter and no longer receives the sub-program at all — a harness that cannot see the woven set cannot name one of its members by accident. All seven call sites already had the subject in scope. The equivalence-probe site's single-itemProgramwrapper existed only to feed the old label machinery, so it and itsf.clone()are gone.REQ-2's no-.crate-stem property and Amendment item 6'sforge_<stem>_<pid>_<n>/scheme are unchanged — only the source of<stem>moved.Corrections to the issue as filed
Unusedwas L0 only because its harness failed to compile — so there is no "spare declarations make files unshippable" policy question to settle. Claims 1 and 2 are the same bug.provenance_demo.this not a bug.careless_querydeliberately passesTaintedto aSqlsink — the negative case the demo exists to demonstrate. It has no.cert.jsonbecause it intentionally contains three must-fail functions; all six of its ADT items certify L3.Verification
* Both failures are pre-existing and environmental:
strat_differentialneeds the Lean spine built locally (lake build Thermite.Strat.Cls.Wire). They fail identically on unmodifiedcheck.rs, which does not appear in that test file.The pins are real pins, verified in both directions:
divergence_multi_adt_subprogram.rs— 3 of 4 fail pre-fix (two assertion failures plus theForgeErrorabort). The 4th is the R-CHAR-3 oracle: adding a declaration nothing references cannot change what is provable about its siblings, so every shared item's level must equal the single-ADT twin's. It holds on both sides by construction, which is the point.divergence_harness_names_checked_item.rs— fails pre-fix withgot `helper_check.rs:13:9`. Its second test pins thathelpergenuinely certifies L3, because it was the pair of facts that fused into the phantom bug.Corpus anchor:
conformance/multi_adt.th, the first corpus program declaring more than one ADT, shaped as theRole×Actionprivilege lattice from the issue. Its golden pinsauthorize(following the fn-only golden convention — no existing golden pins an ADT item, and inventing that schema was out of scope); the ADT items' levels are pinned in the test.Follow-up not taken here
map_kv.thbreaks on verus0.2026.06.14—Set::newin the Map wrapper's generatedspec_domyieldsE0308: expected Set<int>, found Option<Set<int>>. Pre-existing, unrelated to this change, and invisible on the pinned0.2026.05.24. Relevant whenever the verus pin gets bumped.🤖 Generated with Claude Code