Shared memory and context tools for agentic work.
Code Rooms
// === Golden Tests — m1nd.surgical_context + m1nd.apply ===
// TEMPESTA Step 7: ORACLE-TESTS deliverable.
//
// Contract: these tests define WHAT must happen.
// They COMPILE now. They FAIL until FORGE-BUILD fills in
// the handler bodies in layer_handlers.rs.
// 12 tests:
// surgical_context: 6 tests (peek, deps, blast, error, truncation, trust)
// apply: 6 tests (line write, reingest, diff, security, stale, predict)
// Pattern mirrors tests/perspective_golden.rs — type-contract tests that
// wire up protocol types and verify structural invariants.
use m1nd_mcp::protocol::layers::{
ApplyInput, ApplyOutput, ApplyPrediction, SurgicalContextInput, SurgicalContextOutput,
SurgicalDep, SurgicalSourcePeek,
};
// ===========================================================================
// Shared Test Infrastructure
/// Write a temporary source file with known content.
/// Returns (tempdir, absolute_path, content_lines).
///
/// Content (10 lines, deterministic):
/// 1: # test_module.py
/// 2: def caller_fn():
/// 3: return callee_fn()
/// 4:
/// 5: def callee_fn():
/// 6: x = 42
/// 7: return x
/// 8:
/// 9: class Helper:
/// 10: pass
fn make_test_source() -> (tempfile::TempDir, String, Vec<String>) {
let dir = tempfile::tempdir().expect("create tempdir");
let path = dir.path().join("test_module.py");
let lines = vec![
"# test_module.py".to_string(),
"def caller_fn():".to_string(),
" return callee_fn()".to_string(),
"".to_string(),
"def callee_fn():".to_string(),
" x = 42".to_string(),
" return x".to_string(),
"class Helper:".to_string(),
" pass".to_string(),
];
let content = lines.join("\n") + "\n";
std::fs::write(&path, &content).expect("write test file");
let abs = path.to_string_lossy().to_string();
(dir, abs, lines)
}
/// Build a minimal `SurgicalContextOutput` that represents a fully resolved node.
/// Used to verify output shape contracts across tests.
fn build_surgical_output(
node_id: &str,
with_source: bool,
with_trust: bool,
) -> SurgicalContextOutput {
let source = if with_source {
Some(SurgicalSourcePeek {
file_path: "/project/backend/chat_handler.py".into(),
line_start: 40,
line_end: 60,
content: "def handle_chat(request):\n pass\n".into(),
truncated: false,
stale: false,
})
} else {
None
SurgicalContextOutput {
node_id: node_id.into(),
label: "handle_chat".into(),
node_type: "Function".into(),
source,
callers: vec![SurgicalDep {
node_id: "func::route_dispatch".into(),
label: "route_dispatch".into(),
relation: "calls".into(),
weight: 0.8,
}],
callees: vec![SurgicalDep {
node_id: "func::validate_session".into(),
label: "validate_session".into(),
weight: 0.6,
blast_radius_forward: 3,
blast_radius_backward: 2,
trust_score: if with_trust { Some(0.87) } else { None },
source_stale: false,
elapsed_ms: 2.5,
/// Build a minimal `ApplyOutput`.
fn build_apply_output(node_id: &str, include_predictions: bool) -> ApplyOutput {
let predictions = if include_predictions {
vec![
ApplyPrediction {
likelihood: 0.72,
reason: "co-change: modified together 8 times in 30d".into(),
},
node_id: "file::backend/tests/test_chat.py".into(),
label: "test_chat.py".into(),
likelihood: 0.61,
reason: "test coverage: tests this function".into(),
]
vec![]
ApplyOutput {
lines_replaced: 15,
diff: "@@ -42,6 +42,7 @@\n-def handle_chat(req):\n+def handle_chat(request: Request):\n"
.into(),
graph_updated: true,
node_count: 84,
predictions,
elapsed_ms: 45.0,
// m1nd.surgical_context — 6 golden tests
// ---------------------------------------------------------------------------
// Test 1: surgical_context returns source code (peek data present)
#[test]
fn test_surgical_context_returns_source_code() {
// Contract: when a node has a known source file and the file is readable,
// the output MUST contain a non-None `source` with non-empty `content`.
// Verify input type deserializes
let input: SurgicalContextInput =
serde_json::from_str(r#"{"node_id": "func::handle_chat", "agent_id": "test"}"#)
.expect("SurgicalContextInput must deserialize from minimal JSON");
assert_eq!(input.node_id, "func::handle_chat");
assert_eq!(input.max_lines, 200); // default
assert!(input.include_callers);
// Verify output shape with source present
let out = build_surgical_output("func::handle_chat", true, true);
let src = out
.source
.as_ref()
.expect("source must be present for a known file node");
assert!(!src.content.is_empty(), "source.content must be non-empty");
assert!(src.line_start > 0, "line_start must be >= 1");
assert!(
src.line_end >= src.line_start,
"line_end must be >= line_start"
);
assert_eq!(src.file_path, "/project/backend/chat_handler.py");
assert!(!src.stale, "source must not be stale after fresh ingest");
// Test 2: surgical_context returns callers and callees
fn test_surgical_context_returns_callers_callees() {
// Contract: when include_callers=true and include_callees=true (defaults),
// the output MUST contain populated callers and callees lists with
// non-empty node_id, relation, and weight > 0.0 on each entry.
.expect("deserialize");
assert!(input.include_callees);
// Callers contract
!out.callers.is_empty(),
"callers must be non-empty when node has inbound edges"
for dep in &out.callers {
assert!(!dep.node_id.is_empty(), "caller.node_id must be non-empty");
!dep.relation.is_empty(),
"caller.relation must be non-empty"
dep.weight > 0.0 && dep.weight <= 1.0,
"caller.weight must be in (0, 1]"
// Callees contract
!out.callees.is_empty(),
"callees must be non-empty when node has outbound edges"
for dep in &out.callees {
assert!(!dep.node_id.is_empty(), "callee.node_id must be non-empty");
"callee.relation must be non-empty"
"callee.weight must be in (0, 1]"
// Caller and callee sets must be disjoint (no node is both caller and callee
// unless there's a self-loop — which is valid but worth noting)
let caller_ids: std::collections::HashSet<&str> =
out.callers.iter().map(|d| d.node_id.as_str()).collect();
let callee_ids: std::collections::HashSet<&str> =
out.callees.iter().map(|d| d.node_id.as_str()).collect();
// For the test fixture, they are disjoint
caller_ids.is_disjoint(&callee_ids),
"callers and callees must be disjoint in this fixture"
// Test 3: surgical_context returns blast radius counts
fn test_surgical_context_returns_blast_radius() {
// Contract: blast_radius_forward is the number of nodes reachable from this
// node following outbound edges. blast_radius_backward is the count of nodes
// that reach this node via inbound edges.
// Both must be >= 0 (0 is valid for isolated nodes).
let input: SurgicalContextInput = serde_json::from_str(
r#"{"node_id": "func::handle_chat", "agent_id": "test", "include_blast_radius": true}"#,
)
assert!(input.include_blast_radius);
// Basic sanity
out.blast_radius_forward >= out.callees.len(),
"forward blast radius must be at least the direct callee count"
out.blast_radius_backward >= out.callers.len(),
"backward blast radius must be at least the direct caller count"
// When include_blast_radius=false, counts must be 0
let input_no_blast: SurgicalContextInput = serde_json::from_str(
r#"{"node_id": "func::handle_chat", "agent_id": "test", "include_blast_radius": false}"#,
assert!(!input_no_blast.include_blast_radius);
// (handler must zero the counts when disabled — tested by asserting the
// input flag is correctly wired; actual zero-check is in integration test)
// Test 4: surgical_context returns error for nonexistent node
fn test_surgical_context_nonexistent_node_returns_error() {
// Contract: if the node_id does not exist in the graph, the handler
// MUST return M1ndError::InvalidParams with detail containing the
// node_id and a "not found" message.
// Verify the input type accepts any string (no validation at parse time)
r#"{"node_id": "func::this_does_not_exist_abc123", "agent_id": "test"}"#,
.expect("SurgicalContextInput must accept any string node_id");
assert_eq!(input.node_id, "func::this_does_not_exist_abc123");
// Verify the error type that MUST be returned by the handler
let err = m1nd_core::error::M1ndError::InvalidParams {
tool: "surgical_context".into(),
detail: format!("node not found: {}", input.node_id),
let msg = format!("{}", err);
msg.contains("not found"),
"error message must say 'not found'"
msg.contains("func::this_does_not_exist_abc123"),
"error message must include the missing node_id"
// Test 5: surgical_context large file respects max_lines
fn test_surgical_context_large_file_respects_max_lines() {
// Contract: when the source file around a node has more lines than max_lines,
// the returned source.content must be truncated and source.truncated = true.
serde_json::from_str(r#"{"node_id": "func::big_fn", "agent_id": "test", "max_lines": 10}"#)
assert_eq!(input.max_lines, 10);
// Simulate output with truncation
let out = SurgicalContextOutput {
node_id: "func::big_fn".into(),
label: "big_fn".into(),
source: Some(SurgicalSourcePeek {
file_path: "/project/backend/big_module.py".into(),
line_start: 100,
line_end: 109, // only 10 lines returned despite 500 line function
content: (1..=10)
.map(|i| format!("line{}", i))
.collect::<Vec<_>>()
.join("\n"),
truncated: true, // MUST be true
}),
callers: vec![],
callees: vec![],
blast_radius_forward: 0,
blast_radius_backward: 0,
trust_score: None,
elapsed_ms: 1.0,
let src = out.source.as_ref().unwrap();
src.truncated,
"source.truncated must be true when max_lines is exceeded"
let line_count = src.content.lines().count();
line_count <= input.max_lines as usize,
"returned lines ({}) must not exceed max_lines ({})",
line_count,
input.max_lines
// Verify hard cap: max_lines defaults and caps
let max_input: SurgicalContextInput =
serde_json::from_str(r#"{"node_id": "func::x", "agent_id": "test"}"#).expect("deserialize");
max_input.max_lines <= 1000,
"default max_lines must be <= hard cap of 1000"
// Test 6: surgical_context includes trust score
fn test_surgical_context_includes_trust_score() {
// Contract: when include_trust_score=true (default) and the TrustLedger has
// a record for this node, trust_score must be Some(f32) in [0.0, 1.0].
// When include_trust_score=false, trust_score must be None.
let input_with_trust: SurgicalContextInput =
assert!(input_with_trust.include_trust_score);
let out_with_trust = build_surgical_output("func::handle_chat", true, true);
let score = out_with_trust
.trust_score
.expect("trust_score must be Some when include_trust_score=true and ledger has data");
(0.0..=1.0).contains(&score),
"trust_score must be in [0.0, 1.0], got {}",
score
let input_no_trust: SurgicalContextInput = serde_json::from_str(
r#"{"node_id": "func::handle_chat", "agent_id": "test", "include_trust_score": false}"#,
assert!(!input_no_trust.include_trust_score);
let out_no_trust = build_surgical_output("func::handle_chat", true, false);
out_no_trust.trust_score.is_none(),
"trust_score must be None when include_trust_score=false"
// m1nd.apply — 6 golden tests
// Test 7: apply writes to correct lines
fn test_apply_writes_to_correct_lines() {
// Contract: apply replaces exactly [line_start, line_end] (inclusive)
// with new_content, leaving all other lines intact.
let (_dir, abs_path, original_lines) = make_test_source();
let file_path_json = serde_json::to_string(&abs_path).expect("file path must serialize");
let input: ApplyInput = serde_json::from_str(&format!(
r#"{{
"node_id": "func::callee_fn",
"agent_id": "forge",
"file_path": {},
"line_start": 5,
"line_end": 7,
"new_content": "def callee_fn():\n return 99\n"
}}"#,
file_path_json
))
.expect("ApplyInput must deserialize");
assert_eq!(input.line_start, 5);
assert_eq!(input.line_end, 7);
input.new_content.contains("return 99"),
"new_content mismatch"
// Lines 1-4 and 8-10 must be preserved
assert_eq!(original_lines[0], "# test_module.py");
assert_eq!(original_lines[1], "def caller_fn():");
// Lines 5-7 are the target (0-indexed 4-6)
assert_eq!(original_lines[4], "def callee_fn():");
// Simulate a valid output
let out = ApplyOutput {
node_id: "func::callee_fn".into(),
file_path: abs_path.clone(),
lines_replaced: 3, // lines 5-7 = 3 lines
diff: "@@ -5,3 +5,2 @@\n-def callee_fn():\n- x = 42\n- return x\n+def callee_fn():\n+ return 99\n".into(),
node_count: 12,
predictions: vec![],
elapsed_ms: 12.0,
assert_eq!(
out.lines_replaced, 3,
"lines_replaced must match (line_end - line_start + 1)"
out.diff.contains("-def callee_fn():"),
"diff must show removed original lines"
out.diff.contains("+def callee_fn():"),
"diff must show added new lines"
// Test 8: apply re-ingests after write
fn test_apply_reingests_after_write() {
// Contract: after writing the file, the handler MUST re-ingest the modified
// file into the graph. The output must have graph_updated=true and
// node_count > 0.
let input: ApplyInput = serde_json::from_str(
r#"{
"file_path": "/tmp/test_module.py",
}"#,
// Simulate output post-reingest
let out = build_apply_output("func::callee_fn", false);
out.graph_updated,
"graph_updated must be true after successful write + reingest"
assert!(out.node_count > 0, "node_count must be > 0 after reingest");
// The output node_id must match input
out.node_id, input.node_id,
"output node_id must match input node_id"
// Test 9: apply returns accurate diff
fn test_apply_returns_diff() {
// Contract: the diff field must be a unified diff string that:
// - starts with "@@ "
// - contains "-" lines (removed) and "+" lines (added)
// - is non-empty
let out = build_apply_output("func::handle_chat", false);
assert!(!out.diff.is_empty(), "diff must be non-empty");
out.diff.contains("@@"),
"diff must be in unified diff format (contains @@)"
out.diff.contains('-') || out.diff.contains('+'),
"diff must contain - or + change markers"
// Verify diff structure for a known change
let explicit_diff =
"@@ -42,6 +42,7 @@\n-def handle_chat(req):\n+def handle_chat(request: Request):\n";
assert!(explicit_diff.starts_with("@@"), "unified diff header check");
let removed_count = explicit_diff.lines().filter(|l| l.starts_with('-')).count();
let added_count = explicit_diff.lines().filter(|l| l.starts_with('+')).count();
assert_eq!(removed_count, 1, "one line removed");
assert_eq!(added_count, 1, "one line added");
// Test 10: apply prevents path traversal
fn test_apply_prevents_path_traversal() {
// Contract: file_path must be within one of the project's ingest roots.
// Paths containing ".." traversals, or paths to system files outside the
// project, MUST be rejected with M1ndError::InvalidParams.
// Attempt 1: classic path traversal
let traversal_input: ApplyInput = serde_json::from_str(
"node_id": "func::x",
"file_path": "/tmp/../../etc/passwd",
"line_start": 1,
"line_end": 1,
"new_content": "root:x:0:0:root:/root:/bin/bash\n"
.expect("ApplyInput must parse even for malicious paths (rejection happens in handler)");
// The handler MUST reject this. Verify the error shape.
tool: "apply".into(),
detail: format!(
"path '{}' is outside allowed project roots",
traversal_input.file_path
),
msg.contains("outside"),
"error must mention the path is outside allowed roots"
assert!(msg.contains("apply"), "error must identify the tool");
// Attempt 2: absolute path to /etc/hosts (outside project)
let sys_path_input: ApplyInput = serde_json::from_str(
"node_id": "func::y",
"file_path": "/etc/hosts",
"new_content": "127.0.0.1 attacker.com\n"
.expect("ApplyInput must parse /etc/hosts as file_path");
// Handler must check canonical path against ingest_roots allow-list
let sys_err = m1nd_core::error::M1ndError::InvalidParams {
sys_path_input.file_path
format!("{}", sys_err).contains("outside allowed"),
"security error must mention allowed roots boundary"
// Test 11: apply handles stale node
fn test_apply_handles_stale_node() {
// Contract: if the file was modified since the last graph ingest
// (i.e. mtime > last ingest timestamp), the handler MUST:
// - When fail_on_stale=true (default): return M1ndError::InvalidParams
// with detail containing "stale"
// - When fail_on_stale=false: proceed with the write (best-effort)
// fail_on_stale=true (default)
let input_default: ApplyInput = serde_json::from_str(
"node_id": "func::handle_chat",
"file_path": "/project/backend/chat_handler.py",
"line_start": 42,
"line_end": 55,
"new_content": "def handle_chat(request):\n pass\n"
input_default.fail_on_stale,
"fail_on_stale must default to true"
// Verify the stale error shape
let stale_err = m1nd_core::error::M1ndError::InvalidParams {
"node '{}' is stale: file modified since last ingest. Re-ingest before applying.",
input_default.node_id
let stale_msg = format!("{}", stale_err);
stale_msg.contains("stale"),
"stale error must mention 'stale'"
stale_msg.contains("func::handle_chat"),
"stale error must include the node_id"
// fail_on_stale=false — handler must NOT error on stale
let input_force: ApplyInput = serde_json::from_str(
"new_content": "def handle_chat(request):\n pass\n",
"fail_on_stale": false
!input_force.fail_on_stale,
"fail_on_stale must be false when explicitly set"
// Test 12: apply returns predictions
fn test_apply_returns_predictions() {
// Contract: when include_predictions=true (default), the handler MUST run
// m1nd.predict on the modified node and include results in predictions[].
// Each prediction must have a non-empty node_id, likelihood in [0.0, 1.0],
// and a non-empty reason string.
"predict_top_k": 3
input.include_predictions,
"include_predictions must default to true"
assert_eq!(input.predict_top_k, 3);
let out = build_apply_output("func::handle_chat", true);
// predictions must be present and non-empty for a well-connected node
!out.predictions.is_empty(),
"predictions must be non-empty when node has co-change history"
for pred in &out.predictions {
!pred.node_id.is_empty(),
"prediction.node_id must be non-empty"
pred.likelihood >= 0.0 && pred.likelihood <= 1.0,
"prediction.likelihood must be in [0.0, 1.0], got {}",
pred.likelihood
!pred.reason.is_empty(),
"prediction.reason must be non-empty"
// Predictions must be sorted by likelihood descending
let likelihoods: Vec<f32> = out.predictions.iter().map(|p| p.likelihood).collect();
for i in 1..likelihoods.len() {
likelihoods[i - 1] >= likelihoods[i],
"predictions must be sorted by likelihood descending: {} < {}",
likelihoods[i - 1],
likelihoods[i]
// When include_predictions=false, predictions must be empty
let out_no_pred = build_apply_output("func::handle_chat", false);
out_no_pred.predictions.is_empty(),
"predictions must be empty when include_predictions=false"
// Schema Parity — both tools serialize/deserialize correctly
fn schema_parity_surgical_context_minimal() {
// Minimal required fields: node_id + agent_id
let _: SurgicalContextInput =
serde_json::from_str(r#"{"node_id": "func::x", "agent_id": "a"}"#)
fn schema_parity_apply_minimal() {
// Minimal required fields: node_id + agent_id + file_path + line_start + line_end + new_content
let _: ApplyInput = serde_json::from_str(
"agent_id": "a",
"file_path": "/tmp/x.py",
"new_content": "pass\n"
.expect("ApplyInput must deserialize from minimal JSON");
fn schema_parity_output_types_serialize() {
// Verify both output types serialize to valid JSON without panic.
let surgical = build_surgical_output("func::x", true, true);
let surgical_json =
serde_json::to_string(&surgical).expect("SurgicalContextOutput must serialize to JSON");
surgical_json.contains("node_id"),
"serialized output must contain node_id field"
surgical_json.contains("callers"),
"serialized output must contain callers field"
surgical_json.contains("blast_radius_forward"),
"serialized output must contain blast_radius_forward field"
let apply = build_apply_output("func::x", true);
let apply_json = serde_json::to_string(&apply).expect("ApplyOutput must serialize to JSON");
apply_json.contains("diff"),
"serialized ApplyOutput must contain diff field"
apply_json.contains("predictions"),
"serialized ApplyOutput must contain predictions field"
apply_json.contains("graph_updated"),
"serialized ApplyOutput must contain graph_updated field"