car-server-core 0.33.0

Transport-neutral library for the CAR daemon JSON-RPC dispatcher (used by car-server and tokhn-daemon)
//! Sandbox-first execution-environment selection for the assistant.
//!
//! Default: bind a hardened Docker sandbox (`car_sandbox`) so shell + file
//! writes are isolated (`--network none`, capped, caps dropped) and safe out of
//! the box. If Docker isn't available we do **not** hard-fail — we fall back to
//! the local host with the standing permission tier forced to `ReadOnly`, so
//! every write/shell escalates to a human-in-the-loop approval. `--local`
//! selects the host directly.

use std::path::{Path, PathBuf};
use std::sync::Arc;

use car_engine::{LocalSubstrate, Substrate};
use car_policy::permission::PermissionTier;
use car_sandbox::{preflight, SandboxPolicy};

/// Default sandbox image for the assistant. Richer than `car-sandbox`'s
/// `python:3.11-slim` default: the full `python:3.11` bundles git, gcc, make,
/// and curl, so a general assistant can build and inspect code offline (the
/// sandbox has no network, so tools must be pre-baked into the image).
/// Override with `--image`.
pub const DEFAULT_ASSISTANT_IMAGE: &str = "python:3.11";

/// The bound environment plus the safety metadata the caller needs to render a
/// system prompt and set up gating.
pub struct BoundEnvironment {
    /// The execution substrate the runtime binds (sandbox or local host).
    pub substrate: Arc<dyn Substrate>,
    /// The working-directory root (shell cwd on the local path; the clamp
    /// boundary for local file writes).
    pub root: PathBuf,
    /// Standing permission tier granted to the session. In the sandbox the
    /// container is the boundary, so file/shell edits auto-allow (`SandboxEdit`);
    /// on the local host the default is `ReadOnly` so writes/shell need approval.
    /// `--full-access` lifts either to `FullAccess`.
    pub tier: PermissionTier,
    /// One-line environment description for the system prompt.
    pub description: String,
    /// Whether execution is isolated in a container.
    pub sandboxed: bool,
    /// If the sandbox was requested but unavailable, the actionable reason we
    /// fell back to the local host (Docker missing/stopped, image not pulled).
    pub fallback_notice: Option<String>,
}

/// Decide and build the execution environment.
///
/// * `prefer_local` — user passed `--local`; skip the sandbox entirely.
/// * `full_access` — user passed `--full-access`/`-y`; grant `FullAccess`
///   (no HITL). Ignored inside the sandbox only in the sense that the container
///   already isolates — it still removes the approval prompts.
pub async fn bind_default_substrate(
    prefer_local: bool,
    full_access: bool,
    workdir: &Path,
    image: Option<&str>,
) -> BoundEnvironment {
    if !prefer_local {
        let policy =
            SandboxPolicy::default().with_image(image.unwrap_or(DEFAULT_ASSISTANT_IMAGE));
        let pf = preflight(&policy.image).await;
        if pf.is_ok() {
            let executor = Arc::new(policy.build_executor(workdir));
            let substrate: Arc<dyn Substrate> = executor;
            return BoundEnvironment {
                substrate,
                root: workdir.to_path_buf(),
                tier: if full_access {
                    PermissionTier::FullAccess
                } else {
                    PermissionTier::SandboxEdit
                },
                description: format!(
                    "an isolated Docker sandbox (image {}, no network) mounted at {}. \
                     Files and shell run inside the container; web tools run from the host.",
                    policy.image,
                    workdir.display()
                ),
                sandboxed: true,
                fallback_notice: None,
            };
        }
        // Docker unavailable → local host, gated. Never a silent unsandboxed run.
        return BoundEnvironment {
            substrate: Arc::new(LocalSubstrate::new()),
            root: workdir.to_path_buf(),
            tier: if full_access {
                PermissionTier::FullAccess
            } else {
                PermissionTier::ReadOnly
            },
            description: format!(
                "the LOCAL host filesystem and shell at {} (sandbox unavailable). \
                 Writes and shell require approval.",
                workdir.display()
            ),
            sandboxed: false,
            fallback_notice: Some(pf.message()),
        };
    }

    BoundEnvironment {
        substrate: Arc::new(LocalSubstrate::new()),
        root: workdir.to_path_buf(),
        tier: if full_access {
            PermissionTier::FullAccess
        } else {
            PermissionTier::ReadOnly
        },
        description: format!(
            "the LOCAL host filesystem and shell at {}.{}",
            workdir.display(),
            if full_access {
                " Full access granted."
            } else {
                " Writes and shell require approval."
            }
        ),
        sandboxed: false,
        fallback_notice: None,
    }
}