use crate::observation::wire::{Diagnostic, Envelope, Payload};
use std::collections::{BTreeMap, BTreeSet};
#[derive(Debug, Clone)]
pub(crate) enum ResolvedState {
Active(Envelope),
Superseded {
by: String,
original: Envelope,
control_uid: String,
},
Retracted {
original: Envelope,
control_uid: String,
},
}
impl ResolvedState {
pub(crate) fn is_active(&self) -> bool {
matches!(self, ResolvedState::Active(_))
}
pub(crate) fn original(&self) -> &Envelope {
match self {
ResolvedState::Active(e)
| ResolvedState::Superseded { original: e, .. }
| ResolvedState::Retracted { original: e, .. } => e,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum ControlOutcome {
Applied,
Duplicate,
Inert {
reason: String,
},
}
#[derive(Debug, Clone)]
pub(crate) struct Resolution {
states: BTreeMap<String, ResolvedState>,
#[cfg_attr(
not(test),
expect(dead_code, reason = "PHASE-04 / unreachable by design")
)]
diagnostics: Vec<Diagnostic>,
all: BTreeMap<String, Envelope>,
#[expect(dead_code, reason = "populated in resolve(), read only in tests")]
ordering: BTreeMap<String, (String, String)>,
}
impl Resolution {
pub(crate) fn state(&self, uid: &str) -> Option<&ResolvedState> {
self.states.get(uid)
}
#[cfg_attr(not(test), expect(dead_code, reason = "PHASE-04"))]
pub(crate) fn diagnostics(&self) -> &[Diagnostic] {
&self.diagnostics
}
pub(crate) fn all_envelopes(&self) -> impl Iterator<Item = &Envelope> {
self.all.values()
}
pub(crate) fn active(&self) -> Vec<&Envelope> {
let mut active: Vec<&Envelope> = self
.states
.values()
.filter(|s| s.is_active())
.map(ResolvedState::original)
.collect();
active.sort_by(|a, b| {
canonical_cmp_key(a, b)
.reverse()
.then_with(|| a.uid.cmp(&b.uid).reverse())
});
active
}
#[cfg_attr(
not(test),
expect(
dead_code,
reason = "superseded by all_envelopes() in query path; retained for clarity"
)
)]
pub(crate) fn history(&self) -> Vec<&Envelope> {
let mut all: Vec<&Envelope> = self.states.values().map(ResolvedState::original).collect();
all.sort_by(|a, b| {
canonical_cmp_key(a, b)
.reverse()
.then_with(|| a.uid.cmp(&b.uid).reverse())
});
all
}
#[expect(dead_code, reason = "PHASE-04")]
pub(crate) fn envelope(&self, uid: &str) -> Option<&Envelope> {
self.all.get(uid)
}
#[cfg_attr(not(test), expect(dead_code, reason = "PHASE-04"))]
pub(crate) fn resolved_terminus(&self, uid: &str) -> String {
let mut current: String = uid.to_string();
for _ in 0..1_000 {
match self.states.get(¤t) {
Some(ResolvedState::Superseded { by, .. }) => {
if *by == current {
break;
}
current = by.clone();
}
_ => break,
}
}
current
}
#[expect(dead_code, reason = "PHASE-04")]
pub(crate) fn controls_targeting(&self, uid: &str) -> Vec<&Envelope> {
let mut controls: Vec<&Envelope> = self
.all
.values()
.filter(|e| match &e.payload {
Payload::Supersession { old_uid, .. } => old_uid == uid,
Payload::Retraction { target_uid, .. } => target_uid == uid,
_ => false,
})
.collect();
controls.sort_by(|a, b| canonical_cmp(a, b));
controls
}
pub(crate) fn correction_chain<'a>(&'a self, uid: &'a str) -> Vec<(&'a str, &'a Envelope)> {
let mut chain: Vec<(&str, &Envelope)> = Vec::new();
let mut current = uid;
for _ in 0..1_000 {
let Some(envelope) = self.all.get(current) else {
break;
};
chain.push((current, envelope));
match self.states.get(current) {
Some(ResolvedState::Superseded { by, .. }) => {
if by == current {
break;
}
current = by;
}
_ => break,
}
}
chain
}
}
fn canonical_cmp_key(a: &Envelope, b: &Envelope) -> std::cmp::Ordering {
a.recorded_at
.cmp(&b.recorded_at)
.then_with(|| a.uid.cmp(&b.uid))
}
fn canonical_cmp(a: &Envelope, b: &Envelope) -> std::cmp::Ordering {
canonical_cmp_key(a, b)
}
fn sort_canonical(envelopes: &mut [Envelope]) {
envelopes.sort_by(canonical_cmp);
}
pub(crate) fn resolve(envelopes: Vec<Envelope>) -> (Resolution, BTreeMap<String, ControlOutcome>) {
let mut envelopes = envelopes;
sort_canonical(&mut envelopes);
let mut primaries: BTreeMap<String, Envelope> = BTreeMap::new();
let mut controls: Vec<Envelope> = Vec::new();
let mut all: BTreeMap<String, Envelope> = BTreeMap::new();
let mut ordering: BTreeMap<String, (String, String)> = BTreeMap::new();
for e in envelopes {
ordering.insert(e.uid.clone(), (e.recorded_at.clone(), e.uid.clone()));
if e.is_primary() {
primaries.insert(e.uid.clone(), e.clone());
} else {
controls.push(e.clone());
}
all.insert(e.uid.clone(), e);
}
let mut supersessions: BTreeMap<String, (String, String)> = BTreeMap::new();
let mut retractions: BTreeMap<String, String> = BTreeMap::new();
let mut applied_supersessions: BTreeSet<(String, String)> = BTreeSet::new(); let mut applied_retractions: BTreeSet<String> = BTreeSet::new();
let mut diagnostics: Vec<Diagnostic> = Vec::new();
let mut outcomes: BTreeMap<String, ControlOutcome> = BTreeMap::new();
for c in &controls {
let outcome = match &c.payload {
Payload::Supersession {
old_uid,
replacement_uid,
..
} => apply_supersession(
old_uid,
replacement_uid,
&c.uid,
&primaries,
&supersessions,
&retractions,
&applied_supersessions,
&mut diagnostics,
),
Payload::Retraction { target_uid, .. } => apply_retraction(
target_uid,
&c.uid,
&primaries,
&supersessions,
&retractions,
&applied_retractions,
&mut diagnostics,
),
_ => ControlOutcome::Inert {
reason: "control payload does not match control kind".to_string(),
},
};
match &outcome {
ControlOutcome::Applied => match &c.payload {
Payload::Supersession {
old_uid,
replacement_uid,
..
} => {
supersessions.insert(old_uid.clone(), (replacement_uid.clone(), c.uid.clone()));
applied_supersessions.insert((old_uid.clone(), replacement_uid.clone()));
}
Payload::Retraction { target_uid, .. } => {
retractions.insert(target_uid.clone(), c.uid.clone());
applied_retractions.insert(target_uid.clone());
}
_ => {}
},
ControlOutcome::Duplicate => {
match &c.payload {
Payload::Supersession {
old_uid,
replacement_uid,
..
} => {
applied_supersessions.insert((old_uid.clone(), replacement_uid.clone()));
}
Payload::Retraction { target_uid, .. } => {
applied_retractions.insert(target_uid.clone());
}
_ => {}
}
}
ControlOutcome::Inert { .. } => {}
}
outcomes.insert(c.uid.clone(), outcome);
}
let mut states: BTreeMap<String, ResolvedState> = BTreeMap::new();
for (uid, env) in &primaries {
let state = if let Some(control_uid) = retractions.get(uid) {
ResolvedState::Retracted {
original: env.clone(),
control_uid: control_uid.clone(),
}
} else if let Some((by, control_uid)) = supersessions.get(uid) {
ResolvedState::Superseded {
by: by.clone(),
original: env.clone(),
control_uid: control_uid.clone(),
}
} else {
ResolvedState::Active(env.clone())
};
states.insert(uid.clone(), state);
}
(
Resolution {
states,
diagnostics,
all,
ordering,
},
outcomes,
)
}
#[expect(
clippy::too_many_arguments,
reason = "resolution context requires all state maps"
)]
fn apply_supersession(
old_uid: &str,
replacement_uid: &str,
control_uid: &str,
primaries: &BTreeMap<String, Envelope>,
supersessions: &BTreeMap<String, (String, String)>,
retractions: &BTreeMap<String, String>,
applied_supersessions: &BTreeSet<(String, String)>,
diagnostics: &mut Vec<Diagnostic>,
) -> ControlOutcome {
if applied_supersessions.contains(&(old_uid.to_string(), replacement_uid.to_string())) {
return ControlOutcome::Duplicate;
}
let Some(old_primary) = primaries.get(old_uid) else {
let reason =
format!("supersession target {old_uid} does not exist as a primary observation");
diagnostics.push(Diagnostic::new(control_uid.to_string(), reason.clone()));
return ControlOutcome::Inert { reason };
};
let Some(replacement_primary) = primaries.get(replacement_uid) else {
let reason = format!(
"supersession replacement {replacement_uid} does not exist as a primary observation"
);
diagnostics.push(Diagnostic::new(control_uid.to_string(), reason.clone()));
return ControlOutcome::Inert { reason };
};
if old_primary.kind() != replacement_primary.kind() {
let reason = format!(
"supersession replacement kind {:?} is incompatible with target kind {:?}",
replacement_primary.kind(),
old_primary.kind()
);
diagnostics.push(Diagnostic::new(control_uid.to_string(), reason.clone()));
return ControlOutcome::Inert { reason };
}
if supersessions.contains_key(old_uid) {
let reason = format!("supersession target {old_uid} already has an effective supersession");
diagnostics.push(Diagnostic::new(control_uid.to_string(), reason.clone()));
return ControlOutcome::Inert { reason };
}
if retractions.contains_key(old_uid) {
let reason =
format!("supersession target {old_uid} is already retracted (retraction dominates)");
diagnostics.push(Diagnostic::new(control_uid.to_string(), reason.clone()));
return ControlOutcome::Inert { reason };
}
if would_create_cycle(old_uid, replacement_uid, supersessions) {
let reason = format!("supersession {old_uid} -> {replacement_uid} would create a cycle");
diagnostics.push(Diagnostic::new(control_uid.to_string(), reason.clone()));
return ControlOutcome::Inert { reason };
}
ControlOutcome::Applied
}
fn apply_retraction(
target_uid: &str,
control_uid: &str,
primaries: &BTreeMap<String, Envelope>,
_supersessions: &BTreeMap<String, (String, String)>,
_retractions: &BTreeMap<String, String>,
applied_retractions: &BTreeSet<String>,
diagnostics: &mut Vec<Diagnostic>,
) -> ControlOutcome {
if applied_retractions.contains(target_uid) {
return ControlOutcome::Duplicate;
}
match primaries.get(target_uid) {
None => {
let reason =
format!("retraction target {target_uid} does not exist as a primary observation");
diagnostics.push(Diagnostic::new(control_uid.to_string(), reason.clone()));
ControlOutcome::Inert { reason }
}
Some(_target) => {
ControlOutcome::Applied
}
}
}
fn would_create_cycle(
old: &str,
replacement: &str,
supersessions: &BTreeMap<String, (String, String)>,
) -> bool {
let mut current = replacement;
for _ in 0..1_000 {
if current == old {
return true;
}
match supersessions.get(current) {
Some((next, _)) => {
current = next;
}
None => return false,
}
}
true
}
#[cfg(test)]
#[expect(
clippy::unwrap_used,
clippy::expect_used,
clippy::panic,
clippy::indexing_slicing,
reason = "test code is exempt from panic-family lints"
)]
mod tests {
use super::*;
use crate::observation::wire::{Payload, SCHEMA, SCHEMA_VERSION};
fn friction_env(uid: &str, recorded_at: &str, summary: &str) -> Envelope {
Envelope {
schema: SCHEMA.to_string(),
schema_version: SCHEMA_VERSION,
uid: uid.to_string(),
recorded_at: recorded_at.to_string(),
facets: None,
payload: Payload::Friction {
summary: summary.to_string(),
detail: None,
},
}
}
fn ss_env(uid: &str, recorded_at: &str, old: &str, replacement: &str) -> Envelope {
Envelope {
schema: SCHEMA.to_string(),
schema_version: SCHEMA_VERSION,
uid: uid.to_string(),
recorded_at: recorded_at.to_string(),
facets: None,
payload: Payload::Supersession {
old_uid: old.to_string(),
replacement_uid: replacement.to_string(),
reason: None,
},
}
}
fn ret_env(uid: &str, recorded_at: &str, target: &str) -> Envelope {
Envelope {
schema: SCHEMA.to_string(),
schema_version: SCHEMA_VERSION,
uid: uid.to_string(),
recorded_at: recorded_at.to_string(),
facets: None,
payload: Payload::Retraction {
target_uid: target.to_string(),
reason: None,
},
}
}
#[test]
fn single_primary_is_active() {
let e = friction_env(
"01909a0a-0000-7000-8000-000000000001",
"2026-07-26T10:11:12Z",
"test",
);
let (res, _outcomes) = resolve(vec![e.clone()]);
let state = res.state(&e.uid).unwrap();
assert!(state.is_active());
assert_eq!(res.diagnostics().len(), 0);
}
#[test]
fn supersession_chain_resolves_to_terminus() {
let a = friction_env("a", "2026-01-01T00:00:00Z", "original");
let b = friction_env("b", "2026-01-02T00:00:00Z", "replacement");
let c = friction_env("c", "2026-01-03T00:00:00Z", "replacement-2");
let ss1 = ss_env("ss1", "2026-01-04T00:00:00Z", "a", "b");
let ss2 = ss_env("ss2", "2026-01-05T00:00:00Z", "b", "c");
let (res, outcomes) = resolve(vec![a, b, c, ss1, ss2]);
assert_eq!(res.diagnostics().len(), 0);
assert_eq!(outcomes.get("ss1").unwrap(), &ControlOutcome::Applied);
assert_eq!(outcomes.get("ss2").unwrap(), &ControlOutcome::Applied);
match res.state("a").unwrap() {
ResolvedState::Superseded { by, .. } => assert_eq!(by, "b"),
other => panic!("expected Superseded, got {other:?}"),
}
match res.state("b").unwrap() {
ResolvedState::Superseded { by, .. } => assert_eq!(by, "c"),
other => panic!("expected Superseded, got {other:?}"),
}
assert!(res.state("c").unwrap().is_active());
assert_eq!(res.resolved_terminus("a"), "c");
}
#[test]
fn retraction_makes_primary_inactive() {
let a = friction_env("a", "2026-01-01T00:00:00Z", "original");
let ret = ret_env("ret1", "2026-01-02T00:00:00Z", "a");
let (res, outcomes) = resolve(vec![a, ret]);
assert_eq!(res.diagnostics().len(), 0);
assert_eq!(outcomes.get("ret1").unwrap(), &ControlOutcome::Applied);
assert!(matches!(
res.state("a").unwrap(),
ResolvedState::Retracted { .. }
));
}
#[test]
fn invalid_control_cannot_resurrect() {
let a = friction_env("a", "2026-01-01T00:00:00Z", "original");
let b = friction_env("b", "2026-01-02T00:00:00Z", "replacement");
let ret = ret_env("ret1", "2026-01-03T00:00:00Z", "a");
let ss = ss_env("ss1", "2026-01-04T00:00:00Z", "a", "b");
let (res, outcomes) = resolve(vec![a, b, ret, ss]);
assert_eq!(outcomes.get("ret1").unwrap(), &ControlOutcome::Applied);
assert!(matches!(
outcomes.get("ss1").unwrap(),
ControlOutcome::Inert { .. }
));
assert!(matches!(
res.state("a").unwrap(),
ResolvedState::Retracted { .. }
));
assert_eq!(res.resolved_terminus("a"), "a");
assert!(!res.diagnostics().is_empty());
}
#[test]
fn duplicate_controls_are_idempotent() {
let a = friction_env("a", "2026-01-01T00:00:00Z", "original");
let b = friction_env("b", "2026-01-02T00:00:00Z", "replacement");
let ss1 = ss_env("ss1", "2026-01-03T00:00:00Z", "a", "b");
let ss2 = ss_env("ss2", "2026-01-04T00:00:00Z", "a", "b");
let (res, outcomes) = resolve(vec![a, b, ss1, ss2]);
assert_eq!(outcomes.get("ss1").unwrap(), &ControlOutcome::Applied);
assert_eq!(outcomes.get("ss2").unwrap(), &ControlOutcome::Duplicate);
match res.state("a").unwrap() {
ResolvedState::Superseded { by, .. } => assert_eq!(by, "b"),
other => panic!("expected Superseded, got {other:?}"),
}
let x = friction_env("x", "2026-01-01T00:00:00Z", "x");
let r1 = ret_env("r1", "2026-01-02T00:00:00Z", "x");
let r2 = ret_env("r2", "2026-01-03T00:00:00Z", "x");
let (res2, outcomes2) = resolve(vec![x, r1, r2]);
assert_eq!(outcomes2.get("r1").unwrap(), &ControlOutcome::Applied);
assert_eq!(outcomes2.get("r2").unwrap(), &ControlOutcome::Duplicate);
assert!(matches!(
res2.state("x").unwrap(),
ResolvedState::Retracted { .. }
));
}
#[test]
fn resolved_chain_reports_retracted_terminus() {
let a = friction_env("a", "2026-01-01T00:00:00Z", "a");
let b = friction_env("b", "2026-01-02T00:00:00Z", "b");
let c = friction_env("c", "2026-01-03T00:00:00Z", "c");
let ss1 = ss_env("ss1", "2026-01-04T00:00:00Z", "a", "b");
let ss2 = ss_env("ss2", "2026-01-05T00:00:00Z", "b", "c");
let ret = ret_env("ret1", "2026-01-06T00:00:00Z", "c");
let (res, _outcomes) = resolve(vec![a, b, c, ss1, ss2, ret]);
assert_eq!(res.resolved_terminus("a"), "c");
assert!(matches!(
res.state("c").unwrap(),
ResolvedState::Retracted { .. }
));
match res.state("a").unwrap() {
ResolvedState::Superseded { by, .. } => assert_eq!(by, "b"),
other => panic!("expected Superseded, got {other:?}"),
}
}
#[test]
fn cycle_introducing_edge_is_inert() {
let a = friction_env("a", "2026-01-01T00:00:00Z", "a");
let b = friction_env("b", "2026-01-02T00:00:00Z", "b");
let ss1 = ss_env("ss1", "2026-01-03T00:00:00Z", "a", "b");
let ss2 = ss_env("ss2", "2026-01-04T00:00:00Z", "b", "a");
let (res, outcomes) = resolve(vec![a, b, ss1, ss2]);
assert_eq!(outcomes.get("ss1").unwrap(), &ControlOutcome::Applied);
assert!(matches!(
outcomes.get("ss2").unwrap(),
ControlOutcome::Inert { .. }
));
match res.state("a").unwrap() {
ResolvedState::Superseded { by, .. } => assert_eq!(by, "b"),
other => panic!("expected Superseded, got {other:?}"),
}
assert!(res.state("b").unwrap().is_active());
}
#[test]
fn dangling_supersession_is_inert() {
let ss = ss_env(
"ss1",
"2026-01-01T00:00:00Z",
"nonexistent",
"01909a0a-0000-7000-8000-000000000099",
);
let (res, outcomes) = resolve(vec![ss]);
assert!(matches!(
outcomes.get("ss1").unwrap(),
ControlOutcome::Inert { .. }
));
assert!(!res.diagnostics().is_empty());
}
#[test]
fn dangling_retraction_is_inert() {
let ret = ret_env("ret1", "2026-01-01T00:00:00Z", "nonexistent");
let (_res, outcomes) = resolve(vec![ret]);
assert!(matches!(
outcomes.get("ret1").unwrap(),
ControlOutcome::Inert { .. }
));
}
#[test]
fn kind_incompatible_supersession_is_inert() {
let a = friction_env("a", "2026-01-01T00:00:00Z", "friction");
let m = Envelope {
schema: SCHEMA.to_string(),
schema_version: SCHEMA_VERSION,
uid: "m".to_string(),
recorded_at: "2026-01-02T00:00:00Z".to_string(),
facets: None,
payload: Payload::Measurement {
source: "test".to_string(),
counters: BTreeMap::new(),
gauges: BTreeMap::new(),
scope: None,
units: None,
completeness: None,
},
};
let ss = ss_env("ss1", "2026-01-03T00:00:00Z", "a", "m");
let (res, outcomes) = resolve(vec![a, m, ss]);
assert!(matches!(
outcomes.get("ss1").unwrap(),
ControlOutcome::Inert { .. }
));
assert!(res.state("a").unwrap().is_active());
}
#[test]
fn conflicting_successors_earliest_wins() {
let a = friction_env("a", "2026-01-01T00:00:00Z", "a");
let b = friction_env("b", "2026-01-02T00:00:00Z", "b");
let c = friction_env("c", "2026-01-03T00:00:00Z", "c");
let ss1 = ss_env("ss1", "2026-01-04T00:00:00Z", "a", "b");
let ss2 = ss_env("ss2", "2026-01-05T00:00:00Z", "a", "c");
let (res, outcomes) = resolve(vec![a, b, c, ss1, ss2]);
assert_eq!(outcomes.get("ss1").unwrap(), &ControlOutcome::Applied);
assert!(matches!(
outcomes.get("ss2").unwrap(),
ControlOutcome::Inert { .. }
));
match res.state("a").unwrap() {
ResolvedState::Superseded { by, .. } => assert_eq!(by, "b"),
other => panic!("expected Superseded by b, got {other:?}"),
}
}
#[test]
fn retraction_dominates_supersession() {
let a = friction_env("a", "2026-01-01T00:00:00Z", "a");
let b = friction_env("b", "2026-01-02T00:00:00Z", "b");
let ss = ss_env("ss1", "2026-01-03T00:00:00Z", "a", "b");
let ret = ret_env("ret1", "2026-01-04T00:00:00Z", "a");
let (res, outcomes) = resolve(vec![a.clone(), b, ss, ret]);
assert_eq!(outcomes.get("ss1").unwrap(), &ControlOutcome::Applied);
assert_eq!(outcomes.get("ret1").unwrap(), &ControlOutcome::Applied);
assert!(matches!(
res.state("a").unwrap(),
ResolvedState::Retracted { .. }
));
}
#[test]
fn active_projection_excludes_corrected() {
let a = friction_env("a", "2026-01-01T00:00:00Z", "active");
let b = friction_env("b", "2026-01-02T00:00:00Z", "superseded");
let ss = ss_env("ss1", "2026-01-03T00:00:00Z", "b", "a");
let (res, _outcomes) = resolve(vec![a.clone(), b.clone(), ss]);
let active: Vec<&str> = res.active().iter().map(|e| e.uid.as_str()).collect();
assert!(active.contains(&"a"));
assert!(
!active.contains(&"b"),
"b is superseded, should not be in active"
);
let history: Vec<&str> = res.history().iter().map(|e| e.uid.as_str()).collect();
assert!(history.contains(&"a"));
assert!(history.contains(&"b"));
}
#[test]
fn correction_chain_reports_complete_history() {
let a = friction_env("a", "2026-01-01T00:00:00Z", "a");
let b = friction_env("b", "2026-01-02T00:00:00Z", "b");
let c = friction_env("c", "2026-01-03T00:00:00Z", "c");
let ss1 = ss_env("ss1", "2026-01-04T00:00:00Z", "a", "b");
let ss2 = ss_env("ss2", "2026-01-05T00:00:00Z", "b", "c");
let (res, _outcomes) = resolve(vec![a, b, c, ss1, ss2]);
let chain = res.correction_chain("a");
assert_eq!(chain.len(), 3); assert_eq!(chain[0].0, "a");
assert_eq!(chain[1].0, "b");
assert_eq!(chain[2].0, "c");
}
#[test]
fn control_targeting_control_is_inert() {
let a = friction_env("a", "2026-01-01T00:00:00Z", "a");
let b = friction_env("b", "2026-01-02T00:00:00Z", "b");
let ss1 = ss_env("ss1", "2026-01-03T00:00:00Z", "a", "b");
let ss2 = ss_env("ss2", "2026-01-04T00:00:00Z", "ss1", "b");
let (_res, outcomes) = resolve(vec![a, b, ss1, ss2]);
assert_eq!(outcomes.get("ss1").unwrap(), &ControlOutcome::Applied);
assert!(matches!(
outcomes.get("ss2").unwrap(),
ControlOutcome::Inert { .. }
));
}
#[test]
fn empty_corpus_is_valid() {
let (res, outcomes) = resolve(vec![]);
assert!(res.diagnostics().is_empty());
assert!(outcomes.is_empty());
assert!(res.active().is_empty());
assert!(res.history().is_empty());
}
#[test]
fn canonical_ordering_is_by_recorded_at_then_uid() {
let a = friction_env("a", "2026-01-02T00:00:00Z", "later");
let b = friction_env("b", "2026-01-01T00:00:00Z", "earlier");
let c = friction_env("c", "2026-01-01T00:00:00Z", "earlier-same-time");
let (res, _outcomes) = resolve(vec![a, b, c]);
let active = res.active();
assert_eq!(active[0].uid, "a");
assert_eq!(active[1].uid, "c");
assert_eq!(active[2].uid, "b");
}
#[test]
fn retraction_of_already_superseded_target_is_applied() {
let a = friction_env("a", "2026-01-01T00:00:00Z", "a");
let b = friction_env("b", "2026-01-02T00:00:00Z", "b");
let ss = ss_env("ss1", "2026-01-03T00:00:00Z", "a", "b");
let ret = ret_env("ret1", "2026-01-04T00:00:00Z", "a");
let (res, outcomes) = resolve(vec![a, b, ss, ret]);
assert_eq!(outcomes.get("ss1").unwrap(), &ControlOutcome::Applied);
assert_eq!(outcomes.get("ret1").unwrap(), &ControlOutcome::Applied);
assert!(matches!(
res.state("a").unwrap(),
ResolvedState::Retracted { .. }
));
}
}