use std::path::Path;
use std::process::ExitCode;
use anyhow::{bail, Result};
use serde::Serialize;
use crate::output::CommandReport;
use crate::paths::state::StateLayout;
use crate::profile;
use crate::repo::marker as repo_marker;
use crate::state::escalation as escalation_state;
use crate::state::machine_registry as pod_identity;
use crate::state::policy_projection;
#[derive(Serialize)]
pub struct PolicyCheckReport {
command: &'static str,
ok: bool,
path: String,
profile: String,
project_id: String,
locality_id: String,
action_family: policy_projection::PolicyActionFamily,
policy_posture: policy_projection::AutonomousPolicyPosture,
decision: policy_projection::PolicyDecision,
reason_code: &'static str,
summary: String,
policy_projection: policy_projection::PolicyProjectionView,
}
impl CommandReport for PolicyCheckReport {
fn exit_code(&self) -> ExitCode {
ExitCode::SUCCESS
}
fn render_text(&self) {
println!(
"{} -> {} ({})",
self.action_family.label(),
self.decision_label(),
self.reason_code
);
println!("{}", self.summary);
}
}
impl PolicyCheckReport {
fn decision_label(&self) -> &'static str {
match self.decision {
policy_projection::PolicyDecision::Allow => "allow",
policy_projection::PolicyDecision::ApprovalRequired => "approval_required",
policy_projection::PolicyDecision::EscalationRequired => "escalation_required",
policy_projection::PolicyDecision::Deny => "deny",
}
}
}
pub fn run(
repo_root: &Path,
explicit_profile: Option<&str>,
action_family: policy_projection::PolicyActionFamily,
) -> Result<PolicyCheckReport> {
let profile = profile::resolve(explicit_profile)?;
let layout = StateLayout::resolve(repo_root, profile.clone())?;
let Some(marker) = repo_marker::load(repo_root)? else {
bail!(
"workspace is not linked: {} is missing; run `ccd attach --path {}` or `ccd link --path {}` first",
repo_root.join(repo_marker::MARKER_FILE).display(),
repo_root.display(),
repo_root.display()
);
};
let active_pod_identity = pod_identity::resolve_active_identity(&layout)?;
let policy_sources = policy_projection::read_policy_sources(
&layout,
&marker.locality_id,
active_pod_identity.as_ref(),
)?;
let session_context = policy_projection::load_session_context(&layout)?;
let escalation_entries = escalation_state::load_for_layout(&layout)?;
let escalation = escalation_state::build_view(&layout, &escalation_entries);
let projection = policy_projection::build_view(&policy_sources, session_context, &escalation);
let evaluation = policy_projection::evaluate_action_family(&projection, action_family);
Ok(PolicyCheckReport {
command: "policy-check",
ok: true,
path: repo_root.display().to_string(),
profile: profile.to_string(),
project_id: marker.locality_id.clone(),
locality_id: marker.locality_id,
action_family,
policy_posture: projection.autonomous_default_posture,
decision: evaluation.decision,
reason_code: evaluation.reason_code,
summary: evaluation.summary,
policy_projection: projection,
})
}