Supersession: one lineage rule for targets, cut_specs and rulings; tests pin every path

same_lineage replaces the Target and CutSpec arms. A target is superseded only within its campaign, a cut_spec within its campaign and cut, a ruling by any ruling (ruling supersession-lineage). Tests cover same-batch, first/last supersessor, subject in batch, cut_spec cross-campaign, and ruling cross-campaign.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
Meta Vens 2026-10-22 18:38:22 +02:00
commit 528a7f7e5f

View file

@ -794,21 +794,11 @@ fn resolution_rule(docs: &Docs, resolution: &PipelineResolution) -> Result<(), M
}
}
match &resolution.outcome {
ResolutionOutcome::Superseded { by } if subject_kind == K::CutSpec => {
let Some(D::CutSpec(subject)) = docs.find(K::CutSpec, subject_id) else { return Ok(()) };
ResolutionOutcome::Superseded { by } => {
let Some(subject) = docs.find(subject_kind, subject_id) else { return Ok(()) };
for supersessor in by {
if let Some(D::CutSpec(spec)) = docs.find(K::CutSpec, &supersessor.id.0)
&& spec.cut != subject.cut
{
return Err(incompatible());
}
}
}
ResolutionOutcome::Superseded { by } if subject_kind == K::Target => {
let Some(D::Target(subject)) = docs.find(K::Target, subject_id) else { return Ok(()) };
for supersessor in by {
if let Some(D::Target(target)) = docs.find(K::Target, &supersessor.id.0)
&& target.campaign != subject.campaign
if let Some(next) = docs.find(subject_kind, &supersessor.id.0)
&& !same_lineage(subject, next)
{
return Err(incompatible());
}
@ -825,6 +815,17 @@ fn resolution_rule(docs: &Docs, resolution: &PipelineResolution) -> Result<(), M
Ok(())
}
/// The one supersession lineage rule (ruling `supersession-lineage`). A target is
/// superseded only by a target of its campaign, a cut_spec only by a cut_spec of its
/// campaign and cut, and a ruling by any ruling: operator directions cut across campaigns.
fn same_lineage(subject: &D, supersessor: &D) -> bool {
match (subject, supersessor) {
(D::Target(old), D::Target(new)) => old.campaign == new.campaign,
(D::CutSpec(old), D::CutSpec(new)) => old.campaign == new.campaign && old.cut == new.cut,
_ => true,
}
}
/// A10: a write whose identity the image already holds. Every kind answers
/// the same way now that a resolution key carries a sequence: the identity is
/// taken. Whether a subject is closed is A8's question, not this one's.
@ -2831,6 +2832,76 @@ mod tests {
committed(admit(&mut mind, repo_revision(3, &[REPO, OTHER_REPO])));
}
/// A target of the first campaign (`CAMPAIGN`) at `revision`, naming no repos.
fn own_target(revision: u32) -> D {
target(revision, &[INVARIANT])
}
const FOREIGN_TARGET: &str = "other-campaign:target:r1";
fn supersession_refused(outcome: PipelineAdmissionOutcome, subject_kind: K) -> bool {
refusal(outcome) == MindRefusal::IncompatibleResolution { subject_kind, outcome: "Superseded".into() }
}
#[test]
fn a_foreign_target_in_the_same_batch_does_not_supersede() {
let mut mind = seeded();
let mut batch = other_campaign(&[REPO], &[]);
batch.push(resolution(r(K::Target, &id("target", "r1")), superseded(&[r(K::Target, FOREIGN_TARGET)])));
assert!(supersession_refused(admit(&mut mind, batch), K::Target));
}
#[test]
fn every_supersessor_of_a_target_is_checked_whichever_is_foreign() {
for foreign_first in [false, true] {
let mut mind = seeded();
committed(admit(&mut mind, other_campaign(&[REPO], &[])));
let own = r(K::Target, &id("target", "r2"));
let foreign = r(K::Target, FOREIGN_TARGET);
let by = if foreign_first { [foreign, own] } else { [own, foreign] };
let batch = vec![own_target(2), resolution(r(K::Target, &id("target", "r1")), superseded(&by))];
assert!(supersession_refused(admit(&mut mind, batch), K::Target), "foreign first: {foreign_first}");
}
}
#[test]
fn a_new_campaigns_target_in_its_own_batch_is_not_closed_by_a_foreign_one() {
let mut mind = seeded();
let mut batch = other_campaign(&[REPO], &[]);
batch.push(resolution(r(K::Target, FOREIGN_TARGET), superseded(&[r(K::Target, &id("target", "r1"))])));
assert!(supersession_refused(admit(&mut mind, batch), K::Target));
}
#[test]
fn a_cut_spec_is_superseded_only_within_its_campaign() {
let mut mind = seeded();
committed(admit(&mut mind, vec![spec_on("1", REPO)]));
let mut batch = other_campaign(&[REPO], &[]);
let D::CutSpec(mut foreign) = spec_on("1", REPO) else { panic!("a spec") };
foreign.campaign = "other-campaign".into();
batch.push(D::CutSpec(foreign));
committed(admit(&mut mind, batch));
let lone = vec![resolution(r(K::CutSpec, &id("cut_spec", "cut-1.r1")), superseded(&[r(K::CutSpec, "other-campaign:cut_spec:cut-1.r1")]))];
assert!(supersession_refused(admit(&mut mind, lone), K::CutSpec));
// Nothing was written: this campaign's own revision still lands.
let D::CutSpec(mut next) = spec_on("1", REPO) else { panic!("a spec") };
next.revision = 2;
let this = r(K::CutSpec, &id("cut_spec", "cut-1.r2"));
committed(admit(&mut mind, vec![D::CutSpec(next), resolution(r(K::CutSpec, &id("cut_spec", "cut-1.r1")), superseded(&[this]))]));
}
#[test]
fn a_ruling_is_superseded_by_a_ruling_of_any_campaign() {
let mut mind = seeded();
committed(admit(&mut mind, vec![D::Ruling(ruling("old"))]));
let mut batch = other_campaign(&[REPO], &[]);
let mut foreign = ruling("new");
foreign.campaign = "other-campaign".into();
batch.push(D::Ruling(foreign));
batch.push(resolution(r(K::Ruling, &id("ruling", "old")), superseded(&[r(K::Ruling, "other-campaign:ruling:new")])));
committed(admit(&mut mind, batch));
}
#[test]
fn a_hand_off_derives_this_minds_side_only() {
let key = format!("{INSTANCE}:hand_off:{OTHER_INSTANCE}.gamecult_-epiphany.2026-09-16");