supercode-harness 0.4.13

The optional native Supercode agent and tool harness
Documentation
//! `reduce::verify_log` (SPEC.md C4/C7 shared primitive): every reduction in
//! a `ReductionLog` must hash-verify against its sidecar, and a tampered
//! record must be caught and named — the core-level counterpart to the CLI
//! `sessions show-reductions`/`convert` acceptance criteria
//! (`crates/cli/tests/reductions_cli.rs`), isolating the primitive itself
//! from the CLI wiring around it.

use std::path::{Path, PathBuf};

use supercode_harness::reduce::{project, verify_log, ReductionLog, ReductionPolicy};
use supercode_harness::session::Session;
use supercode_harness::ChatMessage;

fn fixture(name: &str) -> PathBuf {
    Path::new(env!("CARGO_MANIFEST_DIR"))
        .join("tests/fixtures")
        .join(name)
}

fn oversized_tool_result(target_len: usize) -> ChatMessage {
    let filler: String = (0..target_len)
        .map(|i| (b'a' + (i % 26) as u8) as char)
        .collect();
    ChatMessage::tool_result("call_oversized_1", "big_tool", filler)
}

#[test]
fn verify_log_passes_on_an_intact_sidecar() {
    let imported = Session::from_codex(fixture("codex_session.jsonl")).unwrap();
    let appended = oversized_tool_result(20_000);
    let mut full = imported.clone();
    full.messages.push(appended);

    let policy = ReductionPolicy {
        tool_output_keep_bytes: 100,
        tool_output_trigger_bytes: 200,
        protect_last_n_tool_results: 0,
        ..Default::default()
    };
    let (_view, log) = project(&full, &policy, &ReductionLog::default());
    assert!(!log.reductions.is_empty());

    verify_log(&log, &full).expect("an untouched sidecar must verify cleanly");
}

#[test]
fn verify_log_names_the_offending_id_when_sidecar_content_is_tampered() {
    let imported = Session::from_codex(fixture("codex_session.jsonl")).unwrap();
    let appended = oversized_tool_result(20_000);
    let mut full = imported.clone();
    full.messages.push(appended);

    let policy = ReductionPolicy {
        tool_output_keep_bytes: 100,
        tool_output_trigger_bytes: 200,
        protect_last_n_tool_results: 0,
        ..Default::default()
    };
    let (_view, log) = project(&full, &policy, &ReductionLog::default());
    assert!(!log.reductions.is_empty());
    let target = &log.reductions[0];
    let idx = target.ptr.addr.index;

    // Tamper the sidecar's recorded content for the reduced message (bytes
    // change, so its content hash no longer matches `SidecarPtr::content_hash`)
    // — a stand-in for on-disk sidecar corruption.
    let mut tampered = full.clone();
    tampered.messages[idx].content = Some("TAMPERED".to_string());

    let err = verify_log(&log, &tampered).expect_err("a tampered record must fail verification");
    assert!(
        err.to_string().contains(&target.id),
        "the error must name the offending record id {}: {err}",
        target.id
    );
}

#[test]
fn verify_log_is_vacuously_ok_on_an_empty_log() {
    let imported = Session::from_codex(fixture("codex_session.jsonl")).unwrap();
    verify_log(&ReductionLog::default(), &imported).expect("no reductions to verify");
}

/// TR-12 dev/05 (legacy fail-safe): a synthetic pre-`cap_tool_output`-gate
/// sidecar + log, standing in for a session recorded BEFORE TR-12's D6/A7
/// supersession gate landed (`Agent::run_loop`'s tool-result push site) —
/// back then `cap_tool_output` ran unconditionally, so a reduction over a
/// tool output over 100KB was minted from an already-capped `history` copy
/// while the recorder durably held the full bytes all along (A3). A live agent run
/// today can no longer produce this divergence (the gate keeps `history`
/// full whenever a reduction can be minted over it), so this test builds the
/// legacy shape by hand: `verify_log` must fail — naming the offending
/// reduction id — rather than ever resolving to silently wrong bytes.
#[test]
fn verify_log_fails_safe_on_a_legacy_sidecar_whose_hash_was_minted_from_a_capped_copy() {
    let imported = Session::from_codex(fixture("codex_session.jsonl")).unwrap();

    let mut full_original = "F".repeat(120_000);
    full_original.push_str("LEGACY-NEEDLE-DEV05");
    full_original.push_str(&"y".repeat(150_000 - full_original.len()));
    assert_eq!(full_original.len(), 150_000);

    // What a pre-gate `history` would have held: `cap_tool_output`'s capped
    // prefix + honest notice, cut at the default 100 KB boundary — mirroring
    // its real construction, without depending on the (crate-private)
    // `Agent::cap_tool_output` itself.
    let capped_like_pre_fix_history = format!(
        "{}\n\n[supercode: tool output truncated — {} bytes total, showing first 100000; \
         full output in session sidecar]",
        &full_original[..100_000],
        full_original.len(),
    );

    // Mint AS IF this capped copy were `history` — exactly the pre-fix bug's
    // mint source (`project`/`project_messages` never distinguishes; it just
    // hashes whatever slice it's given).
    let mut capped_history_session = imported.clone();
    capped_history_session
        .messages
        .push(ChatMessage::tool_result(
            "call_legacy_1",
            "big_tool",
            capped_like_pre_fix_history,
        ));
    let policy = ReductionPolicy {
        tool_output_keep_bytes: 100,
        tool_output_trigger_bytes: 200,
        protect_last_n_tool_results: 0,
        ..Default::default()
    };
    let (_view, log) = project(&capped_history_session, &policy, &ReductionLog::default());
    assert!(!log.reductions.is_empty());
    let target = &log.reductions[0];

    // The TRUE sidecar (what the recorder actually durably held all along,
    // per A3 — full bytes, never capped): a reloaded session whose message at
    // the same address is the real 150,000-byte original.
    let mut true_sidecar = imported.clone();
    true_sidecar.messages.push(ChatMessage::tool_result(
        "call_legacy_1",
        "big_tool",
        full_original,
    ));

    let err = verify_log(&log, &true_sidecar)
        .expect_err("a hash minted from a capped copy must fail against the true full sidecar");
    assert!(
        err.to_string().contains(&target.id),
        "the error must name the offending legacy record id {}: {err}",
        target.id
    );
}