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
//! Tier 4 — the standard ambient proxy variables.
//!
//! This rung reads nothing. `EgressConfig::resolve` already walked
//! `https_proxy` / `HTTPS_PROXY` / `http_proxy` / `HTTP_PROXY` / `ALL_PROXY` / `all_proxy`
//! through the [`EnvSource`](super::super::config::EnvSource) seam, applied the
//! lowercase-wins rule and raised the Unix disagreement warning. Re-reading the
//! environment here would duplicate that logic and, worse, could disagree with it — the
//! config the client actually builds from would then differ from the candidate the trace
//! says won.
//!
//! It lives in the resolver anyway, above the OS rungs, so that when tiers 1–3 are empty
//! the ambient variables appear in the trace as the rung they are, and `init` can say
//! "your shell already has one" rather than reporting a bare OS discovery.

use super::{parse_discovered, RungResult};
use crate::core::egress::config::{EgressConfig, ProxySource};

/// The tier-4 rung.
///
/// The URL comes from `cfg`, which by the time the ladder is walked holds whatever tiers 2
/// through 4 resolved. In practice the caller only reaches the ladder when tiers 1–3 were
/// empty, so what is left is the ambient environment.
pub(crate) fn rung(cfg: &EgressConfig) -> RungResult {
    let Some(raw) = cfg.url.as_deref().filter(|u| !u.is_empty()) else {
        return RungResult::empty(ProxySource::Env);
    };
    match parse_discovered(raw) {
        Some(url) => RungResult::static_route(ProxySource::Env, url),
        None => RungResult::skipped(
            ProxySource::Env,
            crate::core::error::ERR_PROXY_CONFIG_INVALID,
            format!("the ambient proxy variable holds an unusable value: {raw}"),
        ),
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::core::egress::config::{EnvSource, ProxyMode};
    use std::collections::HashMap;

    struct MapEnv(HashMap<String, String>);

    impl EnvSource for MapEnv {
        fn var(&self, key: &str) -> Option<String> {
            self.0.get(key).cloned()
        }
    }

    fn resolved(pairs: &[(&str, &str)]) -> EgressConfig {
        let env = MapEnv(
            pairs
                .iter()
                .map(|(k, v)| ((*k).to_string(), (*v).to_string()))
                .collect(),
        );
        EgressConfig::resolve(None, &env, 7443, 7444).expect("resolve")
    }

    #[test]
    fn nothing_ambient_is_not_a_failure() {
        let cfg = resolved(&[]);
        assert!(matches!(
            rung(&cfg),
            RungResult::NotConfigured {
                source: ProxySource::Env,
                ..
            }
        ));
    }

    #[test]
    fn the_rung_reports_the_resolution_lowercase_already_won() {
        // `EgressConfig::resolve` owns the precedence; this asserts the rung inherits it
        // rather than re-deriving a second, drifting answer.
        let cfg = resolved(&[
            ("https_proxy", "http://lower.corp:8080"),
            ("HTTPS_PROXY", "http://upper.corp:8080"),
        ]);
        let RungResult::Candidate { source, route, .. } = rung(&cfg) else {
            panic!("expected a candidate");
        };
        assert_eq!(source, ProxySource::Env);
        assert_eq!(
            super::super::route_display(&route),
            "http://lower.corp:8080"
        );
    }

    #[test]
    fn http_proxy_is_used_when_https_proxy_is_absent() {
        // A bare `host:port` never reaches this rung: `EgressConfig::resolve` refuses one
        // in the ambient variables, and that is I-1's contract, not re-litigated here.
        let cfg = resolved(&[("http_proxy", "http://proxy.corp:3128")]);
        let RungResult::Candidate { route, .. } = rung(&cfg) else {
            panic!("expected a candidate");
        };
        assert_eq!(
            super::super::route_display(&route),
            "http://proxy.corp:3128"
        );
    }

    #[test]
    fn direct_mode_leaves_the_rung_empty() {
        let mut cfg = resolved(&[("all_proxy", "http://ambient.corp:8080")]);
        cfg.mode = ProxyMode::Direct;
        cfg.url = None;
        assert!(matches!(rung(&cfg), RungResult::NotConfigured { .. }));
    }
}