git-ents.gitmain
⌘K
foforge
commit 5a715d0
query: harden the monotone proptest with a footprint mirror check and a meta() fixture

Assert footprint_touches(index, moved) against the real Query::footprint().matches() at every generated transition, so a silent divergence between the hand-written mirror and the implementation’s derivation fails loudly instead of only showing up as a coincidentally-correct entry-set diff. The mirror stays the oracle’s source of truth; this only cross-checks it.

Add a sixth fixture query, meta(refs/meta/member/*), the one atom shape the proptest never exercised. The oracle computes it independently (direct tip scan under refs/meta/member/, no reachability closure, per query.meta), and a new Op::Member generator variant writes/advances refs/meta/member/<id> so the query’s entry path actually gets hit. Deletion of a meta ref is out of scope for this generator — write/advance only, to avoid adjudicating spec-ambiguous territory.

Assisted-by: Claude:claude-sonnet-5

Joseph D. Carpinelli · 1 month ago

Reviews

No reviews of this commit yet — record a verdict below.

Start a review

verdict

crates/ents-query/tests/monotone.rs @@ -20,9 +20,9 @@ use std::collections::{HashMap, HashSet}; -use ents_model::Status; +use ents_model::{Member, Provenance, Status}; use ents_query::{Evaluator, Query, Transition}; -use ents_testutil::{MemRefStore, ObjectStore, advance_ref, record_result}; +use ents_testutil::{MemRefStore, ObjectStore, advance_ref, record_result, write_member}; use gix_hash::ObjectId; use gix_object::{CommitRef, Find as _}; use gix_ref_store::RefStoreRead as _; @@ -31,6 +31,7 @@ const BRANCHES: [&str; 3] = ["refs/heads/main", "refs/heads/dev", "refs/heads/wip/x"]; const EFFECTS: [&str; 2] = ["unit", "integ"]; const STATUSES: [Status; 3] = [Status::Pass, Status::Fail, Status::Error]; +const MEMBERS: [&str; 2] = ["alice", "bob"]; /// One randomized history operation. #[derive(Debug, Clone)] @@ -52,6 +53,9 @@ status: usize, short_len: usize, }, + Member { + who: usize, + }, } fn op_strategy() -> impl Strategy<Value = Op> { @@ -67,6 +71,7 @@ short_len } ), + (0..2usize).prop_map(|who| Op::Member { who }), ] } @@ -151,7 +156,7 @@ .collect() } -/// The five checked queries and their independent full evaluations. +/// The six checked queries and their independent full evaluations. fn oracle(objects: &ObjectStore, refs: &Refs, query_index: usize) -> HashSet<ObjectId> { let main = reach(objects, refs.get("refs/heads/main").copied()); match query_index { @@ -176,19 +181,29 @@ let dev = reach(objects, refs.get("refs/heads/dev").copied()); main.union(&dev).copied().collect() } - _ => unreachable!("five queries"), + // `meta(refs/meta/member/*)` (`query.meta`): the tip commits of + // matching author-written meta-refs directly, never a + // reachability closure — computed independently of both + // `Query::footprint` and the evaluator's `meta_tips`. + 5 => refs + .iter() + .filter(|(name, _)| name.starts_with("refs/meta/member/")) + .map(|(_, oid)| *oid) + .collect(), + _ => unreachable!("six queries"), } } -const QUERIES: [&str; 5] = [ +const QUERIES: [&str; 6] = [ "rev(refs/heads/main)", "rev(refs/heads/*) - rev(refs/heads/wip/*)", "rev(refs/heads/main) & results(unit, pass)", "results(unit, pass) & results(integ, pass)", "rev(refs/heads/main) | rev(refs/heads/dev)", + "meta(refs/meta/member/*)", ]; -/// Each of the five queries' static ref-footprint (`query.footprint`), +/// Each of the six queries' static ref-footprint (`query.footprint`), /// hand-written independently of `Query::footprint` — this is what the /// oracle checks against, not a call into the thing under test. /// @@ -207,7 +222,8 @@ 2 => moved == "refs/heads/main" || results_of("unit"), 3 => results_of("unit") || results_of("integ"), 4 => moved == "refs/heads/main" || moved == "refs/heads/dev", - _ => unreachable!("five queries"), + 5 => moved.starts_with("refs/meta/member/"), + _ => unreachable!("six queries"), } } @@ -274,6 +290,13 @@ ); Some(format!("refs/meta/results/{}/{short}", EFFECTS[effect])) } + Op::Member { who } => { + let id = MEMBERS[who]; + seconds += 100; + let member = Member::new(format!("key-{id}"), Provenance::AdminRegistered); + write_member(&refs, &objects, id, &member, None, seconds); + Some(format!("refs/meta/member/{id}")) + } }; let after = snapshot(&refs); @@ -289,6 +312,18 @@ }; for (index, query) in queries.iter().enumerate() { + // The hand-written mirror must agree with the real + // `Query::footprint()` on every generated transition — + // any silent divergence between the mirror's theory and + // the implementation's derivation is a loud failure + // here, not a false pass downstream. + let touches = footprint_touches(index, &moved); + prop_assert_eq!( + touches, + query.footprint().matches(transition.name.as_ref()), + "footprint mirror disagreement for {} on {:?}", QUERIES[index], moved + ); + let full_before = oracle(&objects, &before, index); let full_after = oracle(&objects, &after, index); @@ -297,7 +332,7 @@ // outside it is a non-event no matter what the raw diff // of two full evaluations would suggest. let expected: std::collections::BTreeSet<ObjectId> = - if footprint_touches(index, &moved) { + if touches { full_after.difference(&full_before).copied().collect() } else { std::collections::BTreeSet::new()