supercode-harness 0.4.3

The optional native Supercode agent and tool harness
Documentation
//! Acceptance test closing the same coverage-gap class in TR-10
//! (`.volter/tracker/markdown/TR-10.md`) that `tr2_dedup_guarantor.rs`/
//! `tr3_diff_guarantor.rs`/`tr4_normalize_guarantor.rs` already closed for
//! `DuplicateOutput`/`FileReadDiffed`/`OutputNormalized`: every existing
//! `ToolInputElided` test (`tool_input_elision.rs`) mints AND inverts against
//! the SAME in-memory `Session`/message vector (`project_messages` called
//! directly on a hand-built slice). None of them drive `ToolInputElided`
//! through the path the control guarantor actually re-runs: live `Agent` ->
//! recorder(disk sidecar) -> **reload from disk** -> offline
//! `verify_log`/`invert`.
//!
//! THE GAP: TR-12's regression (`tr12_cap_supersession.rs`) proved that a
//! hash minted from an in-memory copy of `history` can silently diverge from
//! what a reloaded sidecar recomputes, once `cap_tool_output` mutates the
//! bytes behind the mint site. `ToolInputElided`'s mint site (`reduce.rs`'s
//! TR-10 pass, run from `Agent::build_request_messages` over
//! `self.history[1..]`) sits in exactly the same position in the pipeline —
//! nothing in the existing TR-10 suite would have caught an equivalent
//! divergence, because none of those tests ever touch a recorder, a sidecar
//! file, or a disk reload. This test fills that gap, reusing the exact
//! harness idiom `tr2_dedup_guarantor.rs`/`tr4_normalize_guarantor.rs`
//! established (`Agent::with_parts` + a scripted `Provider`, a REAL builtin
//! tool, `SidecarWriter`/`SessionStore`, offline reload via
//! `Session::from_sidecar_str`, `verify_log` + `invert`) — with the actual
//! built-in `write_file` tool (the one TR-10's default
//! `tool_input_elidable_fields` names) so the call genuinely executes,
//! genuinely persists to disk, and genuinely succeeds — never a
//! hand-built/synthetic success marker.
//!
//! dev/01 (guarantor-path regression): a live agent runs a real `write_file`
//! tool call with a 20,000-byte `content` argument. The call executes
//! successfully (the file really lands on disk), minting a `ToolInputElided`
//! reduction over the assistant's `tool_calls` arguments. The sidecar and
//! reduction log are persisted and then reloaded from disk via
//! `SessionStore` (the exact primitive `cli/main.rs`'s
//! `show-reductions`/`convert`/`inspect` use) — never the live
//! `Agent`/in-memory `Session`. `verify_log` and `invert` both run OFFLINE
//! against that reloaded state and must pass clean, with `invert` restoring
//! the exact original `write_file` arguments byte-exact — never the elided
//! stub.

use std::path::PathBuf;
use std::sync::atomic::{AtomicUsize, Ordering};

use async_trait::async_trait;
use supercode_harness::reduce::{
    invert, project_messages, verify_log, ReductionKind, ReductionPolicy,
};
use supercode_harness::session::Session;
use supercode_harness::sidecar::SidecarWriter;
use supercode_harness::store::SessionStore;
use supercode_harness::tools::WriteFileTool;
use supercode_harness::{
    Agent, ChatMessage, ChatRequest, Config, FunctionCall, Provider, Role, ToolCall, Usage,
};

fn temp_dir(tag: &str) -> PathBuf {
    static N: AtomicUsize = AtomicUsize::new(0);
    let dir = std::env::temp_dir().join(format!(
        "supercode-tr10-guarantor-{tag}-{}-{}",
        std::process::id(),
        N.fetch_add(1, Ordering::SeqCst)
    ));
    std::fs::create_dir_all(&dir).unwrap();
    dir
}

/// A deterministic all-ASCII filler string of exactly `len` bytes.
fn filler(len: usize) -> String {
    (0..len).map(|i| (b'a' + (i % 26) as u8) as char).collect()
}

fn write_call_msg(id: &str, path: &str, content: &str) -> ChatMessage {
    ChatMessage {
        role: Role::Assistant,
        content: None,
        content_parts: None,
        tool_calls: Some(vec![ToolCall {
            id: id.to_string(),
            kind: "function".to_string(),
            function: FunctionCall {
                name: "write_file".to_string(),
                arguments: serde_json::json!({ "path": path, "content": content }).to_string(),
            },
        }]),
        tool_call_id: None,
        name: None,
        metadata: Default::default(),
    }
}

/// One `write_file` tool call, then a plain-text reply ends the turn (mirrors
/// `tr4_normalize_guarantor.rs`'s `BashThenPlain` shape).
struct WriteThenPlain {
    calls: AtomicUsize,
    path: String,
    content: String,
}
#[async_trait]
impl Provider for WriteThenPlain {
    async fn complete(
        &self,
        _req: &ChatRequest,
        _on_delta: &(dyn for<'a> Fn(&'a str) + Send + Sync),
    ) -> supercode_harness::Result<(ChatMessage, Usage)> {
        let n = self.calls.fetch_add(1, Ordering::SeqCst);
        match n {
            0 => Ok((
                write_call_msg("w0", &self.path, &self.content),
                Usage::default(),
            )),
            _ => Ok((
                ChatMessage::assistant(format!("done {n}")),
                Usage::default(),
            )),
        }
    }
}

/// dev/01 (guarantor-path regression, TR-10 coverage-gap closure): a live
/// agent run with a recorder + `ReductionPolicy` both active produces a
/// `ToolInputElided` reduction from a REAL, successfully-executed
/// `write_file` call; `verify_log` AND `invert` then run OFFLINE against the
/// sidecar and reduction log reloaded from disk (never the live
/// `Agent`/in-memory `Session`). Both must pass clean, and `invert` must
/// restore the exact original `write_file` arguments byte-exact — the large
/// `content` field included.
#[tokio::test]
async fn tool_input_elided_survives_live_agent_disk_reload_offline_verify_and_invert() {
    let dir = temp_dir("dev01");
    let store_dir = dir.join("store");
    let store = SessionStore::open(&store_dir).unwrap();
    let name = "tr10-dev01";
    let sidecar_path = store.sidecar_path(name);

    // 20,000 bytes: comfortably above the default 8,192-byte
    // `tool_input_trigger_bytes`, so this test's mint is unambiguously
    // TR-10's own pass.
    let big = filler(20_000);
    let rel_path = "reports/out.txt";

    let config = Config::builder().cwd(dir.clone()).build();
    let mut reg = supercode_harness::tools::ToolRegistry::new();
    reg.register(WriteFileTool);
    let mut agent = Agent::with_parts(
        config,
        Box::new(WriteThenPlain {
            calls: AtomicUsize::new(0),
            path: rel_path.to_string(),
            content: big.clone(),
        }),
        reg,
    );

    let empty_session = Session::from_claude_code_str("").unwrap();
    let writer = SidecarWriter::create(&sidecar_path, &empty_session).unwrap();
    agent.set_recorder(writer);
    let policy = ReductionPolicy::default();
    agent.set_reduction_policy(policy.clone());

    let reply = agent.send("please write the report").await.unwrap();
    assert!(reply.starts_with("done"), "unexpected reply: {reply}");

    // The call really executed and really landed on disk — a genuinely
    // successful write, not a hand-built success marker.
    let written_path = dir.join(rel_path);
    assert_eq!(
        std::fs::read_to_string(&written_path).unwrap(),
        big,
        "the real write_file tool must have actually written the file"
    );

    let log_live = agent.reduction_log().clone();
    let elided: Vec<_> = log_live
        .reductions
        .iter()
        .filter(|r| matches!(r.kind, ReductionKind::ToolInputElided { .. }))
        .collect();
    assert_eq!(
        elided.len(),
        1,
        "the large successful write_file call must mint exactly one \
         ToolInputElided reduction: {:?}",
        log_live.reductions
    );
    let elided_idx = elided[0].ptr.addr.index;
    let (original_bytes, call_id, field) = match &elided[0].kind {
        ReductionKind::ToolInputElided {
            original_bytes,
            call_id,
            field,
            ..
        } => (*original_bytes, call_id.clone(), field.clone()),
        _ => unreachable!(),
    };
    assert_eq!(original_bytes, big.len());
    assert_eq!(call_id, "w0");
    assert_eq!(field, "content");

    // Never claimed by any other pass this run.
    assert_eq!(
        log_live
            .reductions
            .iter()
            .filter(|r| r.ptr.addr.index == elided_idx)
            .count(),
        1,
        "the elided call's message must carry exactly one reduction, not double-claimed"
    );

    // Persist the reduction log the way the CLI does (`sessions
    // show-reductions`/`convert`/`inspect` all call `store.save_reduction_log`
    // via the resume/chat path).
    store.save_reduction_log(name, &log_live).unwrap();

    // --- OFFLINE from here: fresh reads from disk, no live Agent/Session ---
    let sidecar_jsonl = store
        .load_sidecar(name)
        .unwrap()
        .expect("sidecar must exist on disk");
    let sidecar = Session::from_sidecar_str(&sidecar_jsonl).unwrap();
    let log_reloaded = store
        .load_reduction_log(name)
        .unwrap()
        .expect("reduction log must exist on disk");

    // Confirm the reloaded-from-disk log still carries the ToolInputElided
    // reduction before verifying/inverting against it.
    assert!(
        log_reloaded
            .reductions
            .iter()
            .any(|r| matches!(r.kind, ReductionKind::ToolInputElided { .. })),
        "the reloaded-from-disk log must still carry the ToolInputElided reduction: {:?}",
        log_reloaded.reductions
    );

    // The sidecar itself must hold the FULL original tool_call arguments at
    // this index — A3's "sidecar keeps everything" contract, independent of
    // whatever the projected view showed.
    let sidecar_args = sidecar.messages[elided_idx].tool_calls()[0]
        .function
        .arguments
        .clone();
    let sidecar_parsed: serde_json::Value = serde_json::from_str(&sidecar_args).unwrap();
    assert_eq!(
        sidecar_parsed.get("content").unwrap().as_str().unwrap(),
        big,
        "the sidecar must retain the FULL original write_file content, never the elided stub"
    );

    // The exact primitive `cli/main.rs`'s `show-reductions`/`convert`/
    // `inspect` all call before doing anything else with a reduced session.
    verify_log(&log_reloaded, &sidecar)
        .expect("verify_log must pass clean against the reloaded-from-disk sidecar");

    let (final_view, reprojected_log) = project_messages(&sidecar.messages, &policy, &log_reloaded);
    assert_eq!(
        reprojected_log, log_reloaded,
        "re-projecting from the reloaded sidecar with its own log must not invent new reductions"
    );
    // The reduced VIEW must still show the stub in the tool_call arguments,
    // not the full content.
    let view_args = final_view[elided_idx].tool_calls()[0]
        .function
        .arguments
        .clone();
    let view_parsed: serde_json::Value = serde_json::from_str(&view_args).unwrap();
    assert!(
        view_parsed
            .get("content")
            .unwrap()
            .as_str()
            .unwrap()
            .contains(supercode_harness::reduce::REDUCTION_SENTINEL),
        "the reprojected view must still show the elided stub, not the full content"
    );

    let inverted = invert(&final_view, &log_reloaded, &sidecar)
        .expect("invert must pass clean against the reloaded-from-disk sidecar");
    assert_eq!(inverted.len(), sidecar.messages.len());

    // Byte-exact restore of the original write_file arguments — content
    // included — from the disk-reloaded sidecar.
    let inverted_args = inverted[elided_idx].tool_calls()[0]
        .function
        .arguments
        .clone();
    assert_eq!(
        inverted_args, sidecar_args,
        "invert must restore the exact original write_file arguments byte-exact from disk"
    );
    let inverted_parsed: serde_json::Value = serde_json::from_str(&inverted_args).unwrap();
    assert_eq!(
        inverted_parsed.get("content").unwrap().as_str().unwrap(),
        big,
        "invert must restore the full original content, not the elided stub"
    );

    std::fs::remove_dir_all(&dir).ok();
}