huginn-mind: report heads match by commit, findings defer to cut specs, MindSteward is Life
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
b85cd5d7d5
commit
db8430c6fe
4 changed files with 62 additions and 4 deletions
|
|
@ -438,7 +438,7 @@ fn check(docs: &Docs, staged: &Staged, mind: &Slug) -> Result<(), MindRefusal> {
|
|||
if report.branch != spec.branch {
|
||||
return Err(MindRefusal::SpecMismatch { field: "branch".into() });
|
||||
}
|
||||
if !report.commits.iter().any(|commit| commit.sha == report.range.head) {
|
||||
if !report.commits.iter().any(|commit| commit.sha.names_same_commit(&report.range.head)) {
|
||||
return Err(MindRefusal::RangeOutsideCommits { head: report.range.head.0.clone() });
|
||||
}
|
||||
Ok(())
|
||||
|
|
@ -579,7 +579,7 @@ fn matrix(subject: PipelineKind, outcome: &ResolutionOutcome) -> bool {
|
|||
(K::CutSpec, O::Superseded { by }) => all(by, K::CutSpec),
|
||||
(K::CutSpec, O::Withdrawn { .. }) => true,
|
||||
(K::Finding, O::Fixed { by, .. }) => by.as_ref().is_none_or(|report| report.kind == K::CutReport),
|
||||
(K::Finding, O::Deferred { to }) => to.kind == K::FollowUp,
|
||||
(K::Finding, O::Deferred { to }) => matches!(to.kind, K::FollowUp | K::CutSpec),
|
||||
(K::Finding, O::Recorded { .. } | O::Withdrawn { .. }) => true,
|
||||
(K::FollowUp, O::Fixed { by, .. }) => by.as_ref().is_none_or(|report| report.kind == K::CutReport),
|
||||
(K::FollowUp, O::Superseded { by }) => all(by, K::FollowUp),
|
||||
|
|
@ -1910,6 +1910,49 @@ mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_report_head_may_spell_its_commit_short_or_full() {
|
||||
let full = "5f98228d0123456789abcdef0123456789abcdef";
|
||||
let mut mind = seeded();
|
||||
committed(admit(&mut mind, vec![D::CutSpec(cut_spec("1", 1))]));
|
||||
// The commit is spelled short and the head full, and the reverse.
|
||||
let mut report = cut_report("1", 1);
|
||||
report.range.head = epiphany_pipeline::Sha(full.into());
|
||||
committed(admit(&mut mind, vec![D::CutReport(report)]));
|
||||
let mut report = cut_report("1", 2);
|
||||
report.commits[0].sha = epiphany_pipeline::Sha(full.into());
|
||||
committed(admit(&mut mind, vec![D::CutReport(report)]));
|
||||
// A head that is no commit's prefix, long or short, is outside.
|
||||
let mut report = cut_report("1", 3);
|
||||
report.range.head = epiphany_pipeline::Sha("5f98229d0123456789abcdef0123456789abcdef".into());
|
||||
assert!(matches!(refusal(admit(&mut mind, vec![D::CutReport(report)])), MindRefusal::RangeOutsideCommits { .. }));
|
||||
let mut report = cut_report("1", 3);
|
||||
report.commits[0].sha = epiphany_pipeline::Sha(full.into());
|
||||
report.range.head = epiphany_pipeline::Sha("5f98229".into());
|
||||
assert!(matches!(refusal(admit(&mut mind, vec![D::CutReport(report)])), MindRefusal::RangeOutsideCommits { .. }));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_finding_defers_to_a_cut_spec() {
|
||||
let finding_ref = r(K::Finding, &id("finding", "cut-1.s1.F1"));
|
||||
let defer_to = |to: PipelineRef| resolution(finding_ref.clone(), ResolutionOutcome::Deferred { to });
|
||||
committed(admit(&mut world(), vec![defer_to(r(K::CutSpec, &id("cut_spec", "cut-1.r1")))]));
|
||||
// A spec superseded since is no longer a place to defer to.
|
||||
let mut mind = world();
|
||||
committed(admit(&mut mind, vec![
|
||||
D::CutSpec(cut_spec("1", 2)),
|
||||
resolution(r(K::CutSpec, &id("cut_spec", "cut-1.r1")), superseded(&[r(K::CutSpec, &id("cut_spec", "cut-1.r2"))])),
|
||||
]));
|
||||
assert_eq!(
|
||||
refusal(admit(&mut mind, vec![defer_to(r(K::CutSpec, &id("cut_spec", "cut-1.r1")))])),
|
||||
MindRefusal::CitesResolvedDocument { kind: K::CutSpec, id: id("cut_spec", "cut-1.r1") }
|
||||
);
|
||||
assert_eq!(
|
||||
refusal(admit(&mut world(), vec![defer_to(r(K::Ruling, &id("ruling", "R1")))])),
|
||||
MindRefusal::IncompatibleResolution { subject_kind: K::Finding, outcome: "Deferred".into() }
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verdict_vocabulary_binds_claims_to_findings_promises_and_mutations() {
|
||||
let mut mind = seeded();
|
||||
|
|
|
|||
|
|
@ -1046,6 +1046,9 @@ mod tests {
|
|||
// `faculty`: attribution selects, and grants nothing (ruling 18).
|
||||
assert_eq!(by("faculty", &["Soul"]), vec![id("ruling", "R2")]);
|
||||
assert!(!by("faculty", &["Hands"]).contains(&id("ruling", "R2")));
|
||||
// The renamed faculty is selectable by its new name only.
|
||||
assert!(by("faculty", &["Life"]).is_empty());
|
||||
assert!(invalid("fields[0].values", "MindSteward")(&refused(&mind, &with(Selection::default(), any_of("faculty", &["MindSteward"])))));
|
||||
|
||||
// A repo is named by its identity, which is case-insensitive.
|
||||
assert_eq!(by("repo", &["GAMECULT/EPIPHANY"]), epiphany);
|
||||
|
|
|
|||
|
|
@ -36,7 +36,8 @@ pub enum Faculty {
|
|||
Imagination,
|
||||
Hands,
|
||||
Soul,
|
||||
MindSteward,
|
||||
#[serde(alias = "MindSteward")]
|
||||
Life,
|
||||
Eyes,
|
||||
Operator,
|
||||
}
|
||||
|
|
@ -295,6 +296,17 @@ mod tests {
|
|||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use super::*;
|
||||
|
||||
/// A receipt written before the rename spells the faculty `MindSteward`;
|
||||
/// it decodes as `Life`, and `Life` is what a new one writes.
|
||||
#[test]
|
||||
fn life_is_the_faculty_and_the_old_spelling_decodes() {
|
||||
let old = rmp_serde::to_vec_named("MindSteward").unwrap();
|
||||
assert_eq!(rmp_serde::from_slice::<Faculty>(&old).unwrap(), Faculty::Life);
|
||||
let new = rmp_serde::to_vec_named(&Faculty::Life).unwrap();
|
||||
assert_eq!(rmp_serde::from_slice::<String>(&new).unwrap(), "Life");
|
||||
}
|
||||
|
||||
use crate::fixtures::{INSTANCE, admit, committed, id, instance, now, opened, prepare, provenance, question, refusal, seed, slug};
|
||||
use crate::mind::schema_cache;
|
||||
use crate::store::test_stores::MemoryStore;
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ pub(crate) enum Alias {
|
|||
Outcome,
|
||||
}
|
||||
|
||||
const FACULTIES: [&str; 7] = ["SelfFaculty", "Imagination", "Hands", "Soul", "MindSteward", "Eyes", "Operator"];
|
||||
const FACULTIES: [&str; 7] = ["SelfFaculty", "Imagination", "Hands", "Soul", "Life", "Eyes", "Operator"];
|
||||
const SEVERITIES: [&str; 4] = ["Blocker", "High", "Medium", "Low"];
|
||||
const CONFIDENCES: [&str; 2] = ["Confirmed", "Plausible"];
|
||||
const ORIGINS: [&str; 2] = ["Introduced", "PreExisting"];
|
||||
|
|
|
|||
Loading…
Reference in a new issue