openlatch-client 0.3.3

OpenLatch runtime enforcement node — the capture-and-enforce adapter that evaluates every covered action against a coding agent's Autonomy Zone before it runs
//! The verdict lattice and record assembly — plan 02 §2g, PRD §Verdict lattice.
//!
//! # The lattice
//!
//! `Block > Ask > Optimize > Allow`, joined across **enforce-mode atoms only**.
//! Monitor-mode atoms and `monitor_only` atoms contribute `would_have_verdict`
//! and an anomaly; they NEVER enter the join. *Monitor never joins* is the
//! invariant, and [`assemble`] is where a reviewer checks it: the slice is
//! partitioned once, by [`Contribution::is_enforcing`], and every field that can
//! reach the agent — the verdict, the hold, the rewrite, the matched exception's
//! ground key — is read off the enforcing half.
//!
//! `enforcement_enabled: false` makes every artifact monitor, which is why the
//! kill switch needs no second rule here: it composes into the mode above the
//! tiers and the partition does the rest.
//!
//! # R10 — exactly one rewrite per action
//!
//! At most one rewrite leaves the engine. It is owned by the **first optimize
//! contribution in artifact order that declares a lever**, and only an
//! **enforcing** one: folding over all contributions sent a steer instruction to
//! the agent with the org-wide kill switch off, and let a monitor artifact spend
//! R10's single slot while the artifact that actually decided lost its own.
//!
//! A second optimize does not get a turn. The corpus pins the whole set —
//! `07-rewrite-{one,two,none,enforcement-off,monitor,two-a-monitor}`.
//!
//! **What is not here, and why it is not a gap.** R10 continues: *"evaluate all
//! tiers → at most one rewrite → re-evaluate every tier on the final input
//! **in-daemon** → a second rewrite downgrades to ASK/BLOCK;
//! `original_input`/`updated_input`/`rewrite_rule_id` recorded"*. The second pass
//! is over a **mutated action**, and the PRD puts it in the daemon: this function
//! is pure and never sees the rewritten input, so it reports the one rewrite the
//! daemon would apply and the daemon calls [`super::evaluate`] again with it. The
//! three recorded fields are the decision record's `rewrite` column (PRD
//! §`decision_records`), not [`Rewrite`] — the corpus pins the engine's shape as
//! `{lever, artifact_id, steer_instruction}`, and the oracle agrees.

use std::collections::BTreeSet;

use super::types::{Anomaly, Contribution, Decision, EvalContext, Rewrite};
use crate::generated::types::Verdict;

/// Where a verdict sits in the lattice. Block dominates; Allow yields.
pub fn rank(verdict: &Verdict) -> u8 {
    match verdict {
        Verdict::Allow => 0,
        Verdict::Optimize => 1,
        Verdict::Ask => 2,
        Verdict::Block => 3,
    }
}

/// The highest-ranked contribution, or `None` when the slice is empty.
///
/// **Ties break on `artifact_id`**, ascending, so two artifacts that both block
/// name the same one on every host and every replay. Without it the answer would
/// depend on iteration order, and a decision record a CISO reads as evidence
/// would name a different rule on a re-run of the same event.
pub fn join(contributions: &[Contribution]) -> Option<&Contribution> {
    best(contributions.iter())
}

/// [`join`] over any iterator, so the enforcing and monitoring halves are each
/// joined without materialising a second slice.
fn best<'a>(contributions: impl Iterator<Item = &'a Contribution>) -> Option<&'a Contribution> {
    contributions.min_by(|left, right| {
        rank(&right.verdict)
            .cmp(&rank(&left.verdict))
            .then_with(|| artifact_id(left).cmp(artifact_id(right)))
    })
}

/// Build the `Decision` from the contributions, the classification and the
/// context's accumulators.
pub fn assemble(contributions: &[Contribution], ctx: &EvalContext<'_>) -> Decision {
    // The one partition the whole invariant rests on. Everything that can reach
    // the agent is read off `decided`; `shadow` only ever reports.
    let decided = best(contributions.iter().filter(|c| c.is_enforcing()));
    // What Enforce would have done. Read off the monitor half alone: a shadow
    // verdict is only meaningful for the artifacts that were not allowed to
    // decide.
    let shadow = best(contributions.iter().filter(|c| !c.is_enforcing()));

    // Sorted and deduplicated, across EVERY contribution — a monitor artifact
    // that could not read a fact has still found a gap, and the gap is the point.
    let inconclusive_facts: Vec<String> = contributions
        .iter()
        .flat_map(|c| c.inconclusive.iter().cloned())
        .collect::<BTreeSet<String>>()
        .into_iter()
        .collect();

    // Anomalies record; they never decide. Every contribution's, in artifact
    // order, because a monitor artifact raising one is the whole reason Monitor
    // mode exists.
    let anomalies: Vec<Anomaly> = contributions
        .iter()
        .flat_map(|c| {
            c.anomalies.iter().map(|code| Anomaly {
                code: code.clone(),
                artifact_id: c.artifact_id.clone(),
                atom_id: c.atom_id.clone(),
            })
        })
        .collect();

    Decision {
        verdict: decided.map(|c| c.verdict).unwrap_or(Verdict::Allow),
        artifact_id: decided.and_then(|c| c.artifact_id.clone()),
        atom_id: decided.and_then(|c| c.atom_id.clone()),
        policy_public_id: decided.and_then(|c| c.policy_public_id.clone()),
        dimension: decided.and_then(|c| c.dimension.clone()),
        mode: decided.map(|c| c.mode.clone()),
        tier: decided.and_then(|c| c.tier),
        reason: decided.map(|c| c.reason.clone()).unwrap_or_default(),
        would_have_verdict: shadow.map(|c| c.verdict),
        inconclusive_facts,
        rewrite: rewrite(contributions.iter().filter(|c| c.is_enforcing())),
        hold: decided.and_then(|c| c.hold.clone()),
        effects: ctx.classification.effects.clone(),
        // True when NOTHING contributed — distinct from `verdict: allow`, which
        // an artifact may have decided on purpose, and from an all-monitor
        // bundle, which decided nothing but was not silent.
        undecided: contributions.is_empty(),
        unknown: ctx.classification.unknown.clone(),
        anomalies,
        ground_key: decided.and_then(|c| c.exception_ground_key.clone()),
        warnings: ctx.warnings.clone(),
    }
}

/// R10's single rewrite: the first enforcing optimize that declares a lever.
fn rewrite<'a>(enforcing: impl Iterator<Item = &'a Contribution>) -> Option<Rewrite> {
    enforcing
        .filter(|c| c.verdict == Verdict::Optimize)
        .find_map(|c| {
            c.lever.clone().map(|lever| Rewrite {
                lever,
                artifact_id: c.artifact_id.clone(),
                steer_instruction: c.steer_instruction.clone(),
            })
        })
}

fn artifact_id(contribution: &Contribution) -> &str {
    contribution.artifact_id.as_deref().unwrap_or("")
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::generated::types::{Lever, PolicyMode};
    use crate::zone_eval::facts::FactSet;
    use crate::zone_eval::types::{Classification, Event, MODE_ENFORCE, MODE_MONITOR};

    const NOW: i64 = 1_756_742_400_000;

    fn contribution(artifact_id: &str, mode: &str, verdict: Verdict) -> Contribution {
        Contribution {
            artifact_id: Some(artifact_id.to_string()),
            atom_id: Some(format!("atom-{artifact_id}")),
            policy_public_id: None,
            dimension: None,
            mode: PolicyMode(mode.to_string()),
            tier: Some(1),
            verdict,
            reason: artifact_id.to_string(),
            inconclusive: Vec::new(),
            anomalies: Vec::new(),
            hold: None,
            exception_ground_key: None,
            lever: None,
            steer_instruction: None,
        }
    }

    fn decision(contributions: &[Contribution]) -> Decision {
        let event = Event::default();
        let classification = Classification::default();
        let facts = FactSet::default();
        let ctx = EvalContext::new(&event, &classification, &facts, NOW);
        assemble(contributions, &ctx)
    }

    #[test]
    fn the_lattice_is_block_over_ask_over_optimize_over_allow() {
        assert!(rank(&Verdict::Block) > rank(&Verdict::Ask));
        assert!(rank(&Verdict::Ask) > rank(&Verdict::Optimize));
        assert!(rank(&Verdict::Optimize) > rank(&Verdict::Allow));
    }

    #[test]
    fn monitor_never_joins() {
        // The invariant, in one assertion. A monitor artifact holding the
        // highest verdict in the bundle still contributes nothing to `verdict`.
        let decision = decision(&[
            contribution("m", MODE_MONITOR, Verdict::Block),
            contribution("e", MODE_ENFORCE, Verdict::Allow),
        ]);
        assert_eq!(decision.verdict, Verdict::Allow);
        assert_eq!(decision.artifact_id.as_deref(), Some("e"));
        assert_eq!(decision.would_have_verdict, Some(Verdict::Block));
        assert!(
            !decision.undecided,
            "it was not silent, it just did not decide"
        );
    }

    #[test]
    fn an_all_monitor_bundle_decides_nothing_and_names_nobody() {
        let decision = decision(&[contribution("m", MODE_MONITOR, Verdict::Block)]);
        assert_eq!(decision.verdict, Verdict::Allow);
        assert_eq!(decision.artifact_id, None);
        assert_eq!(
            decision.mode, None,
            "nothing decided, so no mode decided it"
        );
        assert_eq!(decision.tier, None);
        assert_eq!(decision.reason, "");
        assert_eq!(decision.would_have_verdict, Some(Verdict::Block));
        assert!(!decision.undecided);
    }

    #[test]
    fn nothing_at_all_is_undecided_and_an_allow_is_not() {
        assert!(decision(&[]).undecided);
        assert!(
            !decision(&[contribution("a", MODE_ENFORCE, Verdict::Allow)]).undecided,
            "an artifact may allow on purpose"
        );
    }

    #[test]
    fn a_tie_breaks_on_artifact_id_and_not_on_iteration_order() {
        let forward = decision(&[
            contribution("a", MODE_ENFORCE, Verdict::Block),
            contribution("b", MODE_ENFORCE, Verdict::Block),
        ]);
        let reversed = decision(&[
            contribution("b", MODE_ENFORCE, Verdict::Block),
            contribution("a", MODE_ENFORCE, Verdict::Block),
        ]);
        assert_eq!(forward.artifact_id.as_deref(), Some("a"));
        assert_eq!(forward, reversed, "a replay names the same rule");
    }

    #[test]
    fn r10_gives_the_single_rewrite_to_the_first_enforcing_optimize_with_a_lever() {
        let mut without = contribution("a-no-lever", MODE_ENFORCE, Verdict::Optimize);
        without.lever = None;
        let mut first = contribution("b-steer", MODE_ENFORCE, Verdict::Optimize);
        first.lever = Some(Lever("steer".to_string()));
        first.steer_instruction = Some("push a branch instead".to_string());
        let mut second = contribution("c-clamp", MODE_ENFORCE, Verdict::Optimize);
        second.lever = Some(Lever("effort_clamp".to_string()));

        let rewrite = decision(&[without, first, second])
            .rewrite
            .expect("the first lever-bearing optimize owns it");
        assert_eq!(rewrite.lever.0, "steer");
        assert_eq!(rewrite.artifact_id.as_deref(), Some("b-steer"));
        assert_eq!(
            rewrite.steer_instruction.as_deref(),
            Some("push a branch instead")
        );
    }

    #[test]
    fn a_monitor_artifact_never_spends_r10s_single_slot() {
        // Folding the rewrite over ALL contributions sent a steer instruction to
        // the agent with the org-wide kill switch off, and let a monitor
        // artifact spend the slot the artifact that actually decided needed.
        let mut monitored = contribution("a-monitor", MODE_MONITOR, Verdict::Optimize);
        monitored.lever = Some(Lever("effort_clamp".to_string()));
        let mut enforced = contribution("b-steer", MODE_ENFORCE, Verdict::Optimize);
        enforced.lever = Some(Lever("steer".to_string()));

        let rewrite = decision(&[monitored.clone(), enforced])
            .rewrite
            .expect("the enforcing one owns it");
        assert_eq!(rewrite.artifact_id.as_deref(), Some("b-steer"));
        assert_eq!(
            decision(&[monitored]).rewrite,
            None,
            "a monitor-only bundle applies nothing to the agent"
        );
    }

    #[test]
    fn the_hold_and_the_ground_key_come_from_the_artifact_that_decided() {
        let mut monitored = contribution("a-monitor", MODE_MONITOR, Verdict::Block);
        monitored.exception_ground_key = Some("m".repeat(64));
        let mut enforced = contribution("b-enforce", MODE_ENFORCE, Verdict::Ask);
        enforced.exception_ground_key = Some("e".repeat(64));

        let decision = decision(&[monitored, enforced]);
        assert_eq!(decision.ground_key, Some("e".repeat(64)));
        assert_eq!(decision.hold, None);
    }

    #[test]
    fn inconclusive_facts_are_sorted_deduplicated_and_gathered_from_every_artifact() {
        let mut monitored = contribution("a", MODE_MONITOR, Verdict::Allow);
        monitored.inconclusive = vec!["change_ticket".to_string(), "approved_domains".to_string()];
        let mut enforced = contribution("b", MODE_ENFORCE, Verdict::Allow);
        enforced.inconclusive = vec!["change_ticket".to_string()];

        assert_eq!(
            decision(&[monitored, enforced]).inconclusive_facts,
            vec!["approved_domains".to_string(), "change_ticket".to_string()],
            "a monitor artifact that could not read a fact still found the gap"
        );
    }

    #[test]
    fn anomalies_record_from_every_artifact_and_carry_their_own_provenance() {
        let mut monitored = contribution("a", MODE_MONITOR, Verdict::Allow);
        monitored.anomalies = vec!["burst".to_string()];
        let anomalies = decision(&[monitored]).anomalies;
        assert_eq!(anomalies.len(), 1);
        assert_eq!(anomalies[0].code, "burst");
        assert_eq!(anomalies[0].artifact_id.as_deref(), Some("a"));
        assert_eq!(anomalies[0].atom_id.as_deref(), Some("atom-a"));
    }
}