shepherd-core 6.6.0

The harness-agnostic shepherd engine: domain types, configuration schema, and run state. Knows nothing about any CLI, harness, or process.
//! Deterministic plan-v2 parsing, structural validation, projection, and probe policy.
//!
//! Parsing and structure validation are pure. Filesystem and process observations enter
//! only through [`SourceProbe`], which keeps host I/O in the adapter crate.

use alloc::{string::String, vec::Vec};

use thiserror::Error;

mod environment;
mod parser;
mod render;
mod validate;

pub use environment::check_plan_environment;
pub use parser::parse_plan;
pub use render::{render_lane, render_topology};
pub use validate::{validate_plan_repository_path, validate_plan_structure};

pub const PLAN_SCHEMA: &str = "shepherd.plan/2";
pub const TOPOLOGY_SCHEMA: &str = "shepherd.plan-topology/2";
pub const PROBE_SCHEMA: &str = "shepherd.plan-probes/1";

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct PlanDocument {
    pub manifest: PlanManifestV2,
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct PlanManifestV2 {
    pub schema: String,
    pub run: String,
    pub seed: String,
    pub mesh: String,
    pub planning_evidence: String,
    pub goal: String,
    pub deliverables: Vec<String>,
    pub lanes: Vec<String>,
    pub root_roles: Vec<String>,
    pub child_lead_roles: Vec<String>,
    pub planning_lead: String,
    pub engineer_count: usize,
    pub review_rejection_limit: usize,
    pub fourth_rejection: String,
    pub root_continuation: String,
    pub exclusions: Vec<String>,
    pub capacity: PlanCapacity,
    pub nodes: Vec<PlanNode>,
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct PlanCapacity {
    pub logical_lane_limit: usize,
    pub host_process_ceiling: usize,
    pub project_spawn_max_parallel: usize,
    pub plan_process_ceiling: usize,
    pub parent_role_cap: usize,
    pub run_budget: usize,
    pub simultaneous_process_ceiling: usize,
    pub per_lane_child_wave_ceiling: usize,
    pub disk_min_mib: u64,
    pub model_quota: usize,
    pub backpressure: String,
    pub cargo_targets: Vec<LaneBinding>,
    pub conductors: Vec<LaneBinding>,
    pub schedule: Vec<CapacityWave>,
    pub scale_outcome: Option<String>,
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct LaneBinding {
    pub lane: String,
    pub value: String,
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct CapacityWave {
    pub lanes: Vec<String>,
    pub process_slots: usize,
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct PlanNode {
    pub id: String,
    pub seed_deliverables: Vec<String>,
    pub lane: String,
    pub role: String,
    pub work_kind: String,
    pub outcome: String,
    pub owns: Vec<String>,
    pub forbidden: Vec<String>,
    pub consumes: Vec<String>,
    pub produces: Vec<String>,
    pub depends_on: Vec<String>,
    pub red: GateContract,
    pub green: GateContract,
    pub eval: EvalContract,
    pub evidence: String,
    pub review: ReviewContract,
    pub failure_route: String,
    pub rollback: String,
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct GateContract {
    pub command: Vec<String>,
    pub expects: String,
    pub reason: String,
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct EvalContract {
    pub command: Vec<String>,
    pub threshold: Option<u32>,
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ReviewContract {
    pub role: String,
    pub predicate: String,
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct VerifiedPlanSeed {
    pub run: String,
    pub relative_path: String,
    pub mesh: String,
    pub deliverables: Vec<String>,
    pub outcomes: Vec<String>,
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct PlanTopology {
    pub schema: String,
    pub run: String,
    pub seed: String,
    pub mesh: String,
    pub planning_evidence: String,
    pub goal: String,
    pub deliverables: Vec<String>,
    pub lanes: Vec<PlanLane>,
    pub nodes: Vec<PlanNode>,
    pub topological_order: Vec<String>,
    pub capacity: PlanCapacity,
    pub capacity_policy: String,
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct PlanLane {
    pub id: String,
    pub conductor: String,
    pub cargo_target: String,
    pub node_ids: Vec<String>,
    pub deliverables: Vec<String>,
}

#[derive(
    Clone,
    Copy,
    Debug,
    Eq,
    PartialEq,
    strum::AsRefStr,
    strum::Display,
    strum::EnumCount,
    strum::EnumIs,
    strum::EnumString,
    strum::IntoStaticStr,
    strum::VariantNames,
)]
#[strum(ascii_case_insensitive, serialize_all = "snake_case")]
pub enum PathKind {
    File,
    Directory,
}

#[derive(
    Clone,
    Copy,
    Debug,
    Eq,
    PartialEq,
    strum::AsRefStr,
    strum::Display,
    strum::EnumCount,
    strum::EnumIs,
    strum::EnumString,
    strum::IntoStaticStr,
    strum::VariantNames,
)]
#[strum(ascii_case_insensitive, serialize_all = "snake_case")]
pub enum PathState {
    Missing,
    File,
    Directory,
    Symlink,
    Other,
}

#[derive(
    Clone,
    Copy,
    Debug,
    Eq,
    PartialEq,
    strum::AsRefStr,
    strum::Display,
    strum::EnumCount,
    strum::EnumIs,
    strum::EnumString,
    strum::IntoStaticStr,
    strum::VariantNames,
)]
#[strum(ascii_case_insensitive, serialize_all = "snake_case")]
pub enum ProbeExpectation {
    Modify,
    Create,
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub enum PlanEnvironmentProbe {
    Path {
        path: String,
        expectation: ProbeExpectation,
        kind: PathKind,
    },
    Symbol {
        path: String,
        symbol: String,
        expected_matches: usize,
    },
    Interface {
        path: String,
        schema: String,
        version: String,
    },
    Command {
        argv: Vec<String>,
        expected_exit: i32,
        semantic_marker: String,
    },
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct PlanProbeManifest {
    pub schema: String,
    pub worktree_identity: String,
    pub baseline: String,
    pub probes: Vec<PlanEnvironmentProbe>,
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct InterfaceObservation {
    pub schema: String,
    pub version: String,
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct CommandObservation {
    pub exit: i32,
    pub stdout: String,
    pub stderr: String,
}

pub trait SourceProbe {
    type Error: core::fmt::Display;

    fn worktree_identity(&self) -> Result<String, Self::Error>;
    fn baseline(&self) -> Result<String, Self::Error>;
    fn path_state(&self, path: &str) -> Result<PathState, Self::Error>;
    fn symbol_matches(&self, path: &str, symbol: &str) -> Result<usize, Self::Error>;
    fn interface(&self, path: &str) -> Result<InterfaceObservation, Self::Error>;
    fn run(&self, argv: &[String]) -> Result<CommandObservation, Self::Error>;
    fn available_disk_mib(&self) -> Result<u64, Self::Error>;
    fn model_quota(&self) -> Result<usize, Self::Error>;
    fn host_process_ceiling(&self) -> Result<usize, Self::Error>;
    fn project_spawn_max_parallel(&self) -> Result<usize, Self::Error>;
}

#[derive(Clone, Debug, Eq, PartialEq)]
pub struct PlanCheckReport {
    pub schema: String,
    pub run: String,
    pub worktree_identity: String,
    pub baseline: String,
    pub probe_count: usize,
    pub available_disk_mib: u64,
    pub model_quota: usize,
    pub host_process_ceiling: usize,
    pub project_spawn_max_parallel: usize,
}

#[derive(Clone, Debug, Eq, Error, PartialEq)]
pub enum PlanError {
    #[error("plan parse error: {0}")]
    Parse(String),
    #[error("plan structure error: {0}")]
    Structure(String),
    #[error("plan environment error: {0}")]
    Environment(String),
    #[error("unknown plan lane `{0}`")]
    UnknownLane(String),
}