//! OS-sandbox adapters (lot LP4-1) — concrete [`domain::sandbox::SandboxEnforcer`]
//! implementations and the cfg-selected default constructor.
//!
//! - [`LandlockSandbox`] (Linux only) enforces a [`domain::sandbox::SandboxPlan`]
//!   via the kernel Landlock LSM.
//! - [`NoopSandbox`] (everywhere) enforces nothing — the non-Linux / fallback path.
//!
//! [`default_enforcer`] returns the right one for the build target as a
//! `Arc<dyn SandboxEnforcer>`, ready for the composition root (LP4-3) to inject.
//! Nothing here is wired into the launch path yet (that is LP4-2): until then the
//! enforcer stays unused and `SpawnSpec.sandbox` stays `None`, so there is zero
//! runtime behaviour change.

use std::sync::Arc;

use domain::sandbox::SandboxEnforcer;

mod noop;
pub use noop::NoopSandbox;

#[cfg(target_os = "linux")]
mod landlock;
#[cfg(target_os = "linux")]
pub use landlock::LandlockSandbox;

/// The default OS-sandbox enforcer for the current build target: [`LandlockSandbox`]
/// on Linux, [`NoopSandbox`] everywhere else.
#[cfg(target_os = "linux")]
#[must_use]
pub fn default_enforcer() -> Arc<dyn SandboxEnforcer> {
    Arc::new(LandlockSandbox::new())
}

/// The default OS-sandbox enforcer for the current build target: [`NoopSandbox`]
/// on non-Linux platforms (no OS sandbox wired yet).
#[cfg(not(target_os = "linux"))]
#[must_use]
pub fn default_enforcer() -> Arc<dyn SandboxEnforcer> {
    Arc::new(NoopSandbox::new())
}

#[cfg(test)]
mod tests {
    use super::*;
    use domain::sandbox::{SandboxKind, SandboxPlan, SandboxStatus};
    use domain::Posture;

    #[test]
    fn noop_enforcer_is_unsupported_and_never_errors() {
        use domain::sandbox::PathGrant;

        let noop = NoopSandbox::new();
        assert_eq!(noop.kind(), SandboxKind::Unsupported);

        // Across every posture — and even with grants present — the no-op enforces
        // nothing and never fails closed: there is no OS sandbox here to fail closed
        // against (the Deny posture's teeth live only in the advisory LP3 projection).
        for posture in [Posture::Allow, Posture::Ask, Posture::Deny] {
            let plan = SandboxPlan {
                allowed: vec![PathGrant {
                    abs_root: "/whatever".to_owned(),
                    access: domain::sandbox::PathAccess::RW,
                }],
                default_posture: posture,
            };
            assert_eq!(
                noop.enforce(&plan).unwrap(),
                SandboxStatus::Unsupported,
                "no-op must return Unsupported (never Err) under posture {posture:?}"
            );
        }
    }

    #[test]
    fn default_enforcer_matches_the_build_target() {
        let enforcer = default_enforcer();
        #[cfg(target_os = "linux")]
        assert_eq!(enforcer.kind(), SandboxKind::Landlock);
        #[cfg(not(target_os = "linux"))]
        assert_eq!(enforcer.kind(), SandboxKind::Unsupported);
    }
}
