orchestral-runtime 0.1.0

Thread Runtime with concurrency, interruption, scheduling, LLM planners, actions, and API for Orchestral
Documentation
use std::path::Path;

use serde_json::{json, Value};

use orchestral_core::types::{VerifyDecision, VerifyStatus};

use super::model::{parse_structured_file, parse_structured_patch_spec};
use super::support::{display_path, normalize_path};

pub(super) fn verify_structured_patch(
    patch_spec: &Value,
    inspection: Option<&Value>,
) -> Result<(VerifyDecision, String), String> {
    let patch_spec = parse_structured_patch_spec(patch_spec)?;
    if patch_spec.files.is_empty() {
        return Err("structured verify received empty patch_spec.files".to_string());
    }

    let mut checked_paths = Vec::new();
    let mut checked_ops = 0usize;
    let mut verified_changes = Vec::new();
    for file in &patch_spec.files {
        let path = normalize_path(Path::new(&file.path))?;
        let parsed = parse_structured_file(&path)?;
        checked_paths.push(display_path(&path));

        for operation in &file.operations {
            checked_ops += 1;
            match operation.op.as_str() {
                "set" => {
                    let Some(expected) = &operation.value else {
                        return Err(format!(
                            "structured set operation '{}' is missing value",
                            operation.path
                        ));
                    };
                    let Some(actual) = parsed.pointer(&operation.path) else {
                        return Ok((
                            VerifyDecision {
                                status: VerifyStatus::Failed,
                                reason: format!(
                                    "structured path '{}' is missing after patch",
                                    operation.path
                                ),
                                evidence: json!({
                                    "path": display_path(&path),
                                    "operation": operation,
                                }),
                            },
                            "Structured verify failed.".to_string(),
                        ));
                    };
                    if actual != expected {
                        return Ok((
                            VerifyDecision {
                                status: VerifyStatus::Failed,
                                reason: format!(
                                    "structured path '{}' does not match expected value",
                                    operation.path
                                ),
                                evidence: json!({
                                    "path": display_path(&path),
                                    "operation": operation,
                                    "actual": actual,
                                }),
                            },
                            "Structured verify failed.".to_string(),
                        ));
                    }
                    verified_changes.push(format_verified_change(file.path.as_str(), operation));
                }
                "remove" => {
                    if parsed.pointer(&operation.path).is_some() {
                        return Ok((
                            VerifyDecision {
                                status: VerifyStatus::Failed,
                                reason: format!(
                                    "structured path '{}' still exists after remove",
                                    operation.path
                                ),
                                evidence: json!({
                                    "path": display_path(&path),
                                    "operation": operation,
                                }),
                            },
                            "Structured verify failed.".to_string(),
                        ));
                    }
                    verified_changes.push(format_verified_change(file.path.as_str(), operation));
                }
                other => {
                    return Err(format!("unsupported structured operation '{}'", other));
                }
            }
        }
    }

    let summary = build_verify_summary(&verified_changes);

    Ok((
        VerifyDecision {
            status: VerifyStatus::Passed,
            reason: "structured patch verified".to_string(),
            evidence: json!({
                "checked_paths": checked_paths,
                "checked_operations": checked_ops,
                "verified_changes": verified_changes,
                "inspection_target_count": inspection
                    .and_then(|value| value.get("target_count"))
                    .cloned()
                    .unwrap_or(Value::Null),
            }),
        },
        summary,
    ))
}

fn build_verify_summary(changes: &[String]) -> String {
    if changes.is_empty() {
        return "Structured patch verified.".to_string();
    }
    let mut summary = format!("Structured patch verified. Changes: {}", changes.join("; "));
    if !summary.ends_with('.') {
        summary.push('.');
    }
    summary
}

fn format_verified_change(
    path: &str,
    operation: &super::model::StructuredPatchOperation,
) -> String {
    let target = operation
        .selector
        .as_deref()
        .unwrap_or(operation.path.as_str());
    let reason = operation.reason.as_deref().unwrap_or("verified");
    match operation.op.as_str() {
        "set" => {
            let rendered = operation
                .value
                .as_ref()
                .map(render_value)
                .unwrap_or_else(|| "<missing>".to_string());
            format!("{} -> set {} = {} ({})", path, target, rendered, reason)
        }
        "remove" => format!("{} -> removed {} ({})", path, target, reason),
        other => format!("{} -> {} {} ({})", path, other, target, reason),
    }
}

fn render_value(value: &Value) -> String {
    match value {
        Value::String(text) => format!("{:?}", text),
        other => other.to_string(),
    }
}