From 8fc39b127e6fa8e761343ab9d42955ab69b723d6 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Wed, 16 Sep 2026 21:30:34 +0200 Subject: [PATCH] Give the read side a mutant per rule `tools/eureka-cut9-mutations.psd1`, V1-V23: every rule the views decide, with the edit that removes it and the test that must fail while it is applied. A rule with two weakenings gets two entries, `Vn` for the rule absent and `VnL` for the rule weakened rather than removed, so a suite that only catches the crude form does not pass for the subtle one. 34 entries; M0 green and every one killed. V1 is the proof that in force has one owner: its mutant lands in `docs.rs`, and V1L is H41's own edit run against the views instead of the rules. One mutant, two suites. Four tests grew to put a mutant in reach of a real failure, which is the point of writing the entries before trusting the suite: - Two receipts naming one write, forged and planted, so the integrity check that refuses them is not dead text. - A cut spec whose withdrawal is itself withdrawn, so `base` has two steps to walk and stopping at one is visible. - A `cut-1` spec beside `cut-9` and `cut-10`, so the prefix that ends at the dot is not the same answer as the prefix that does not. - A second report of one cut, judged by nothing, so "this cut has a verdict" and "this report has one" come apart. Three weakenings the map names have no entry and say why in the file's header: the `semantic` check moved below the filters still refuses; `resolutions_of` ignoring a subject's kind cannot matter while an id carries its own kind segment; `assignments_of` ignoring the instance cannot matter while A5 refuses a stewardship naming another. A mutation that cannot fail is not a proof, and listing it would only make the suite look thorough. Nine cut-8 entries moved with their code in the first commit; that suite is green as well, H1-H67, M0 included. Co-Authored-By: Claude Fable 5.1 --- crates/huginn-mind/src/query.rs | 55 +++- tools/eureka-cut9-mutations.psd1 | 442 +++++++++++++++++++++++++++++++ 2 files changed, 491 insertions(+), 6 deletions(-) create mode 100644 tools/eureka-cut9-mutations.psd1 diff --git a/crates/huginn-mind/src/query.rs b/crates/huginn-mind/src/query.rs index 7de29ed..99eb1a6 100644 --- a/crates/huginn-mind/src/query.rs +++ b/crates/huginn-mind/src/query.rs @@ -425,6 +425,7 @@ mod tests { use super::*; use crate::fixtures::*; + use crate::mind::schema_cache; use crate::store::test_stores::MemoryStore; use chrono::{TimeZone, Utc}; use epiphany_pipeline::{ @@ -635,6 +636,29 @@ mod tests { Some(MindRefusal::Unavailable { detail: detail.clone() }) ); assert_eq!(orphaned.query(&PipelineQuery::default()).err(), Some(MindRefusal::Unavailable { detail })); + + // The other half of the same rule: A10 admits one write per identity, + // so two receipts naming one document is a store this organ cannot + // read, not a choice of which receipt to believe. + let doubled = MemoryStore::new(); + for row in store.rows() { + doubled.plant(row); + } + let forged = crate::receipt::candidate( + &slug(INSTANCE), + provenance(Faculty::Soul), + &[], + &[prepare(&question("Q1", &["A", "B"], "A")), prepare(&question("Q8", &["A", "B"], "A"))], + now(), + ) + .unwrap(); + doubled.plant(schema_cache().unwrap().prepare_entry_named(&forged.receipt_id, &forged).unwrap().0); + assert_eq!( + opened(doubled, INSTANCE).query(&PipelineQuery::default()).err(), + Some(MindRefusal::Unavailable { + detail: format!("document {}/{q1} is written by two receipts", K::Question.type_id()), + }) + ); } /// R-C: `stored_at` is the store's stamp and no derivation reads it. @@ -680,6 +704,7 @@ mod tests { committed(admit(&mut mind, vec![ question("Q1", &["A", "B"], "A"), D::Ruling(ruling("R1")), + D::CutSpec(cut_spec("1", 1)), D::CutSpec(cut_spec("9", 1)), D::CutSpec(cut_spec("10", 1)), follow_up("FU-1", r(K::Question, &id("question", "Q1"))), @@ -693,6 +718,14 @@ mod tests { D::CutSpec(cut_spec("9", 2)), resolution(r(K::CutSpec, &id("cut_spec", "cut-9.r1")), superseded(&[r(K::CutSpec, &id("cut_spec", "cut-9.r2"))])), ])); + // A resolution of a resolution, so a filter reading through the base + // has two steps to walk: the cut-10 spec is withdrawn and its + // withdrawal is itself withdrawn, which puts the spec back in force. + committed(admit(&mut mind, vec![resolution(r(K::CutSpec, &id("cut_spec", "cut-10.r1")), withdrawn())])); + committed(admit(&mut mind, vec![resolution( + r(K::Resolution, &id("resolution", "cut_spec.cut-10.r1.n1")), + withdrawn(), + )])); committed(admit_as(&mut mind, Faculty::Soul, vec![D::Ruling(ruling("R2"))])); // One batch at a later clock, so the window filters have a boundary. let later = Utc.with_ymd_and_hms(2026, 9, 17, 12, 0, 0).unwrap(); @@ -745,7 +778,12 @@ mod tests { assert_eq!(matching(PipelineQuery { cut: Some(l("10")), ..Default::default() }), vec![ id("cut_report", "cut-10.h1"), id("cut_spec", "cut-10.r1"), + id("resolution", "cut_spec.cut-10.r1.n1"), + id("resolution", "resolution.cut_spec.cut-10.r1.n1.n1"), ]); + // The prefix ends at the dot, so cut-1 is not every cut that starts + // with its label. + assert_eq!(matching(PipelineQuery { cut: Some(l("1")), ..Default::default() }), vec![id("cut_spec", "cut-1.r1")]); // `kinds`, empty for every kind. assert_eq!(matching(PipelineQuery { kinds: vec![K::Question], ..Default::default() }), vec![ @@ -756,7 +794,10 @@ mod tests { // `in_force`, both ways: the superseded revision is the one thing this // mind has closed. - assert_eq!(matching(PipelineQuery { in_force: Some(false), ..Default::default() }), vec![id("cut_spec", "cut-9.r1")]); + assert_eq!(matching(PipelineQuery { in_force: Some(false), ..Default::default() }), vec![ + id("cut_spec", "cut-9.r1"), + id("resolution", "cut_spec.cut-10.r1.n1"), + ]); let standing = matching(PipelineQuery { in_force: Some(true), ..Default::default() }); assert!(!standing.contains(&id("cut_spec", "cut-9.r1"))); assert!(standing.contains(&id("cut_spec", "cut-9.r2"))); @@ -807,7 +848,7 @@ mod tests { let questions = PipelineQuery { kinds: vec![K::Question], ..Default::default() }; let page = mind.query(&PipelineQuery { limit: Some(500), ..questions.clone() }).unwrap(); assert_eq!(page.matched, 201); - assert_eq!(page.items.len(), QUERY_LIMIT_MAX); + assert_eq!(page.items.len(), 200, "the cap, spelled out rather than read from the constant under test"); assert_eq!(page.items[0].admission.admitted_at, "2026-09-17T12:00:00Z", "the earliest batch first"); assert_eq!(page.items[0].id.id.0, id("question", "Q10"), "and inside it, key order"); assert_ne!(page.items[0].id.id.0, id("question", "Q1"), "Q1 sorts first by id and is admitted last"); @@ -824,7 +865,7 @@ mod tests { ); assert_eq!(page.matched as usize - page.items.len(), 1); - for (limit, expected) in [(None, QUERY_LIMIT_MAX), (Some(500), QUERY_LIMIT_MAX), (Some(0), 1), (Some(5), 5)] { + for (limit, expected) in [(None, 200), (Some(500), 200), (Some(0), 1), (Some(5), 5)] { let page = mind.query(&PipelineQuery { limit, ..questions.clone() }).unwrap(); assert_eq!(page.items.len(), expected, "limit {limit:?}"); assert_eq!(page.matched, 201, "the count is taken before the cap"); @@ -853,7 +894,9 @@ mod tests { // cut-11: a spec with a report, that report with a verdict, and two // findings, one of which is then fixed. committed(admit(&mut mind, vec![D::CutSpec(cut_spec("11", 1))])); - committed(admit(&mut mind, vec![D::CutReport(cut_report("11", 1))])); + // Two reports of one cut, one of them judged, so "this cut has a + // verdict" is not the same answer as "this report has one". + committed(admit(&mut mind, vec![D::CutReport(cut_report("11", 1)), D::CutReport(cut_report("11", 2))])); let f1 = id("finding", "cut-11.s1.F1"); let f2 = id("finding", "cut-11.s1.F2"); committed(admit(&mut mind, vec![ @@ -878,8 +921,8 @@ mod tests { ); assert_eq!( ids(&open.reports_without_verdict), - vec![id("cut_report", "cut-10.h1")], - "a report is not resolvable, so only the verdict that names it closes it" + vec![id("cut_report", "cut-10.h1"), id("cut_report", "cut-11.h2")], + "a report is not resolvable, so only the verdict that names it, exactly, closes it" ); } diff --git a/tools/eureka-cut9-mutations.psd1 b/tools/eureka-cut9-mutations.psd1 new file mode 100644 index 0000000..d64a3bf --- /dev/null +++ b/tools/eureka-cut9-mutations.psd1 @@ -0,0 +1,442 @@ +# Cut 9 mutations, V1-V23: the read side's rules, each entry restoring one +# permissiveness the cut closed and naming the test that must fail while it is +# applied. A rule with two weakenings has two entries: `Vn` is the revert, the +# rule simply absent, and `VnL` is the loosening, the rule weakened rather than +# removed. Run through Epiphany's harness from this repo: +# +# $env:CARGO_TARGET_DIR = 'C:\Users\Meta\.cargo-target-codex' +# powershell -File F:\Projects\Epiphany\tools\eureka-mutations.ps1 -Repo F:\Projects\Huginn ` +# -Entries tools/eureka-cut9-mutations.psd1 ` +# -Target crates/huginn-mind/src/docs.rs,crates/huginn-mind/src/query.rs ` +# -Test 'cargo test -p huginn-mind --lib' +# +# `docs.rs` is a target for V1 alone, which is the point of V1: in force has +# one owner, and the mutant that breaks it for the rules is the mutant that +# breaks it for the views. V1L is H41's own edit, listed here as well as in +# `eureka-cut8-mutations.psd1` -- one mutant, two suites, one owner. +# +# Three weakenings the spec names have no entry because they change no +# behaviour this Body can reach, and a mutation that cannot fail is not a +# proof: +# +# - The `semantic` check moved below the filters. It still refuses every query +# that carries one; only the work done before the refusal changes. +# - `resolutions_of` comparing a subject by `id` and ignoring `kind`. A +# pipeline id carries its own kind segment and the leaf refuses a +# `PipelineRef` whose kind and id disagree, so no two subjects of different +# kinds can share an id. +# - `assignments_of` comparing the repo and ignoring the instance. A5 refuses +# a stewardship naming another instance, so every stewardship a mind holds +# already names that mind. +@{ + Mutations = @( + @{ + Id = 'V1' + Rule = 'One owner for in force: the views derive status through the same closing_resolution the rules ask.' + Test = 'query::tests::status_is_the_derivation_admission_uses' + File = 'crates/huginn-mind/src/docs.rs' + Old = @' + self.resolutions().find(|&(key, resolution)| { + resolution.subject.kind == kind + && resolution.subject.id.0 == id + && !own(resolution) + && self.in_force(PipelineKind::Resolution, key) + }) +'@ + New = @' + let _ = (kind, id, own); + None +'@ + } + @{ + Id = 'V1L' + Rule = 'In force is recursive for a reader as it is for a rule: a withdrawn resolution stops closing its subject. H41''s edit, run against the views.' + Test = 'query::tests::status_is_the_derivation_admission_uses' + File = 'crates/huginn-mind/src/docs.rs' + Old = @' + && !own(resolution) + && self.in_force(PipelineKind::Resolution, key) +'@ + New = @' + && !own(resolution) + && { let _ = key; true } +'@ + } + @{ + Id = 'V2' + Rule = 'admitted_at is the receipt''s committed_at, never the store''s stored_at stamp (R-C).' + Test = 'query::tests::views_read_admitted_at_from_the_receipt_not_stored_at' + File = 'crates/huginn-mind/src/query.rs' + Old = ' admitted_at: receipt.committed_at.clone(),' + New = ' admitted_at: mind.raw_envelope(&write.document_type, &write.document_key).map_or_else(|| receipt.committed_at.clone(), |envelope| envelope.stored_at.clone()),' + } + @{ + Id = 'V3' + Rule = 'The facts come from a receipt''s writes only, so a document a later batch cited keeps the receipt that wrote it.' + Test = 'query::tests::views_join_admission_facts_from_the_receipt_that_wrote_them' + File = 'crates/huginn-mind/src/query.rs' + Old = ' for write in receipt.writes.iter() {' + New = ' for write in receipt.writes.iter().chain(&receipt.strong_reads) {' + } + @{ + Id = 'V4' + Rule = 'A row no receipt wrote refuses, from view and query alike, and is never skipped.' + Test = 'query::tests::views_join_admission_facts_from_the_receipt_that_wrote_them' + Edits = @( + @{ + File = 'crates/huginn-mind/src/query.rs' + Old = ' let view = reader.view_of(held)?;' + New = ' let Ok(view) = reader.view_of(held) else { continue };' + } + @{ + File = 'crates/huginn-mind/src/query.rs' + Old = ' reader.held(id.kind, &id.id.0).map(|held| reader.view_of(held)).transpose()' + New = ' Ok(reader.held(id.kind, &id.id.0).and_then(|held| reader.view_of(held).ok()))' + } + ) + } + @{ + Id = 'V4L' + Rule = 'Two receipts naming one write is an integrity fault, not a choice of which to believe.' + Test = 'query::tests::views_join_admission_facts_from_the_receipt_that_wrote_them' + File = 'crates/huginn-mind/src/query.rs' + Old = @' + if facts.insert(identity, landed).is_some() { + return Err(integrity(&write.document_type, &write.document_key, "is written by two receipts")); + } +'@ + New = @' + facts.insert(identity, landed); +'@ + } + @{ + Id = 'V5' + Rule = 'The cap: a caller''s limit is clamped into 1..=QUERY_LIMIT_MAX.' + Test = 'query::tests::query_orders_by_admitted_at_then_id_and_caps_at_200_with_the_match_count' + File = 'crates/huginn-mind/src/query.rs' + Old = '.clamp(1, QUERY_LIMIT_MAX));' + New = '.max(1));' + } + @{ + Id = 'V5L' + Rule = 'The cap is 200, and the test spells it out rather than reading the constant under test.' + Test = 'query::tests::query_orders_by_admitted_at_then_id_and_caps_at_200_with_the_match_count' + File = 'crates/huginn-mind/src/query.rs' + Old = 'pub const QUERY_LIMIT_MAX: usize = 200;' + New = 'pub const QUERY_LIMIT_MAX: usize = 201;' + } + @{ + Id = 'V6' + Rule = 'The order is (admitted_at, id): the batch decides, and the key only breaks a tie inside one.' + Test = 'query::tests::query_orders_by_admitted_at_then_id_and_caps_at_200_with_the_match_count' + File = 'crates/huginn-mind/src/query.rs' + Old = @' + (&left.admission.admitted_at, &left.id.id.0).cmp(&(&right.admission.admitted_at, &right.id.id.0)) +'@ + New = @' + left.id.id.0.cmp(&right.id.id.0) +'@ + } + @{ + Id = 'V6L' + Rule = 'A tie inside one batch falls to the key, which is root-first and so is not the image''s (type, key) order.' + Test = 'query::tests::query_orders_by_admitted_at_then_id_and_caps_at_200_with_the_match_count' + File = 'crates/huginn-mind/src/query.rs' + Old = @' + (&left.admission.admitted_at, &left.id.id.0).cmp(&(&right.admission.admitted_at, &right.id.id.0)) +'@ + New = @' + left.admission.admitted_at.cmp(&right.admission.admitted_at) +'@ + } + @{ + Id = 'V7' + Rule = 'matched is the count before the cap, so a truncated page is visible as one.' + Test = 'query::tests::query_orders_by_admitted_at_then_id_and_caps_at_200_with_the_match_count' + File = 'crates/huginn-mind/src/query.rs' + Old = @' + let matched = items.len() as u32; + ordered(&mut items); + let limit = query.limit.map_or(QUERY_LIMIT_MAX, |limit| (limit as usize).clamp(1, QUERY_LIMIT_MAX)); + items.truncate(limit); +'@ + New = @' + ordered(&mut items); + let limit = query.limit.map_or(QUERY_LIMIT_MAX, |limit| (limit as usize).clamp(1, QUERY_LIMIT_MAX)); + items.truncate(limit); + let matched = items.len() as u32; +'@ + } + @{ + Id = 'V8' + Rule = 'semantic is refused typed until Cut 11, never answered with an empty page.' + Test = 'query::tests::semantic_query_refuses_typed_until_wired' + File = 'crates/huginn-mind/src/query.rs' + Old = @' + return Err(MindRefusal::Unavailable { + detail: "semantic query: the index is not wired (Cut 11)".into(), + }); +'@ + New = @' + return Ok(PipelineQueryPage { items: vec![], matched: 0 }); +'@ + } + @{ + Id = 'V9' + Rule = 'History is in the scope''s own sequence order, not the image''s key order.' + Test = 'query::tests::a_subjects_history_lists_every_resolution_with_its_status_and_receipt' + File = 'crates/huginn-mind/src/query.rs' + Old = ' records.sort_by_key(|(sequence, _)| *sequence);' + New = ' let _ = &records;' + } + @{ + Id = 'V10' + Rule = 'A subject''s history is selected by the subject field, so a resolution''s own withdrawals are not in it.' + Test = 'query::tests::a_subjects_history_lists_every_resolution_with_its_status_and_receipt' + File = 'crates/huginn-mind/src/query.rs' + Old = @' + .resolutions_of(subject) + .map(|(key, resolution)| (resolution.sequence, key.to_string())) +'@ + New = @' + .resolutions() + .map(|(key, resolution)| { let _ = subject; (resolution.sequence, key.to_string()) }) +'@ + } + @{ + Id = 'V11' + Rule = 'A repo''s history is selected by (this mind, this repo), so another repo''s assignments are another history.' + Test = 'query::tests::a_repos_stewardship_history_on_a_mind_lists_every_assignment' + File = 'crates/huginn-mind/src/query.rs' + Old = @' + .assignments_of(reader.mind.instance(), repo) + .map(|(key, stewardship)| (stewardship.sequence, key.to_string())) +'@ + New = @' + .stewardships() + .map(|(key, stewardship)| { let _ = repo; (stewardship.sequence, key.to_string()) }) +'@ + } + @{ + Id = 'V12' + Rule = 'Open questions, findings and follow-ups are the ones in force.' + Test = 'query::tests::open_items_are_derived_from_in_force_and_citation' + File = 'crates/huginn-mind/src/query.rs' + Old = ' of_kind(kind).filter(|view| view.status == PipelineStatus::InForce).cloned().collect::>()' + New = ' of_kind(kind).cloned().collect::>()' + } + @{ + Id = 'V13' + Rule = 'A spec without a report is open only while it is in force: a superseded revision is not open work.' + Test = 'query::tests::open_items_are_derived_from_in_force_and_citation' + File = 'crates/huginn-mind/src/query.rs' + Old = ' specs_without_report: in_force(PipelineKind::CutSpec)' + New = ' specs_without_report: of_kind(PipelineKind::CutSpec).cloned().collect::>()' + } + @{ + Id = 'V14' + Rule = 'A report is closed by the verdict that names it; a report is not resolvable, so nothing else closes it.' + Test = 'query::tests::open_items_are_derived_from_in_force_and_citation' + File = 'crates/huginn-mind/src/query.rs' + Old = ' .filter(|view| !judged.contains(&view.id.id.0.as_str()))' + New = ' .filter(|view| { let _ = &judged; true })' + } + @{ + Id = 'V14L' + Rule = 'The citation is compared as the exact id, not as the cut it belongs to: one cut may have two reports and one verdict.' + Test = 'query::tests::open_items_are_derived_from_in_force_and_citation' + File = 'crates/huginn-mind/src/query.rs' + Old = ' .filter(|view| !judged.contains(&view.id.id.0.as_str()))' + New = ' .filter(|view| !judged.iter().any(|cited| root_and_local(cited).1.split(''.'').next() == root_and_local(&view.id.id.0).1.split(''.'').next()))' + } + @{ + Id = 'V15' + Rule = 'Open items are one campaign''s, selected by the key''s root.' + Test = 'query::tests::open_items_are_derived_from_in_force_and_citation' + File = 'crates/huginn-mind/src/query.rs' + Old = ' .filter(|view| root_and_local(&view.id.id.0).0 == campaign.0)' + New = ' .filter(|view| { let _ = &campaign; true })' + } + @{ + Id = 'V16' + Rule = 'The cut filter is the label, not every cut.' + Test = 'query::tests::query_filters_each_select_by_one_field' + File = 'crates/huginn-mind/src/query.rs' + Old = ' && !root_and_local(&base.key).1.starts_with(&format!("cut-{}.", cut.0))' + New = ' && !root_and_local(&base.key).1.starts_with("cut-")' + } + @{ + Id = 'V16L' + Rule = 'The cut prefix ends at the dot, so cut-1 does not match cut-10.' + Test = 'query::tests::query_filters_each_select_by_one_field' + File = 'crates/huginn-mind/src/query.rs' + Old = ' && !root_and_local(&base.key).1.starts_with(&format!("cut-{}.", cut.0))' + New = ' && !root_and_local(&base.key).1.starts_with(&format!("cut-{}", cut.0))' + } + @{ + Id = 'V17' + Rule = 'A filter reaches a resolution through its base, the document it is a record of.' + Test = 'query::tests::query_filters_each_select_by_one_field' + File = 'crates/huginn-mind/src/query.rs' + Old = @' + let mut current = held; + while let PipelineDocument::Resolution(resolution) = ¤t.document { + let Some(subject) = self.held(resolution.subject.kind, &resolution.subject.id.0) else { + return current; + }; + current = subject; + } + current +'@ + New = @' + held +'@ + } + @{ + Id = 'V17L' + Rule = 'The base is walked to the first non-resolution, not one step: a withdrawal of a resolution is two steps from its document.' + Test = 'query::tests::query_filters_each_select_by_one_field' + File = 'crates/huginn-mind/src/query.rs' + Old = ' while let PipelineDocument::Resolution(resolution) = ¤t.document {' + New = ' if let PipelineDocument::Resolution(resolution) = ¤t.document {' + } + @{ + Id = 'V18' + Rule = 'campaign is the key''s root segment.' + Test = 'query::tests::query_filters_each_select_by_one_field' + File = 'crates/huginn-mind/src/query.rs' + Old = @' + if let Some(campaign) = &query.campaign + && root_and_local(&held.key).0 != campaign.0 + { + return false; + } +'@ + New = '' + } + @{ + Id = 'V18L' + Rule = 'The root, not a campaign field: a stewardship under an instance root and a resolution under its subject''s root carry no campaign of their own.' + Test = 'query::tests::query_filters_each_select_by_one_field' + File = 'crates/huginn-mind/src/query.rs' + Old = ' && root_and_local(&held.key).0 != campaign.0' + New = ' && !matches!(&held.document, PipelineDocument::Campaign(value) if value.slug == *campaign)' + } + @{ + Id = 'V19' + Rule = 'kinds selects by the document''s kind, and an empty list matches every kind.' + Test = 'query::tests::query_filters_each_select_by_one_field' + File = 'crates/huginn-mind/src/query.rs' + Old = @' + if !query.kinds.is_empty() && !query.kinds.contains(&held.kind) { + return false; + } +'@ + New = '' + } + @{ + Id = 'V20' + Rule = 'in_force selects by the derived status.' + Test = 'query::tests::query_filters_each_select_by_one_field' + File = 'crates/huginn-mind/src/query.rs' + Old = @' + if let Some(in_force) = query.in_force + && in_force != (view.status == PipelineStatus::InForce) + { + return false; + } +'@ + New = '' + } + @{ + Id = 'V20L' + Rule = 'in_force selects the status the caller asked for, not its opposite.' + Test = 'query::tests::query_filters_each_select_by_one_field' + File = 'crates/huginn-mind/src/query.rs' + Old = ' && in_force != (view.status == PipelineStatus::InForce)' + New = ' && in_force == (view.status == PipelineStatus::InForce)' + } + @{ + Id = 'V21' + Rule = 'faculty selects by the receipt''s provenance: attribution filters, and grants nothing (ruling 18).' + Test = 'query::tests::query_filters_each_select_by_one_field' + File = 'crates/huginn-mind/src/query.rs' + Old = @' + if let Some(faculty) = query.faculty + && view.admission.provenance.faculty != faculty + { + return false; + } +'@ + New = '' + } + @{ + Id = 'V22' + Rule = 'The admission window selects by admitted_at.' + Test = 'query::tests::query_filters_each_select_by_one_field' + Edits = @( + @{ + File = 'crates/huginn-mind/src/query.rs' + Old = @' + if let Some(after) = &query.admitted_after + && view.admission.admitted_at <= after.0 + { + return false; + } +'@ + New = '' + } + @{ + File = 'crates/huginn-mind/src/query.rs' + Old = @' + if let Some(before) = &query.admitted_before + && view.admission.admitted_at >= before.0 + { + return false; + } +'@ + New = '' + } + ) + } + @{ + Id = 'V22L' + Rule = 'Both ends of the window are exclusive at the exact boundary.' + Test = 'query::tests::query_filters_each_select_by_one_field' + Edits = @( + @{ + File = 'crates/huginn-mind/src/query.rs' + Old = ' && view.admission.admitted_at <= after.0' + New = ' && view.admission.admitted_at < after.0' + } + @{ + File = 'crates/huginn-mind/src/query.rs' + Old = ' && view.admission.admitted_at >= before.0' + New = ' && view.admission.admitted_at > before.0' + } + ) + } + @{ + Id = 'V23' + Rule = 'repo selects by the repo the document names, read through the base.' + Test = 'query::tests::query_filters_each_select_by_one_field' + File = 'crates/huginn-mind/src/query.rs' + Old = @' + if let Some(repo) = &query.repo + && !repo_matches(&base.document, repo) + { + return false; + } +'@ + New = '' + } + @{ + Id = 'V23L' + Rule = 'A campaign matches through the repos it lists, as the five single-repo kinds match through their field.' + Test = 'query::tests::query_filters_each_select_by_one_field' + File = 'crates/huginn-mind/src/query.rs' + Old = ' D::Campaign(campaign) => campaign.repos.contains(repo),' + New = ' D::Campaign(_) => false,' + } + ) +}