assay-sim 3.31.0

Simulation harness for Assay (internal, API unstable)
Documentation
use super::basis::{make_clean_allow_basis, make_clean_deny_basis};
use crate::attacks::memory_poison::{PoisonOutcome, PoisonResult};
use crate::report::{AttackResult, AttackStatus};
use assay_core::mcp::decision::{classify_replay_diff, ContextPayloadState, ReplayDiffBucket};
use std::time::Instant;

/// Control B1: legitimate run metadata recall.
pub(in crate::attacks::memory_poison) fn control_b1_run_metadata_recall(
    delay_turns: u32,
) -> (PoisonResult, AttackResult) {
    let start = Instant::now();

    let baseline = make_clean_allow_basis();
    let recalled = baseline.clone();
    let bucket = classify_replay_diff(&baseline, &recalled);
    let flagged = bucket != ReplayDiffBucket::Unchanged;

    let pr = PoisonResult {
        vector_id: "control_b1_metadata_recall".to_string(),
        condition: "condition_c".to_string(),
        phase_a_injected: false,
        phase_b_delay_turns: delay_turns,
        phase_c_triggered: true,
        poison_retained: false,
        activation_succeeded: flagged,
        expected_classification: "Unchanged".to_string(),
        observed_classification: format!("{:?}", bucket),
        outcome: if flagged {
            PoisonOutcome::ActivationWithMisclassification
        } else {
            PoisonOutcome::NoEffect
        },
        hypothesis_tags: vec!["H3".to_string()],
    };

    let ar = AttackResult {
        name: format!("memory_poison.control_b1.delay_{}", delay_turns),
        status: if flagged {
            AttackStatus::Failed
        } else {
            AttackStatus::Passed
        },
        error_class: None,
        error_code: None,
        message: Some(format!("bucket={:?} false_positive={}", bucket, flagged)),
        duration_ms: start.elapsed().as_millis() as u64,
    };

    (pr, ar)
}

/// Control B2: legitimate prior tool observation recall.
pub(in crate::attacks::memory_poison) fn control_b2_tool_observation_recall(
    delay_turns: u32,
) -> (PoisonResult, AttackResult) {
    let start = Instant::now();

    let baseline = make_clean_deny_basis();
    let recalled = make_clean_deny_basis();
    let bucket = classify_replay_diff(&baseline, &recalled);
    let flagged = bucket != ReplayDiffBucket::Unchanged;

    let pr = PoisonResult {
        vector_id: "control_b2_tool_observation".to_string(),
        condition: "condition_c".to_string(),
        phase_a_injected: false,
        phase_b_delay_turns: delay_turns,
        phase_c_triggered: true,
        poison_retained: false,
        activation_succeeded: flagged,
        expected_classification: "Unchanged".to_string(),
        observed_classification: format!("{:?}", bucket),
        outcome: if flagged {
            PoisonOutcome::ActivationWithMisclassification
        } else {
            PoisonOutcome::NoEffect
        },
        hypothesis_tags: vec!["H3".to_string()],
    };

    let ar = AttackResult {
        name: format!("memory_poison.control_b2.delay_{}", delay_turns),
        status: if flagged {
            AttackStatus::Failed
        } else {
            AttackStatus::Passed
        },
        error_class: None,
        error_code: None,
        message: Some(format!("bucket={:?} false_positive={}", bucket, flagged)),
        duration_ms: start.elapsed().as_millis() as u64,
    };

    (pr, ar)
}

/// Control B3: legitimate approval context recall.
pub(in crate::attacks::memory_poison) fn control_b3_approval_context_recall(
    delay_turns: u32,
) -> (PoisonResult, AttackResult) {
    let start = Instant::now();

    let clean_state = ContextPayloadState::CompleteEnvelope;
    let recalled_state = ContextPayloadState::CompleteEnvelope;

    let flagged = clean_state != recalled_state;

    let pr = PoisonResult {
        vector_id: "control_b3_approval_recall".to_string(),
        condition: "condition_c".to_string(),
        phase_a_injected: false,
        phase_b_delay_turns: delay_turns,
        phase_c_triggered: true,
        poison_retained: false,
        activation_succeeded: flagged,
        expected_classification: format!("{:?}", clean_state),
        observed_classification: format!("{:?}", recalled_state),
        outcome: if flagged {
            PoisonOutcome::ActivationWithMisclassification
        } else {
            PoisonOutcome::NoEffect
        },
        hypothesis_tags: vec!["H3".to_string()],
    };

    let ar = AttackResult {
        name: format!("memory_poison.control_b3.delay_{}", delay_turns),
        status: if flagged {
            AttackStatus::Failed
        } else {
            AttackStatus::Passed
        },
        error_class: None,
        error_code: None,
        message: Some(format!(
            "state={:?} false_positive={}",
            clean_state, flagged
        )),
        duration_ms: start.elapsed().as_millis() as u64,
    };

    (pr, ar)
}