cellos-ctl 0.6.0-pre

cellctl — kubectl-style CLI for CellOS execution cells and formations. Thin HTTP client over cellos-server with apply/get/describe/logs/events/webui.
Documentation
//! `cellctl evidence` — operator surface over the accreditation-evidence library
//! (S46, ADR-0032).
//!
//! Local operations over the in-repo 800-53 control-mapping catalog: lint it, or
//! emit the candidate OSCAL component-definition + SSP-fragment. The catalog,
//! lint, and emitter logic — and their tests — live in `cellos-evidence`; this is
//! thin plumbing (like `cellctl audit`), bypassing the server client.
//!
//! The emitted artifacts are **candidate evidence** for an assessor's
//! eMASS/Xacta submission, never a self-attestation of control satisfaction —
//! the OSCAL remarks say so per requirement.

use std::path::Path;

use crate::exit::{CtlError, CtlResult};

/// `cellctl evidence lint` — validate every catalog entry against the schema and
/// assert its cited seams/tests resolve in-repo. Non-zero on any failure.
pub fn lint() -> CtlResult<()> {
    let root = cellos_evidence::repo_root();
    let entries = cellos_evidence::lint_catalog(&root)
        .map_err(|e| CtlError::validation(format!("evidence lint: {e}")))?;
    println!(
        "evidence catalog OK: {} control-mapping entries lint-clean",
        entries.len()
    );
    Ok(())
}

/// `cellctl evidence emit` — emit the OSCAL component-definition + SSP-fragment
/// for the catalog to `out` (deterministic; candidate evidence).
pub fn emit(out: &Path) -> CtlResult<()> {
    let root = cellos_evidence::repo_root();
    let entries = cellos_evidence::load_catalog(&root)
        .map_err(|e| CtlError::validation(format!("evidence emit: load catalog: {e}")))?;
    let pkg = cellos_evidence::emit_oscal(&entries);

    std::fs::create_dir_all(out)
        .map_err(|e| CtlError::usage(format!("evidence emit: mkdir {}: {e}", out.display())))?;
    let cd = serde_json::to_string_pretty(&pkg.component_definition).map_err(|e| {
        CtlError::validation(format!("evidence emit: serialize component-def: {e}"))
    })?;
    let ssp = serde_json::to_string_pretty(&pkg.ssp_fragment)
        .map_err(|e| CtlError::validation(format!("evidence emit: serialize ssp: {e}")))?;
    let cd_path = out.join("component-definition.json");
    let ssp_path = out.join("ssp-fragment.json");
    std::fs::write(&cd_path, cd)
        .map_err(|e| CtlError::usage(format!("evidence emit: write {}: {e}", cd_path.display())))?;
    std::fs::write(&ssp_path, ssp).map_err(|e| {
        CtlError::usage(format!("evidence emit: write {}: {e}", ssp_path.display()))
    })?;

    println!(
        "emitted OSCAL component-definition + SSP-fragment ({} controls, CANDIDATE evidence) to {}",
        entries.len(),
        out.display()
    );
    Ok(())
}