illumitech-forge-cli 1.5.0

Evidence-backed agent execution CLI
Documentation
//! Local planning attempts and a closed, typed export boundary. No transport.
use super::*;
use std::time::Instant;
#[derive(Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub struct PlanningMetrics {
    pub schema_version: u32,
    pub attempt_id: String,
    pub status: String,
    pub active_ms: Option<u64>,
    pub tokens: forge_domain::usage::TokenUsage,
    pub coverage: String,
}
pub struct Attempt {
    pub id: String,
    dir: PathBuf,
    started: Instant,
    finished: bool,
}
impl Attempt {
    pub fn begin(ops: &Path, output: &Path, root: &Path) -> Result<Self> {
        let mut nonce = [0u8; 16];
        getrandom::fill(&mut nonce).map_err(|e| anyhow::anyhow!("attempt ID: {e}"))?;
        let id = digest(&nonce);
        let dir = ops.join("attempts").join(&id);
        guided::create(&dir.join("started.json"), &json!({"schema_version":1,"attempt_id":id,"planning_receipt":digest(output.to_string_lossy().as_bytes()),"project":digest(root.to_string_lossy().as_bytes()),"status":"planning","started_ms":crate::duration::now_ms()}).to_string())?;
        Ok(Self {
            id,
            dir,
            started: Instant::now(),
            finished: false,
        })
    }
    pub fn finish(&mut self, passed: bool, observed: &Value) -> Result<()> {
        let usage = forge_domain::usage::TokenUsage::new(
            observed["total"]["inputTokens"].as_u64(),
            observed["total"]["cachedInputTokens"].as_u64(),
            observed["total"]["outputTokens"].as_u64(),
        );
        guided::create(&self.dir.join("finished.json"), &json!({"schema_version":1,"attempt_id":self.id,"status":if passed{"planned"}else{"failed"},"active_ms":self.started.elapsed().as_millis(),"tokens":usage,"coverage":if usage.total_tokens.is_some(){"complete"}else{"unavailable"}}).to_string())?;
        self.finished = true;
        Ok(())
    }
}
impl Drop for Attempt {
    fn drop(&mut self) {
        if !self.finished {
            // A crash may leave only started.json. Neither case implies success or zero tokens.
            let _ = guided::create(&self.dir.join("interrupted.json"), &json!({"schema_version":1,"attempt_id":self.id,"status":"interrupted","active_ms":self.started.elapsed().as_millis(),"tokens":null,"coverage":"unavailable"}).to_string());
        }
    }
}
fn outcome(value: &Value) -> &'static str {
    match value.as_str().unwrap_or("") {
        "passed" => "passed",
        "failed" => "failed",
        "cancelled" => "cancelled",
        "paused" => "paused",
        "blocked" => "blocked",
        "prepared" => "prepared",
        "awaiting_approval" => "awaiting_approval",
        "running" => "running",
        "evaluating" => "evaluating",
        _ => "unknown",
    }
}
/// Every emitted field is constructed here. Never copy arbitrary report subtrees.
fn shareable(id: &str, status: &Value, human_minutes: Option<u64>) -> Value {
    let r = &status["report"];
    let planning = &status["planning_metrics"];
    let tokens = &r["token_usage"];
    let coverage = match tokens["coverage"].as_str() {
        Some("complete") => "complete",
        Some("partial") => "partial",
        _ => "unavailable",
    };
    let attempts = r["run"]["tasks"].as_object().and_then(|tasks| {
        tasks
            .values()
            .try_fold(0u64, |n, t| n.checked_add(t["attempts"].as_u64()?))
    });
    json!({"schema_version":1,"export_version":1,"workflow_pseudonym":digest(id.as_bytes()),"forge_version":env!("CARGO_PKG_VERSION"),"outcome":outcome(status.get("effective_status").unwrap_or(&status["status"])),"evidence_current":r["evidence_current"].as_bool().unwrap_or(false),"metrics":{"execution_attempts":attempts,"active_ms":r["run"]["duration"]["observed_active_ms"].as_u64(),"wait_ms":status["journal_metrics"]["wait_ms"].as_u64(),"completed_wait_ms":status["journal_metrics"]["completed_wait_ms"].as_u64(),"observed_stops":status["journal_metrics"]["observed_stops"].as_u64(),"interruptions":if status["journal_metrics"]["active_or_uncertain"]==false{status["journal_metrics"]["observed_stops"].as_u64()}else{None},"interruption_coverage":if status["journal_metrics"]["active_or_uncertain"]==false{"persisted_journal"}else{"unknown"},"decision_stops":status["journal_metrics"]["decision_stops"].as_u64(),"external_stops":status["journal_metrics"]["external_stops"].as_u64(),"tokens":{"coverage":coverage,"input":tokens["input_tokens"].as_u64(),"output":tokens["output_tokens"].as_u64(),"total":tokens["total_tokens"].as_u64()},"human_minutes":human_minutes,"human_time_source":if human_minutes.is_some(){"manual"}else{"unknown"}},"planning_attempt_pseudonym":planning["attempt_id"].as_str().map(|v|digest(v.as_bytes())),"planning_active_ms":planning["active_ms"].as_u64(),"full_active_ms":planning["active_ms"].as_u64().zip(r["run"]["duration"]["observed_active_ms"].as_u64()).and_then(|(a,b)|a.checked_add(b)),"wait_scope":"execution stops until explicit resume; native approval and pre-execution wait unobserved","measurement_scope":if planning["status"]=="planned"{"planning_and_execution"}else{"execution_only"},"planning_coverage":if planning["coverage"]=="complete"{"complete"}else{"unavailable"},"coverage_scope":"linked planning and execution; separate failed attempts remain visible in the local journal","network_sent":false})
}
pub fn export(
    root: &Path,
    ops: &Path,
    id: &str,
    output: Option<&Path>,
    human_minutes: Option<u64>,
) -> Result<Value> {
    let value = if attempt_id(id) {
        attempt_report(root, ops, id, human_minutes)?
    } else {
        let status = recovery::status(root, ops, id)?;
        shareable(id, &status, human_minutes)
    };
    if let Some(path) = output {
        ensure!(
            !guided::safe_path(path)?.exists(),
            "export already exists; choose a new versioned filename"
        );
        guided::create(path, &serde_json::to_string_pretty(&value)?)?;
    }
    Ok(
        json!({"schema_version":1,"status":if output.is_some(){"written"}else{"preview"},"report":value,"mutation":output.is_some()}),
    )
}
fn attempt_id(id: &str) -> bool {
    id.len() == 64 && id.bytes().all(|b| b.is_ascii_hexdigit())
}
fn attempt_report(root: &Path, ops: &Path, id: &str, human_minutes: Option<u64>) -> Result<Value> {
    ensure!(attempt_id(id), "invalid attempt ID");
    let root = guided::root(root)?;
    let dir = guided::external(ops, &root)?.join("attempts").join(id);
    let started: Value = read_json(&dir.join("started.json"))?;
    ensure!(
        started["project"] == digest(root.to_string_lossy().as_bytes())
            && started["attempt_id"] == id
            && started["schema_version"] == 1,
        "attempt identity mismatch"
    );
    let receipt: Value = if dir.join("finished.json").exists() {
        read_json(&dir.join("finished.json"))?
    } else if dir.join("interrupted.json").exists() {
        read_json(&dir.join("interrupted.json"))?
    } else {
        Value::Null
    };
    ensure!(
        receipt.is_null() || receipt["attempt_id"] == id,
        "attempt receipt mismatch"
    );
    let state = match receipt["status"].as_str() {
        Some("planned") => "planned",
        Some("failed") => "failed",
        Some("interrupted") => "interrupted",
        _ => "unknown",
    };
    Ok(
        json!({"schema_version":1,"export_version":1,"attempt_pseudonym":digest(id.as_bytes()),"forge_version":env!("CARGO_PKG_VERSION"),"outcome":state,"coverage":if receipt["coverage"]=="complete"{"complete"}else{"unavailable"},"active_ms":receipt["active_ms"].as_u64(),"tokens":{"input":receipt["tokens"]["input_tokens"].as_u64(),"output":receipt["tokens"]["output_tokens"].as_u64(),"total":receipt["tokens"]["total_tokens"].as_u64()},"human_minutes":human_minutes,"human_time_source":if human_minutes.is_some(){"manual"}else{"unknown"},"workflow_link":"use the same attempt pseudonym in workflow exports; do not count both as separate attempts","network_sent":false}),
    )
}
pub fn attempts(root: &Path, ops: &Path) -> Result<Value> {
    let root = guided::root(root)?;
    let ops = guided::external(ops, &root)?;
    let directory = guided::safe_path(&ops.join("attempts"))?;
    let mut rows = vec![];
    if directory.exists() {
        for entry in std::fs::read_dir(directory)?.take(1000) {
            let entry = entry?;
            let id = entry.file_name().to_string_lossy().into_owned();
            if entry.file_type()?.is_dir() && attempt_id(&id) {
                rows.push(json!({"id":id,"report":attempt_report(&root,&ops,&id,None)?}));
            }
        }
    }
    rows.sort_by(|a, b| a["id"].as_str().cmp(&b["id"].as_str()));
    Ok(
        json!({"schema_version":1,"attempts":rows,"limit":1000,"mutation":false,"native_inference_calls":0}),
    )
}
#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn export_drops_arbitrary_sensitive_strings_and_unknown_is_not_zero() {
        let raw = json!({"status":"SECRET","prompt":"SECRET","report":{"token_usage":{"coverage":"SECRET","total_tokens":"SECRET"},"run":{"tasks":{"SECRET":{"attempts":1,"logs":"SECRET"}}},"remote":"SECRET"}});
        let value = shareable("SECRET", &raw, Some(12));
        assert!(!value.to_string().contains("SECRET"));
        assert!(value["metrics"]["tokens"]["total"].is_null());
        assert_eq!(value["metrics"]["human_time_source"], "manual");
    }
    #[test]
    fn failed_and_dropped_planning_attempts_are_preserved() {
        let d = tempfile::tempdir().unwrap();
        let base = d.path().canonicalize().unwrap();
        let mut a = Attempt::begin(&base, &base.join("proposal.json"), &base).unwrap();
        a.finish(false, &Value::Null).unwrap();
        let b = Attempt::begin(&base, &base.join("proposal.json"), &base).unwrap();
        let path = b.dir.clone();
        drop(b);
        assert!(a.dir.join("finished.json").exists());
        assert!(path.join("interrupted.json").exists());
        assert_ne!(a.dir, path);
    }
    #[test]
    fn failed_attempt_without_workflow_can_be_previewed_and_exported() {
        let d = tempfile::tempdir().unwrap();
        let base = d.path().canonicalize().unwrap();
        let root = base.join("project");
        std::fs::create_dir(&root).unwrap();
        git(&root, &["init", "-q"]).unwrap();
        let ops = base.join("ops");
        let mut a = Attempt::begin(&ops, &ops.join("proposal.json"), &root).unwrap();
        a.finish(false, &Value::Null).unwrap();
        let preview = export(&root, &ops, &a.id, None, None).unwrap();
        assert_eq!(preview["status"], "preview");
        assert_eq!(preview["report"]["outcome"], "failed");
        assert!(preview["report"]["tokens"]["total"].is_null());
        assert!(!preview.to_string().contains(root.to_str().unwrap()));
        let output = base.join("attempt-v1.json");
        export(&root, &ops, &a.id, Some(&output), Some(3)).unwrap();
        assert!(output.exists());
        assert_eq!(
            attempts(&root, &ops).unwrap()["attempts"]
                .as_array()
                .unwrap()
                .len(),
            1
        );
        let other = base.join("other");
        std::fs::create_dir(&other).unwrap();
        git(&other, &["init", "-q"]).unwrap();
        assert!(export(&other, &ops, &a.id, None, None).is_err());
    }
}