Shared memory and context tools for agentic work.
Code Rooms
// === Golden Test Sequences for Perspective MCP ===
// Theme 17 from 12-PERSPECTIVE-SYNTHESIS.
// 6 deterministic test sequences defining the contract for Perspective MCP behavior.
//
// These tests define WHAT should happen, not HOW.
// Each test is a sequence of tool calls with expected outcomes.
// They compile NOW (against CONTRACTS types) but call todo!() functions.
// The BUILD phase fills in implementations until these pass.
use m1nd_mcp::perspective::confidence::*;
use m1nd_mcp::perspective::keys::*;
use m1nd_mcp::perspective::state::*;
use m1nd_mcp::perspective::validation::*;
use m1nd_mcp::protocol::lock::*;
use m1nd_mcp::protocol::perspective::*;
// ===========================================================================
// Test Harness (deterministic test graph)
/// Build a deterministic test graph: 20 nodes, ~50 edges, fixed weights.
/// Used by all golden tests for reproducibility.
///
/// Graph topology (code-like):
/// main.rs → lib.rs → session.rs → state.rs → error.rs
/// → protocol.rs → types.rs
/// → server.rs → dispatch.rs → handlers.rs
/// config.rs → settings.rs
/// utils.rs → helpers.rs → format.rs
/// test_main.rs → test_session.rs → test_state.rs
/// README.md (isolated, no code edges)
/// Cargo.toml (imports → main.rs, lib.rs)
#[allow(dead_code)]
fn build_test_graph_nodes() -> Vec<(&'static str, &'static str)> {
vec![
("main.rs", "module"),
("lib.rs", "module"),
("session.rs", "module"),
("state.rs", "module"),
("error.rs", "module"),
("protocol.rs", "module"),
("types.rs", "module"),
("server.rs", "module"),
("dispatch.rs", "module"),
("handlers.rs", "module"),
("config.rs", "module"),
("settings.rs", "module"),
("utils.rs", "module"),
("helpers.rs", "module"),
("format.rs", "module"),
("test_main.rs", "test"),
("test_session.rs", "test"),
("test_state.rs", "test"),
("README.md", "doc"),
("Cargo.toml", "config"),
]
}
fn build_test_graph_edges() -> Vec<(&'static str, &'static str, &'static str, f32)> {
("main.rs", "lib.rs", "imports", 0.9),
("lib.rs", "session.rs", "imports", 0.85),
("lib.rs", "protocol.rs", "imports", 0.8),
("lib.rs", "server.rs", "imports", 0.8),
("session.rs", "state.rs", "imports", 0.9),
("state.rs", "error.rs", "imports", 0.7),
("protocol.rs", "types.rs", "imports", 0.85),
("server.rs", "dispatch.rs", "imports", 0.9),
("dispatch.rs", "handlers.rs", "imports", 0.85),
("config.rs", "settings.rs", "imports", 0.7),
("utils.rs", "helpers.rs", "imports", 0.6),
("helpers.rs", "format.rs", "imports", 0.5),
("test_main.rs", "test_session.rs", "tests", 0.8),
("test_session.rs", "test_state.rs", "tests", 0.75),
("Cargo.toml", "main.rs", "declares", 0.95),
("Cargo.toml", "lib.rs", "declares", 0.95),
// Cross-cutting edges
("session.rs", "error.rs", "uses", 0.6),
("server.rs", "error.rs", "uses", 0.55),
("handlers.rs", "session.rs", "calls", 0.7),
("handlers.rs", "protocol.rs", "calls", 0.65),
("test_session.rs", "session.rs", "tests", 0.8),
("test_state.rs", "state.rs", "tests", 0.8),
// Golden Test 1: Happy Path
// start → routes → inspect → peek → follow → routes → back → close
#[test]
fn golden_01_happy_path_types_compile() {
// This test verifies the type contracts compile and wire together.
// The actual behavior tests will work once engine_ops is implemented.
// 1. start
let start_input = PerspectiveStartInput {
agent_id: "test_agent".into(),
query: "session management".into(),
anchor_node: Some("session.rs".into()),
lens: None,
};
assert_eq!(start_input.agent_id, "test_agent");
// Expected output shape
let _start_output = PerspectiveStartOutput {
perspective_id: "persp_test_001".into(),
mode: PerspectiveMode::Anchored,
focus_node: Some("session.rs".into()),
routes: vec![],
total_routes: 0,
page: 1,
total_pages: 1,
route_set_version: 1710000000000,
cache_generation: 0,
suggested: Some("inspect R01".into()),
proof_state: "triaging".into(),
next_suggested_tool: Some("perspective_inspect".into()),
next_suggested_target: Some("R01".into()),
next_step_hint: Some("Inspect the top route first.".into()),
// 2. routes
let routes_input = PerspectiveRoutesInput {
page_size: 6,
route_set_version: Some(1710000000000),
assert_eq!(routes_input.page, 1);
// 3. inspect (by route_id)
let inspect_input = PerspectiveInspectInput {
route_id: Some("R_abc123".into()),
route_index: None,
assert!(inspect_input.route_id.is_some());
// 4. peek (by route_index)
let peek_input = PerspectivePeekInput {
route_id: None,
route_index: Some(1),
assert!(peek_input.route_index.is_some());
// 5. follow
let follow_input = PerspectiveFollowInput {
assert!(follow_input.route_id.is_some());
// 6. back
let back_input = PerspectiveBackInput {
assert_eq!(back_input.perspective_id, "persp_test_001");
// 7. close
let close_input = PerspectiveCloseInput {
assert_eq!(close_input.perspective_id, "persp_test_001");
// Golden Test 2: Staleness Detection
// start → routes → [ingest happens] → follow (expect ROUTE_SET_STALE)
fn golden_02_staleness_detection() {
// After ingest, graph_generation bumps → cache_generation bumps
// → PerspectiveState.captured_cache_generation < current cache_generation
// → follow should return RouteSetStale error
let state = PerspectiveState {
anchor_query: Some("session".into()),
lens: PerspectiveLens::default(),
entry_path: vec!["session.rs".into()],
navigation_history: vec![NavigationEvent {
action: "start".into(),
target: Some("session.rs".into()),
timestamp_ms: 1710000000000,
route_set_version: 100,
}],
checkpoints: vec![],
visited_nodes: std::collections::HashSet::new(),
route_cache: Some(CachedRouteSet {
total_routes: 3,
version: 100,
synthesis_elapsed_ms: 15.0,
captured_cache_generation: 1, // captured at gen 1
}),
captured_cache_generation: 1,
stale: false,
created_at_ms: 1710000000000,
last_accessed_ms: 1710000000000,
branches: vec![],
// Simulate: graph_generation bumped to 2 after ingest
let current_cache_generation: u64 = 2;
// The follow handler should detect: state.captured_cache_generation (1) < current (2)
// and return M1ndError::RouteSetStale
assert!(state.captured_cache_generation < current_cache_generation);
// Verify the error type exists and has the right fields
let err = m1nd_core::error::M1ndError::RouteSetStale {
current_version: 200,
assert!(format!("{}", err).contains("stale"));
// Golden Test 3: Error Recovery
// routes → follow with wrong version → re-list → follow correct
fn golden_03_error_recovery_types() {
// Verify error types support recovery flow
// Step 1: RouteSetStale error includes version info for recovery
route_set_version: 50,
current_version: 75,
let msg = format!("{}", err);
assert!(msg.contains("50"));
assert!(msg.contains("75"));
// Step 2: RouteNotFound error includes perspective context
let err = m1nd_core::error::M1ndError::RouteNotFound {
route_id: "R_abc123".into(),
assert!(msg.contains("R_abc123"));
// Step 3: NavigationAtRoot error
let err = m1nd_core::error::M1ndError::NavigationAtRoot {
assert!(format!("{}", err).contains("root"));
// Step 4: PerspectiveNotFound
let err = m1nd_core::error::M1ndError::PerspectiveNotFound {
assert!(format!("{}", err).contains("not found"));
// Golden Test 4: Multi-Agent Isolation
// agent A start → agent B start → verify no cross-contamination
fn golden_04_multi_agent_isolation() {
use std::collections::HashMap;
// Simulate perspective storage: HashMap<(agent_id, perspective_id), PerspectiveState>
let mut perspectives: HashMap<(String, String), PerspectiveState> = HashMap::new();
// Agent A creates perspective
let state_a = PerspectiveState {
perspective_id: "persp_agentA_001".into(),
agent_id: "agent_A".into(),
navigation_history: vec![],
route_cache: None,
captured_cache_generation: 0,
perspectives.insert(("agent_A".into(), "persp_agentA_001".into()), state_a);
// Agent B creates perspective
let state_b = PerspectiveState {
perspective_id: "persp_agentB_001".into(),
agent_id: "agent_B".into(),
mode: PerspectiveMode::Local,
anchor_node: None,
anchor_query: None,
focus_node: Some("config.rs".into()),
entry_path: vec!["config.rs".into()],
route_set_version: 200,
created_at_ms: 1710000001000,
last_accessed_ms: 1710000001000,
perspectives.insert(("agent_B".into(), "persp_agentB_001".into()), state_b);
// Verify isolation: A cannot see B's perspective
assert!(!perspectives.contains_key(&("agent_A".into(), "persp_agentB_001".into())));
assert!(!perspectives.contains_key(&("agent_B".into(), "persp_agentA_001".into())));
// Verify each agent sees only their own
let a_persp = perspectives
.get(&("agent_A".into(), "persp_agentA_001".into()))
.unwrap();
assert_eq!(a_persp.agent_id, "agent_A");
assert_eq!(a_persp.focus_node.as_deref(), Some("session.rs"));
let b_persp = perspectives
.get(&("agent_B".into(), "persp_agentB_001".into()))
assert_eq!(b_persp.agent_id, "agent_B");
assert_eq!(b_persp.focus_node.as_deref(), Some("config.rs"));
// Verify limit enforcement (max 5 per agent)
let limits = PerspectiveLimits::default();
let agent_a_count = perspectives.keys().filter(|(a, _)| a == "agent_A").count();
assert!(agent_a_count <= limits.max_perspectives_per_agent);
// Golden Test 5: Lock Lifecycle
// lock.create → ingest → lock.diff (expect changes) → lock.release
fn golden_05_lock_lifecycle() {
use std::collections::{HashMap, HashSet};
// 1. Create lock on session.rs (node scope)
let create_input = LockCreateInput {
scope: LockScope::Node,
root_nodes: vec!["session.rs".into()],
radius: None,
query: None,
path_nodes: None,
assert_eq!(create_input.scope, LockScope::Node);
// Simulate lock state after creation
let mut lock = LockState {
lock_id: "lock_test_001".into(),
scope: LockScopeConfig {
scope_type: LockScope::Node,
radius: Some(0),
},
baseline: LockSnapshot {
nodes: {
let mut s = HashSet::new();
s.insert("session.rs".into());
s
edges: {
let mut m = HashMap::new();
m.insert(
edge_content_key("session.rs", "state.rs", "imports"),
EdgeSnapshotEntry {
source: "session.rs".into(),
target: "state.rs".into(),
relation: "imports".into(),
weight: 0.9,
);
m
graph_generation: 1,
captured_at_ms: 1710000000000,
key_format: "v1_content_addr".into(),
watcher: None,
baseline_stale: false,
last_diff_ms: 1710000000000,
// 2. Simulate ingest → graph_generation bumps
// The rebuild_engines invalidation (Theme 16) marks baselines stale
lock.baseline_stale = true;
// 3. lock.diff should report staleness
let diff_input = LockDiffInput {
assert_eq!(diff_input.lock_id, "lock_test_001");
// Verify diff result shape
let diff = LockDiffResult {
no_changes: false,
new_nodes: vec![],
removed_nodes: vec![],
new_edges: vec!["session.rs->new_module.rs: imports".into()],
removed_edges: vec![],
boundary_edges_added: vec![],
boundary_edges_removed: vec![],
weight_changes: vec![EdgeWeightChange {
edge_key: edge_content_key("session.rs", "state.rs", "imports"),
old_weight: 0.9,
new_weight: 0.85,
baseline_stale: true,
elapsed_ms: 5.0,
assert!(diff.baseline_stale);
assert_eq!(diff.new_edges.len(), 1);
// 4. lock.release
let release_input = LockReleaseInput {
assert_eq!(release_input.lock_id, "lock_test_001");
// Golden Test 6: Schema Parity
// For each tool, verify minimal params deserialize into the Rust struct.
fn golden_06_schema_parity_perspective_tools() {
// perspective.start
let _: PerspectiveStartInput = serde_json::from_str(r#"{"agent_id": "a", "query": "q"}"#)
.expect("perspective.start minimal params");
// perspective.routes
let _: PerspectiveRoutesInput =
serde_json::from_str(r#"{"agent_id": "a", "perspective_id": "p"}"#)
.expect("perspective.routes minimal params");
// perspective.inspect
let _: PerspectiveInspectInput = serde_json::from_str(
r#"{"agent_id": "a", "perspective_id": "p", "route_id": "R_abc", "route_set_version": 1}"#,
)
.expect("perspective.inspect minimal params");
// perspective.peek
let _: PerspectivePeekInput = serde_json::from_str(
r#"{"agent_id": "a", "perspective_id": "p", "route_index": 1, "route_set_version": 1}"#,
.expect("perspective.peek minimal params");
// perspective.follow
let _: PerspectiveFollowInput = serde_json::from_str(
r#"{"agent_id": "a", "perspective_id": "p", "route_id": "R_x", "route_set_version": 1}"#,
.expect("perspective.follow minimal params");
// perspective.suggest
let _: PerspectiveSuggestInput =
serde_json::from_str(r#"{"agent_id": "a", "perspective_id": "p", "route_set_version": 1}"#)
.expect("perspective.suggest minimal params");
// perspective.affinity
let _: PerspectiveAffinityInput = serde_json::from_str(
.expect("perspective.affinity minimal params");
// perspective.branch
let _: PerspectiveBranchInput =
.expect("perspective.branch minimal params");
// perspective.back
let _: PerspectiveBackInput =
.expect("perspective.back minimal params");
// perspective.compare
let _: PerspectiveCompareInput = serde_json::from_str(
r#"{"agent_id": "a", "perspective_id_a": "p1", "perspective_id_b": "p2"}"#,
.expect("perspective.compare minimal params");
// perspective.list
let _: PerspectiveListInput =
serde_json::from_str(r#"{"agent_id": "a"}"#).expect("perspective.list minimal params");
// perspective.close
let _: PerspectiveCloseInput =
.expect("perspective.close minimal params");
fn golden_06_schema_parity_lock_tools() {
// lock.create
let _: LockCreateInput =
serde_json::from_str(r#"{"agent_id": "a", "scope": "node", "root_nodes": ["x"]}"#)
.expect("lock.create minimal params");
// lock.watch
let _: LockWatchInput =
serde_json::from_str(r#"{"agent_id": "a", "lock_id": "l", "strategy": "manual"}"#)
.expect("lock.watch minimal params");
// lock.diff
let _: LockDiffInput = serde_json::from_str(r#"{"agent_id": "a", "lock_id": "l"}"#)
.expect("lock.diff minimal params");
// lock.rebase
let _: LockRebaseInput = serde_json::from_str(r#"{"agent_id": "a", "lock_id": "l"}"#)
.expect("lock.rebase minimal params");
// lock.release
let _: LockReleaseInput = serde_json::from_str(r#"{"agent_id": "a", "lock_id": "l"}"#)
.expect("lock.release minimal params");
// Validation contract tests
fn golden_validation_lens_contract() {
// Valid lens
let lens = PerspectiveLens::default();
let validated = validate_lens(&lens, 100).unwrap();
assert_eq!(validated.dimensions.len(), 4);
// Invalid dimension
let bad = PerspectiveLens {
dimensions: vec!["quantum".into()],
..PerspectiveLens::default()
assert!(validate_lens(&bad, 100).is_err());
fn golden_validation_pagination_contract() {
// Normal case
let p = validate_pagination(2, 6, 30).unwrap();
assert_eq!(p.page, 2);
assert_eq!(p.total_pages, 5);
assert_eq!(p.offset, 6);
assert!(!p.page_size_clamped);
// page=0 rejected
assert!(validate_pagination(0, 6, 30).is_err());
// page_size clamped
let p = validate_pagination(1, 100, 30).unwrap();
assert_eq!(p.page_size, 10);
assert!(p.page_size_clamped);
fn golden_validation_route_ref_contract() {
// Exactly one required
assert!(validate_route_ref(&Some("R_x".into()), &None, "test").is_ok());
assert!(validate_route_ref(&None, &Some(1), "test").is_ok());
assert!(validate_route_ref(&Some("R_x".into()), &Some(1), "test").is_err());
assert!(validate_route_ref(&None, &None, "test").is_err());
// Content-addressable keys contract tests
fn golden_keys_edge_key_deterministic() {
let k1 = edge_content_key("a", "b", "imports");
let k2 = edge_content_key("a", "b", "imports");
assert_eq!(k1, k2);
// Different relation → different key
let k3 = edge_content_key("a", "b", "calls");
assert_ne!(k1, k3);
fn golden_keys_route_id_stable() {
let id1 = route_content_id("session.rs", &RouteFamily::Structural);
let id2 = route_content_id("session.rs", &RouteFamily::Structural);
assert_eq!(id1, id2);
assert!(id1.starts_with("R_"));
assert_eq!(id1.len(), 8);
// Different family → different ID
let id3 = route_content_id("session.rs", &RouteFamily::Ghost);
assert_ne!(id1, id3);
fn golden_keys_bidi_normalization() {
let (lo1, hi1) = normalize_bidi_endpoints("z", "a");
let (lo2, hi2) = normalize_bidi_endpoints("a", "z");
assert_eq!(lo1, lo2);
assert_eq!(hi1, hi2);
// Confidence calibration contract tests
fn golden_confidence_max_cap() {
// Even perfect scores cap at 0.85
let breakdown = ConfidenceBreakdown {
ghost_edge_strength: Some(1.0),
structural_hole_pressure: Some(1.0),
resonant_amplitude: Some(1.0),
semantic_overlap: Some(1.0),
provenance_overlap: Some(1.0),
route_path_neighborhood: Some(1.0),
let c = compute_combined_confidence(&breakdown).unwrap();
assert!(c <= MAX_CONFIDENCE);
fn golden_confidence_min_threshold() {
// Very low scores drop below threshold → None
ghost_edge_strength: Some(0.01),
structural_hole_pressure: Some(0.02),
resonant_amplitude: None,
semantic_overlap: None,
provenance_overlap: None,
route_path_neighborhood: None,
assert!(compute_combined_confidence(&breakdown).is_none());
fn golden_confidence_single_source_gate() {
// Single source cannot exceed 0.40 (needs corroboration)
ghost_edge_strength: Some(0.95),
structural_hole_pressure: None,
assert!(c <= 0.40);