car-ffi-common 0.32.1

Shared logic for FFI bindings (NAPI, PyO3) — JSON wrappers for verify, multi-agent, scheduler
//! JSON wrappers for car-verify functions.

use car_ir::{ActionProposal, ToolSchema};
use serde_json::Value;
use std::collections::{HashMap, HashSet};

/// Verify a proposal from JSON inputs. Returns result JSON.
///
/// When `tool_schemas_json` is supplied (a JSON array of `ToolSchema`
/// objects, e.g. `[{"name":"echo","parameters":{...}}]`), each
/// `tool_call`'s parameters are validated against the matching
/// schema — catching type mismatches and missing required fields, not
/// just unknown tools (car-releases#56). `tool_names` remains
/// supported for existence-only checks when no schemas are at hand;
/// `tool_schemas_json` takes precedence when both are passed.
pub fn verify(
    proposal_json: &str,
    initial_state_json: Option<&str>,
    tool_names: Option<Vec<String>>,
    max_actions: usize,
    tool_schemas_json: Option<&str>,
) -> Result<String, String> {
    let proposal: ActionProposal =
        serde_json::from_str(proposal_json).map_err(|e| format!("invalid proposal JSON: {}", e))?;
    let initial_state: Option<HashMap<String, Value>> = initial_state_json
        .map(|s| serde_json::from_str(s))
        .transpose()
        .map_err(|e| format!("invalid state JSON: {}", e))?;

    let result = if let Some(schemas_json) = tool_schemas_json {
        let schemas: Vec<ToolSchema> = serde_json::from_str(schemas_json)
            .map_err(|e| format!("invalid tool schemas JSON: {}", e))?;
        let map: HashMap<String, ToolSchema> =
            schemas.into_iter().map(|s| (s.name.clone(), s)).collect();
        car_verify::verify_with_schemas(&proposal, initial_state.as_ref(), Some(&map), max_actions)
    } else {
        let tools: Option<HashSet<String>> = tool_names.map(|v| v.into_iter().collect());
        car_verify::verify(
            &proposal,
            initial_state.as_ref(),
            tools.as_ref(),
            max_actions,
        )
    };

    serde_json::to_string(&serde_json::json!({
        "valid": result.valid,
        "issues": result.issues.iter().map(|i| serde_json::json!({
            "action_id": i.action_id,
            "severity": i.severity,
            "message": i.message,
        })).collect::<Vec<_>>(),
        "simulated_state": result.simulated_state,
        "execution_levels": result.execution_levels,
        "conflicts": result.conflicts,
        // Evidence bundle: the verifier's declared scope — checks run,
        // assumptions, untested regions, residual risks, coverage
        // confidence (survey "Code as Agent Harness" §5.2.2).
        "evidence": result.evidence,
    }))
    .map_err(|e| e.to_string())
}

/// Simulate a proposal from JSON inputs. Returns state JSON.
pub fn simulate(proposal_json: &str, initial_state_json: Option<&str>) -> Result<String, String> {
    let proposal: ActionProposal =
        serde_json::from_str(proposal_json).map_err(|e| format!("invalid proposal JSON: {}", e))?;
    let initial_state: Option<HashMap<String, Value>> = initial_state_json
        .map(|s| serde_json::from_str(s))
        .transpose()
        .map_err(|e| format!("invalid state JSON: {}", e))?;

    let state = car_verify::simulate(&proposal, initial_state.as_ref());
    serde_json::to_string(&state).map_err(|e| e.to_string())
}

/// Optimize a proposal from JSON. Returns optimized proposal JSON.
pub fn optimize(proposal_json: &str) -> Result<String, String> {
    let proposal: ActionProposal =
        serde_json::from_str(proposal_json).map_err(|e| format!("invalid proposal JSON: {}", e))?;
    let optimized = car_verify::optimize(&proposal);
    serde_json::to_string(&optimized).map_err(|e| e.to_string())
}

/// Check a proposal for transactional conflicts against the current shared
/// state (survey §4.3/§5.2.4). `versions_json` is a JSON object mapping
/// state key → current version (from `StateStore::versions`);
/// `state_json` (optional) maps key → current value for value-level
/// assumption checks. Returns the `TransactionReport` JSON:
/// `{ consistent, conflicts: [{ kind, key, actions, explanation,
/// resolution }] }`.
pub fn transaction_check(
    proposal_json: &str,
    versions_json: Option<&str>,
    state_json: Option<&str>,
) -> Result<String, String> {
    let proposal: ActionProposal = crate::from_json("proposal", proposal_json)?;
    let versions: HashMap<String, u64> =
        crate::from_json_opt("versions", versions_json)?.unwrap_or_default();
    let state: Option<HashMap<String, Value>> = crate::from_json_opt("state", state_json)?;
    let report = car_verify::check_transaction(&proposal, &versions, state.as_ref());
    crate::to_json(&report)
}

/// Like [`transaction_check`], but augments each action's write set with the keys
/// a verified Code World Model predicts it writes (Code World Models Slice 3b —
/// pre-flight conflict detection). `predictions_json` is a JSON object mapping an
/// `action_id` → array of predicted write keys (the caller produced them by
/// running the generated model). Predicted writes are unioned into the action's
/// declared write set before the same conflict analysis runs, so a tool that
/// writes a key it didn't declare is caught *before* execution. Returns the same
/// `TransactionReport` JSON as `transaction_check`.
pub fn transaction_check_with_predictions(
    proposal_json: &str,
    versions_json: Option<&str>,
    state_json: Option<&str>,
    predictions_json: &str,
) -> Result<String, String> {
    let proposal: ActionProposal = crate::from_json("proposal", proposal_json)?;
    let versions: HashMap<String, u64> =
        crate::from_json_opt("versions", versions_json)?.unwrap_or_default();
    let state: Option<HashMap<String, Value>> = crate::from_json_opt("state", state_json)?;
    let predicted: HashMap<String, Vec<String>> = crate::from_json("predictions", predictions_json)?;
    let report = car_verify::check_transaction_with_predictions(
        &proposal,
        &versions,
        state.as_ref(),
        &predicted,
    );
    crate::to_json(&report)
}

/// Static information-flow + tool-sequence safety check over a plan
/// (arXiv 2601.08012; `docs/proposals/verifiable-tool-safety.md`). `labels_json`
/// is a JSON object mapping `tool_name` → `ToolLabels`
/// (`{ capability?, confidentiality, trust, sink, declassifier }`, all
/// defaulting to public/trusted/non-sink). `policy_json` (optional) is a
/// `FlowPolicy` (`{ min_confidential, forbidden_sequences: [[before, after]] }`).
/// Returns the `FlowReport` JSON `{ safe, violations: [{ kind, actions, key?,
/// explanation, mitigation }] }` — flagging sensitive data reaching an
/// exfiltration/untrusted sink and forbidden tool orderings, statically, before
/// execution. Tools absent from `labels_json` are unconstrained, so an empty map
/// is trivially safe.
pub fn check_information_flow(
    proposal_json: &str,
    labels_json: &str,
    policy_json: Option<&str>,
) -> Result<String, String> {
    let proposal: ActionProposal = crate::from_json("proposal", proposal_json)?;
    let labels: HashMap<String, car_verify::ToolLabels> = crate::from_json("labels", labels_json)?;
    let policy: car_verify::FlowPolicy =
        crate::from_json_opt("policy", policy_json)?.unwrap_or_default();
    let report = car_verify::check_information_flow(&proposal, &labels, &policy);
    crate::to_json(&report)
}

/// Map an information-flow `FlowReport` to an enforcement decision (Slice 2 of
/// `docs/proposals/verifiable-tool-safety.md`). `report_json` is the output of
/// `check_information_flow`; `gate_policy_json` (optional) is a `FlowGatePolicy`
/// (`{ on_sensitive_to_sink, on_forbidden_sequence }`, each `allow` |
/// `require_approval` | `block`; defaults block exfiltration and escalate
/// forbidden orderings). Returns the `FlowGateDecision` JSON `{ action, blocked,
/// needs_approval, reason }` — turning the advisory check into a gate that
/// blocks clear hazards and escalates ambiguous ones to HITL.
pub fn gate_information_flow(
    report_json: &str,
    gate_policy_json: Option<&str>,
) -> Result<String, String> {
    let report: car_verify::FlowReport = crate::from_json("report", report_json)?;
    let policy: car_verify::FlowGatePolicy =
        crate::from_json_opt("gate policy", gate_policy_json)?.unwrap_or_default();
    let decision = car_verify::gate_flow(&report, &policy);
    crate::to_json(&decision)
}

/// Enforce a `FlowGateDecision` against the durable approval ledger (verifiable
/// tool safety, Slice 3 — the HITL bridge). `decision_json` is the output of
/// `gate_information_flow`; `approvals_json` (optional) is a JSON array of prior
/// `ApprovalRecord`s (`{ fingerprint, required_tier, decision, reviewer, reason,
/// evidence?, decided_at }`) — the human-in-the-loop history. A `require_approval`
/// hazard a human previously **approved** (by `flow_fingerprint`) is allowed,
/// one **rejected** is blocked, an unseen one becomes pending. Returns the
/// `FlowEnforcement` JSON `{ allow, blocked, pending: [{ fingerprint, violation
/// }], reason }`. Stateless: the ledger is rebuilt from `approvals_json` each
/// call (the daemon owns the live ledger).
pub fn enforce_information_flow(
    decision_json: &str,
    approvals_json: Option<&str>,
) -> Result<String, String> {
    let decision: car_verify::FlowGateDecision = crate::from_json("decision", decision_json)?;
    let mut ledger = car_policy::permission::ApprovalLedger::new();
    if let Some(aj) = approvals_json {
        let records: Vec<car_policy::permission::ApprovalRecord> =
            crate::from_json("approvals", aj)?;
        for r in records {
            ledger
                .record(r)
                .map_err(|e| format!("record approval: {e}"))?;
        }
    }
    let enforcement = car_policy::enforce_flow(&decision, &ledger);
    crate::to_json(&enforcement)
}

/// Check equivalence of two proposals from JSON.
pub fn equivalent(p1_json: &str, p2_json: &str) -> Result<bool, String> {
    let p1: ActionProposal =
        serde_json::from_str(p1_json).map_err(|e| format!("invalid proposal1 JSON: {}", e))?;
    let p2: ActionProposal =
        serde_json::from_str(p2_json).map_err(|e| format!("invalid proposal2 JSON: {}", e))?;
    Ok(car_verify::equivalent(&p1, &p2, None))
}

/// Analyze a schedule of multi-agent read-generate-write operations for the four
/// concurrency anomalies of arXiv 2606.17182 and classify the achieved
/// consistency level (`docs/proposals/concurrency-anomalies.md`). `ops_json` is a
/// JSON array of `AgentOp` (`{ id, agent?, read_set?, write_set?, tools_read?,
/// tools_written?, depends_on?, read_at?, commit_at? }`; omitted fields default).
/// Returns the `ConcurrencyReport` JSON `{ level: "l0".."l4", serializable,
/// anomalies: [{ anomaly, key, ops, explanation }] }` — `level` set by the most
/// severe anomaly present (causal-cascade → l0 … none → l4 serializable).
pub fn analyze_concurrency(ops_json: &str) -> Result<String, String> {
    let ops: Vec<car_verify::AgentOp> = crate::from_json("ops", ops_json)?;
    let report = car_verify::analyze_concurrency(&ops);
    crate::to_json(&report)
}

/// Gate a concurrency report into remediations under a policy (arXiv 2606.17182
/// Slice 2 — `docs/proposals/concurrency-anomalies.md`; the analogue of
/// `gate_information_flow` for concurrency). `report_json` is a
/// `ConcurrencyReport` (the output of [`analyze_concurrency`]); `policy_json`
/// (optional) is a `ConcurrencyGatePolicy` `{ abort_at_or_below,
/// require_approval_at_or_below }` (levels `"l0".."l4"`; defaults abort on l0,
/// approval on l1). Returns the `ConcurrencyGate` JSON `{ safe, level, abort,
/// remediations: [{ anomaly, remediation: { kind, .. }, disposition }] }`.
pub fn gate_concurrency(report_json: &str, policy_json: Option<&str>) -> Result<String, String> {
    let report: car_verify::ConcurrencyReport = crate::from_json("report", report_json)?;
    let policy: car_verify::ConcurrencyGatePolicy = match policy_json {
        Some(p) => crate::from_json("policy", p)?,
        None => car_verify::ConcurrencyGatePolicy::default(),
    };
    let gate = car_verify::gate_concurrency(&report, &policy);
    crate::to_json(&gate)
}

/// Statically verify a workflow graph for structural defects (arXiv 2603.20356
/// "Agentproof" — `docs/proposals/workflow-graph-verification.md`). `graph_json`
/// is a `WorkflowGraph` `{ entry, terminals: [..], stages: [..], edges: [{ from,
/// to, condition? }] }`. Returns the `WorkflowVerifyReport` JSON `{ sound,
/// defects: [{ kind, subject, witness: [..stage path..], explanation }] }` —
/// `kind` is `missing_entry` | `dangling_edge` | `unreachable_stage` | `dead_end`
/// | `no_exit_reachable` | `unreachable_terminal`. Each defect carries a witness
/// path from the entry, the actionable counter-example for a repair loop.
pub fn verify_workflow_graph(graph_json: &str) -> Result<String, String> {
    let graph: car_verify::WorkflowGraph = crate::from_json("graph", graph_json)?;
    let report = car_verify::verify_workflow_graph(&graph);
    crate::to_json(&report)
}

/// Check temporal safety policies over a workflow graph (arXiv 2603.20356
/// "Agentproof" Slice 2 — `docs/proposals/workflow-graph-verification.md`).
/// `graph_json` is a `WorkflowGraph`; `policies_json` is a JSON array of
/// `TemporalPolicy` (currently `{ kind: "precedes", earlier, later, name? }` —
/// `earlier` must be visited before `later` on every path, e.g. a human-gate).
/// Returns the `PolicyReport` JSON `{ compliant, violations: [{ policy, stage,
/// witness: [..path..], explanation }] }`; each violation's witness is a path
/// that reaches the guarded stage without the required predecessor.
pub fn check_workflow_policies(
    graph_json: &str,
    policies_json: &str,
) -> Result<String, String> {
    let graph: car_verify::WorkflowGraph = crate::from_json("graph", graph_json)?;
    let policies: Vec<car_verify::TemporalPolicy> = crate::from_json("policies", policies_json)?;
    let report = car_verify::check_temporal_policies(&graph, &policies);
    crate::to_json(&report)
}

/// Verify a plan's sequential feasibility by symbolic forward simulation (arXiv
/// 2603.14730 "GNNVerifier" — `docs/proposals/plan-precondition-verification.md`;
/// the deterministic, training-free counterpart). `request_json` is a
/// `PlanCheckRequest` `{ initial: [..facts..], steps: [{ id, preconditions?,
/// add_effects?, del_effects? }], goal: [..facts..] }`. Returns the
/// `PlanCheckReport` JSON `{ valid, defects: [{ kind, step?, fact, explanation }],
/// final_state: [..facts..] }` — `kind` is `unmet_precondition` |
/// `goal_not_achieved`. Effects apply even when a precondition fails, so one pass
/// surfaces every defect.
pub fn check_plan(request_json: &str) -> Result<String, String> {
    let req: car_verify::PlanCheckRequest = crate::from_json("request", request_json)?;
    let report = car_verify::check_plan(&req);
    crate::to_json(&report)
}

#[derive(serde::Deserialize)]
struct IntentCheckRequest {
    #[serde(default)]
    intent: car_verify::IntentSpec,
    #[serde(default)]
    actions: Vec<car_verify::IntentAction>,
}

/// Intent-grounded verify-before-commit (arXiv 2601.05755 "VIGIL"): flag actions
/// that drift outside the user's declared intent, blocking commit when the
/// drifting action is influenced by an untrusted tool result (tool-stream
/// injection). `request_json` is `{ intent: { allowed_tools?, allowed_resources?,
/// forbidden_capabilities? }, actions: [{ id, tool?, targets?, capabilities?,
/// depends_on?, untrusted? }] }`. Returns the `IntentReport` JSON `{ safe,
/// commit_blocked: [action_id..], violations: [{ action, kind, detail,
/// tool_influenced, explanation }] }` — `kind` is `tool_out_of_intent` |
/// `target_out_of_intent` | `forbidden_capability`.
pub fn check_intent(request_json: &str) -> Result<String, String> {
    let req: IntentCheckRequest = crate::from_json("request", request_json)?;
    let report = car_verify::check_intent(&req.intent, &req.actions);
    crate::to_json(&report)
}

#[derive(serde::Deserialize)]
struct IntentPlanRequest {
    #[serde(default)]
    intent: car_verify::IntentSpec,
    #[serde(default)]
    actions: Vec<car_ir::Action>,
    /// Tools whose output is attacker-controllable (web/file/remote reads).
    #[serde(default)]
    untrusted_tools: Vec<String>,
    /// Specific action ids the executor marked tainted from tool-result
    /// provenance.
    #[serde(default)]
    untrusted_ids: Vec<String>,
}

/// Intent-grounded verify-before-commit straight from a plan's `car_ir` actions
/// (VIGIL Slice 4 — the IR populater + check in one call). `request_json` is `{
/// intent, actions: [Action], untrusted_tools?: [..], untrusted_ids?: [..] }`. The
/// runtime derives the `IntentAction`s from its own IR — `tool`, `expected_effects`
/// → targets, the executor's dependency edges → `depends_on`,
/// `metadata.capabilities` → capabilities, and `untrusted` from the supplied
/// tool/id provenance — then runs [`check_intent`]. Returns the same
/// `IntentReport` JSON. Saves a caller hand-assembling the check input.
pub fn check_intent_plan(request_json: &str) -> Result<String, String> {
    let req: IntentPlanRequest = crate::from_json("request", request_json)?;
    let untrusted_tools: std::collections::HashSet<String> =
        req.untrusted_tools.into_iter().collect();
    let untrusted_ids: std::collections::HashSet<String> = req.untrusted_ids.into_iter().collect();
    let actions = car_verify::intent_actions_from(&req.actions, &untrusted_tools, &untrusted_ids);
    let report = car_verify::check_intent(&req.intent, &actions);
    crate::to_json(&report)
}

/// Map an intent report to an enforcement disposition (VIGIL Slice 2 — the gate).
/// `report_json` is an `IntentReport` (from [`check_intent`]); `gate_policy_json`
/// is an optional `IntentGatePolicy` `{ on_untainted_drift: "allow" |
/// "require_approval" | "block" }` (default `require_approval`). Injections
/// (tool-stream-influenced drift) and forbidden capabilities always block
/// regardless of policy. Returns the `IntentGateDecision` JSON `{ action, blocked,
/// needs_approval, reason }`.
pub fn gate_intent(report_json: &str, gate_policy_json: Option<&str>) -> Result<String, String> {
    let report: car_verify::IntentReport = crate::from_json("report", report_json)?;
    let policy: car_verify::IntentGatePolicy =
        crate::from_json_opt("gate policy", gate_policy_json)?.unwrap_or_default();
    let decision = car_verify::gate_intent(&report, &policy);
    crate::to_json(&decision)
}

/// Enforce an intent gate decision against the durable approval ledger (VIGIL
/// Slice 3 — the HITL bridge). `decision_json` is the output of [`gate_intent`];
/// `approvals_json` is an optional `ApprovalRecord[]` seeding the ledger. An
/// out-of-intent action a human previously approved commits; one they rejected is
/// blocked; a novel one is pending. Hard blocks (injections / forbidden
/// capabilities) are never committable. Returns the `IntentEnforcement` JSON
/// `{ commit, blocked, pending: [{ fingerprint, violation }], reason }`.
pub fn enforce_intent(
    decision_json: &str,
    approvals_json: Option<&str>,
) -> Result<String, String> {
    let decision: car_verify::IntentGateDecision = crate::from_json("decision", decision_json)?;
    let mut ledger = car_policy::permission::ApprovalLedger::new();
    if let Some(aj) = approvals_json {
        let records: Vec<car_policy::permission::ApprovalRecord> =
            crate::from_json("approvals", aj)?;
        for r in records {
            ledger
                .record(r)
                .map_err(|e| format!("record approval: {e}"))?;
        }
    }
    let enforcement = car_policy::enforce_intent(&decision, &ledger);
    crate::to_json(&enforcement)
}