nixfleet_reconciler/planner_gates/
compliance_wave.rs

1//! Compliance-wave gate. Earlier-wave hosts with outstanding evidence
2//! failures hold later-wave dispatch.
3//!
4//! **Phase 9a state: STUB**. Reads from
5//! `FleetState.outstanding_failing_enforce_probes` (populated in 9b once
6//! the `probe_failures` projection writer lands). Until 9b, the source
7//! map is empty and this gate is effectively pass-through. Per RFC-0007
8//! ยง7.2 the gate's input pipeline is being rebuilt against the new
9//! probe-result model; the shape is correct in 9a, data starts flowing
10//! in 9b.
11
12use crate::planner_gates::GateBlock;
13use crate::planner_types::{FleetState, HostId, RolloutId, SignedManifestSet};
14
15pub fn check(
16    fleet_state: &FleetState,
17    manifests: &SignedManifestSet,
18    host: &HostId,
19    rollout_id: &RolloutId,
20) -> Option<GateBlock> {
21    let fleet = manifests.fleet();
22    let host_channel = fleet.hosts.get(host).map(|h| h.channel.as_str())?;
23
24    let host_wave_idx = fleet
25        .waves
26        .get(host_channel)
27        .and_then(|waves| waves.iter().position(|w| w.hosts.iter().any(|h| h == host)));
28    let host_wave = match host_wave_idx {
29        Some(0) | None => return None, // wave 0 / unwaved: no earlier wave to gate on
30        Some(n) => n,
31    };
32
33    let waves = fleet.waves.get(host_channel)?;
34    let per_host = fleet_state
35        .outstanding_failing_enforce_probes
36        .get(rollout_id)?;
37
38    let mut failing_count: usize = 0;
39    for wave in waves.iter().take(host_wave) {
40        for h in &wave.hosts {
41            if let Some(&n) = per_host.get(h)
42                && n > 0
43            {
44                failing_count += n;
45            }
46        }
47    }
48
49    if failing_count > 0 {
50        Some(GateBlock::ComplianceWave {
51            failing_events_count: failing_count,
52            host_wave: host_wave as u32,
53        })
54    } else {
55        None
56    }
57}