Resident: query fingerprints, retain-only sessions and a pin request - #45
kiranandcode wants to merge 10 commits into
Conversation
What a caller needs to keep a resident session across a source edit without reopening it (verus-tools-mcp refresh_session): - Every catalogue query carries a `fingerprint`: FNV-1a over its AIR as the printer writes it, the declaration prefix asserted below it (the bucket's base context and the journal scopes up to the query's own) and the query body apart. The printer writes assertion labels as their notes and never a span, so a query that only moved to other lines fingerprints the same; generated names carry per-function counters, not line numbers. - Under VERUS_RESIDENT_RETAIN_ONLY the invocation retains every selected query and checks none, reported as `ready.retain_only`: the front-end pass a caller opens on the edited source, after which it checks only the queries whose fingerprints changed. - A `pin` request gives a query the rung its checks try first, as a ladder request pins one, so a caller can carry a pin over. A second front-end pass in the serving process was tried first and is not possible: rustc_mir_build's VERUS_AWARE_DEF_IDS and VERUS_ERASURE_CTXT are set once per process and panic on a second set, so the caller runs the pass in a new worker. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Automatic trigger selection lists a quantifier's :pattern groups in an order that varies from one compilation to the next, and sometimes lists one twice (page-table's protect_aux fingerprinted differently on runs of unchanged source), so the fingerprint hashes them sorted by their text and deduplicated, in place among the term's other annotations. Three retain-only runs of impl_u::l2_impl (134 queries) now agree on every query; a one-line edit in one lemma changes that lemma's query alone. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
ca7a3c3 to
ab465d3
Compare
A query's rlimit lives beside its AIR, so raising `#[verifier::rlimit]` left the fingerprint unchanged and a caller comparing fingerprints kept the old verdict. Hash the budget into the body. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Review fixes for the retain-only/pin work: - A pin request is refused where the checks that follow it could not run it, as a ladder refuses or skips: a cvc5 without :quant-strategy (whose check would end the session), a rung the solver has no module for, and a budget below one cvc5 resource unit (which reached cvc5 as no limit). - A retain-only session retains every recommends query a check could have added (retain_unchecked_recommends), so its catalogue no longer depends on answers it never had: a follow-up retained because a check failed in the session before an edit is still there to compare. - probe_strategy_rung's doc: cvc5 fills in the available strategies when it is initialized (first assertion or push), not at the first check-sat, so a ladder or pin on a retain-only session sees them. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A user quantifier's :qid and :skolemid end in a counter that the bucket's Ctx keeps (new_user_qid), not the function's, so every quantifier lowered before one renumbers it. A retain-only session lowers recommends follow-ups that a checked session skips, and an edit that adds a quantifier to an earlier function or flips its verdict adds or drops some, so the body and prefix fingerprints of every later query in the bucket changed on source that left them alone. Hash those names without the counter. A retain-only session also lowered follow-ups for functions the filter leaves out, which it never retains; skip them. The retain-only test's source now has quantifiers in the functions whose follow-ups only a retain-only session lowers, and in a function after them. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A query's prefix fingerprint now covers the prelude its solver starts from. The prelude reads the crate's word size, which a `global size_of usize` line sets, so a query whose AIR and bucket context an edit left alone could change verdict and still fingerprint the same. A by(bit_vector) spinoff's solver gets neither the prelude nor the bucket context, so its journal records neither: before this, every declaration added to the bucket changed the prefix of its bit-vector queries. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A by(bit_vector) query's solver gets no prelude, no bucket context and no
journal scope, so in a retain-only session nothing has reached it before
the first request. cvc5 lists the strategy modules it has only once it is
initialized, so a pin on such a query was refused ("the solver has no
module for this rung") and a ladder marked every rung unavailable.
probe_strategy_rung now pushes and pops an empty scope first.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Review follow-up (pushed as dd3227a + c11737d): Fixed: a pin or ladder on a Not a bug: the fingerprint leaves out the prover. Adding 🤖 Generated with Claude Code |
What a caller needs to keep a resident session across a source edit without reopening it (verus-tools-mcp
refresh_session, BasisResearch/verus-tools-mcp#49):fingerprint: {prefix, body}: FNV-1a over its AIR as the printer writes it, the declaration prefix asserted below the query (the prelude, the bucket's base context plus the journal scopes up to the query's own, hashed incrementally per solver) and the query body apart; the body hash also covers the query's rlimit, which is set outside the AIR. The prelude is hashed because it is not fixed text: it reads the crate's word size, which aglobal size_of usizeline sets, possibly in another module whose size_of lemma the bucket prunes, so a query's AIR and bucket context can stay the same while its verdict changes. Aby(bit_vector)query's solver gets neither the prelude nor the bucket context, so its prefix covers neither, and declarations added to its bucket leave it alone. The printer writes assertion labels as their notes and never a span, so a query that only moved to other lines fingerprints the same; generated local names carry per-function counters, not line numbers. A user quantifier's:qid/:skolemidis hashed without the counter it ends in (forget_quantifier_counters): that counter is the bucket'sCtx's, not the function's, so without this every quantifier lowered after an extra recommends follow-up (which a retain-only session lowers and a checked one skips), after a quantifier added to an earlier function, or after an earlier check flipping between pass and fail, fingerprinted differently on queries the edit left alone. Two queries with equal fingerprints are therefore the same up to their quantifiers' names, so what a caller carries over by fingerprint must not name a quantifier by:qid(a pin does not). A quantifier's:patterngroups are hashed sorted and once each (sort_patterns): automatic trigger selection lists them in an order that varies between compilations and sometimes lists one twice (page-table'sprotect_auxfingerprinted differently on runs of unchanged source before this). Together with the existing identity (function, kind, description, repeat), this is what a caller compares across compilations.VERUS_RESIDENT_RETAIN_ONLYthe invocation retains every selected query and checks none of them (run_commands_queriesreturns early), reported asready.retain_only. This is the front-end pass a caller opens on the edited source, after which it checks only the queries whose fingerprints changed. With no answers to decide which recommends follow-ups to retain, it retains every one a check could have added (retain_unchecked_recommends): the follow-up of every body or termination op that has something to check, and forcheck_recommendsfunctions the checked kind too, for the functions the--verify-*filter selects (for the rest nothing is retained, so nothing is generated). Its catalogue is then a superset of any checked session's on the same source, with the same fingerprints, so a follow-up retained because a check failed before the edit is still there to compare. A caller should check a follow-up only when its function's body fails, as the batch run does.--expand-errorsqueries are the one exception: they focus on the assertion a failed check named, so a retain-only session has none.pinrequest ({bucket, query, rung, alongside, rlimit}→pinned): gives a query the rung its checks try first, as a ladder request does, so a caller can carry a pin over to the new worker. Refused, as a ladder refuses or skips, on a cvc5 without:quant-strategy(whose pinned check would end the session), for a rung the solver has no module for, and for a budget below one cvc5 resource unit (which would reach cvc5 as no limit).Why not a
refreshrequest served in-process: that was implemented first (a seconddriver::runfrom the serving process with the invocation's own arguments). It cannot work on this toolchain:rustc_mir_build'sVERUS_AWARE_DEF_IDS/VERUS_ERASURE_CTXT(source/rustc_mir_build_additional_files/verus.rs, compiled into the patched rustc) are set once per process and panic on a second set (VERUS_AWARE_DEF_IDS has already been set). So the second front-end pass runs in a new worker process, and the diff lives in the caller; Verus provides what the diff needs.Tests:
resident::tests::fingerprints_ignore_spans_and_tell_the_prefix_from_the_body(same AIR at other spans → same fingerprint; a declaration added below the query → prefix differs, body does not; the query edited → body differs, prefix does not; triggers reordered or repeated → same body, one dropped → different; a user:qid/:skolemidwith another counter → same body, another function's → different); plus the same query at another rlimit → body differs; a journal on another prelude → prefix differs, body does not;commands_names_every_requestandresident_ready_lists_the_requests_it_servescoverpin. Integration tests:resident_fingerprints_cover_the_word_size_in_the_prelude(aglobal size_of usize == 8in another module flips a query from invalid to valid; before the prelude was hashed its fingerprint was identical, prefix and body),resident_bit_vector_fingerprints_ignore_the_bucket_context(a spec fn and a struct added to the bucket change a default query's prefix but not a bit-vector query's; before, the bit-vector prefix changed too),resident_pin_is_refused_where_checks_could_not_follow_it(unavailable rungs, out-of-range and sub-unit budgets refused, the session still checks, a valid pin is followed),resident_retain_only_session_pins_and_ladders_before_any_check(a pin and a ladder on solvers that have checked nothing see all five rungs),resident_retain_only_session_pins_and_ladders_bit_vector_queries(the same forby(bit_vector)queries, whose solvers get no prelude, context or scope, soprobe_strategy_rungpushes and pops an empty scope before asking; fails without that),resident_retain_only_session_retains_what_any_checked_session_could(every query of a checked session is in the retain-only catalogue with the same fingerprint; the extras are exactly the follow-ups of checks that passed; the source has quantifiers in those functions and in a function lowered after them, so the retain-only session numbers that function's quantifier differently, and the test fails withoutforget_quantifier_counters). On this Mac:cargo test -p rust_verify --lib resident33 passed,vargo test --release -p rust_verify_test --test resident58 passed (both new tests fail on the commit before the prelude/bit-vector fix); on the code before the review fixes, the pin and catalogue tests fail and the ready-list test failed; with only theforget_quantifier_counterscall removed, the fingerprint unit test and the catalogue test fail (the catalogue test onlater's body fingerprint).rustfmt --checkandcargo clippy -p air -p rust_verify --all-targets -- -D warningsclean.Measured before the retain-only session retained follow-ups for passing checks and before quantifier names were hashed without their counter, so its catalogue and front-end time are now somewhat larger (through the resident protocol directly, this Mac,
-V cvc5 --rlimit 10, verified-nrkernel page-table, one-line edit in the body oflemma_new_entry_mb0_bits_are_zero):--verify-module impl_u::l2_impl(7 buckets, 134 queries): a full open (front-end + every check) 131.7 s wall; a retain-only run on the edited source 4.5–6.0 s, classified 133 unchanged / 1 changed (body); the changed query then checked in 4.9–8.7 s. Three retain-only runs of unchanged source agree on all 134.--verify-only-module impl_u::l2_impl --verify-function lemma_new_entry_mb0_bits_are_zero(16 queries): 11.5 s vs 4.7 s, 15 unchanged / 1 changed.🤖 Generated with Claude Code