use crate::core::agent::harness_kernel::SessionToolCatalogSnapshot;
use crate::prompts::system::{
PLAN_MODE_INTERVIEW_POLICY_LINE, PLAN_MODE_NO_REQUEST_USER_INPUT_POLICY_LINE,
};
#[derive(Debug, PartialEq, Eq)]
pub enum AlignmentError {
PlanModeMismatch {
snapshot_plan_mode: bool,
registry_plan_mode: bool,
},
RequestUserInputMismatch {
snapshot_request_user_input_enabled: bool,
runtime_request_user_input_enabled: bool,
},
PlanModePromptPolicyMismatch { expected_line: &'static str },
MutatingToolInPlanModePrompt { tool_name: &'static str },
}
pub fn validate_prompt_catalog_alignment(
system_instruction: &str,
tool_snapshot: &SessionToolCatalogSnapshot,
plan_mode: bool,
request_user_input_enabled: bool,
) -> Result<(), AlignmentError> {
if tool_snapshot.plan_mode != plan_mode {
return Err(AlignmentError::PlanModeMismatch {
snapshot_plan_mode: tool_snapshot.plan_mode,
registry_plan_mode: plan_mode,
});
}
if tool_snapshot.request_user_input_enabled != request_user_input_enabled {
return Err(AlignmentError::RequestUserInputMismatch {
snapshot_request_user_input_enabled: tool_snapshot.request_user_input_enabled,
runtime_request_user_input_enabled: request_user_input_enabled,
});
}
if plan_mode {
let expected_line = if request_user_input_enabled {
PLAN_MODE_INTERVIEW_POLICY_LINE
} else {
PLAN_MODE_NO_REQUEST_USER_INPUT_POLICY_LINE
};
let unexpected_line = if request_user_input_enabled {
PLAN_MODE_NO_REQUEST_USER_INPUT_POLICY_LINE
} else {
PLAN_MODE_INTERVIEW_POLICY_LINE
};
if !system_instruction.contains(expected_line)
|| system_instruction.contains(unexpected_line)
{
return Err(AlignmentError::PlanModePromptPolicyMismatch { expected_line });
}
const MUTATING_HINTS: &[&str] = &["apply_patch", "unified_file write", "unified_file edit"];
for &hint in MUTATING_HINTS {
if system_instruction.contains(hint) {
return Err(AlignmentError::MutatingToolInPlanModePrompt { tool_name: hint });
}
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Arc;
fn snapshot(plan_mode: bool) -> SessionToolCatalogSnapshot {
SessionToolCatalogSnapshot::new(1, 1, plan_mode, false, Some(Arc::new(Vec::new())), false)
}
#[test]
fn alignment_ok_when_plan_modes_match() {
assert!(
validate_prompt_catalog_alignment("normal prompt", &snapshot(false), false, false)
.is_ok()
);
assert!(
validate_prompt_catalog_alignment(
PLAN_MODE_NO_REQUEST_USER_INPUT_POLICY_LINE,
&snapshot(true),
true,
false,
)
.is_ok()
);
}
#[test]
fn plan_mode_mismatch_detected() {
let err = validate_prompt_catalog_alignment("any prompt", &snapshot(false), true, false)
.expect_err("mismatch should be detected");
assert_eq!(
err,
AlignmentError::PlanModeMismatch {
snapshot_plan_mode: false,
registry_plan_mode: true,
}
);
}
#[test]
fn mutating_tool_in_plan_mode_prompt_detected() {
let err = validate_prompt_catalog_alignment(
&format!(
"{}\nyou may call apply_patch to write files",
PLAN_MODE_NO_REQUEST_USER_INPUT_POLICY_LINE
),
&snapshot(true),
true,
false,
)
.expect_err("canary should fire");
assert_eq!(
err,
AlignmentError::MutatingToolInPlanModePrompt {
tool_name: "apply_patch"
}
);
}
#[test]
fn no_false_positive_in_normal_mode_with_apply_patch() {
assert!(
validate_prompt_catalog_alignment(
"you may call apply_patch",
&snapshot(false),
false,
false
)
.is_ok()
);
}
#[test]
fn request_user_input_mismatch_detected() {
let err = validate_prompt_catalog_alignment(
PLAN_MODE_NO_REQUEST_USER_INPUT_POLICY_LINE,
&SessionToolCatalogSnapshot::new(1, 1, true, false, Some(Arc::new(Vec::new())), false),
true,
true,
)
.expect_err("request-user-input mismatch should be detected");
assert_eq!(
err,
AlignmentError::RequestUserInputMismatch {
snapshot_request_user_input_enabled: false,
runtime_request_user_input_enabled: true,
}
);
}
#[test]
fn prompt_policy_mismatch_detected() {
let err = validate_prompt_catalog_alignment(
PLAN_MODE_INTERVIEW_POLICY_LINE,
&SessionToolCatalogSnapshot::new(1, 1, true, false, Some(Arc::new(Vec::new())), false),
true,
false,
)
.expect_err("prompt policy mismatch should be detected");
assert_eq!(
err,
AlignmentError::PlanModePromptPolicyMismatch {
expected_line: PLAN_MODE_NO_REQUEST_USER_INPUT_POLICY_LINE,
}
);
}
}