diff --git a/fixtures/mcp_trace_replay_corruptions/mcp_trace_replay_degraded_v1/drop_approval_gate/dependency_graph.json b/fixtures/mcp_trace_replay_corruptions/mcp_trace_replay_degraded_v1/drop_approval_gate/dependency_graph.json new file mode 100644 index 0000000..f2d8fc1 --- /dev/null +++ b/fixtures/mcp_trace_replay_corruptions/mcp_trace_replay_degraded_v1/drop_approval_gate/dependency_graph.json @@ -0,0 +1,132 @@ +{ + "graph_version": "1.0", + "nodes": [ + { + "node_id": "system_start", + "label": "System start", + "metadata": { + "phase": "system" + } + }, + { + "node_id": "user_request_received", + "label": "User request received", + "metadata": { + "phase": "intake" + } + }, + { + "node_id": "capability_scope_checked", + "label": "Capability scope checked", + "metadata": { + "phase": "governance" + } + }, + { + "node_id": "tool_schema_validated", + "label": "Tool schema validated", + "metadata": { + "phase": "validation" + } + }, + { + "node_id": "read_context", + "label": "Read context", + "metadata": { + "phase": "context" + } + }, + { + "node_id": "validate_external_action", + "label": "Validate external action", + "metadata": { + "phase": "validation" + } + }, + { + "node_id": "execute_external_action", + "label": "Execute external action", + "metadata": { + "phase": "execution" + } + }, + { + "node_id": "verify_result", + "label": "Verify result", + "metadata": { + "phase": "verification" + } + }, + { + "node_id": "recovery_path_registered", + "label": "Recovery path registered", + "metadata": { + "phase": "recovery" + } + } + ], + "edges": [ + { + "source": "system_start", + "target": "user_request_received", + "relation": "TEMPORAL", + "metadata": {} + }, + { + "source": "user_request_received", + "target": "capability_scope_checked", + "relation": "PREREQUISITE", + "metadata": {} + }, + { + "source": "capability_scope_checked", + "target": "tool_schema_validated", + "relation": "PREREQUISITE", + "metadata": {} + }, + { + "source": "tool_schema_validated", + "target": "read_context", + "relation": "DATA_FLOW", + "metadata": {} + }, + { + "source": "read_context", + "target": "validate_external_action", + "relation": "PREREQUISITE", + "metadata": {} + }, + { + "source": "validate_external_action", + "target": "execute_external_action", + "relation": "CAUSAL", + "metadata": {} + }, + { + "source": "execute_external_action", + "target": "verify_result", + "relation": "CAUSAL", + "metadata": {} + }, + { + "source": "execute_external_action", + "target": "recovery_path_registered", + "relation": "RECOVERY", + "metadata": {} + }, + { + "source": "capability_scope_checked", + "target": "execute_external_action", + "relation": "BLOCKER", + "metadata": { + "state": "enforced" + } + }, + { + "source": "capability_scope_checked", + "target": "validate_external_action", + "relation": "CAUSAL", + "metadata": {} + } + ] +} diff --git a/fixtures/mcp_trace_replay_corruptions/mcp_trace_replay_degraded_v1/drop_approval_gate/state.json b/fixtures/mcp_trace_replay_corruptions/mcp_trace_replay_degraded_v1/drop_approval_gate/state.json new file mode 100644 index 0000000..4fb9820 --- /dev/null +++ b/fixtures/mcp_trace_replay_corruptions/mcp_trace_replay_degraded_v1/drop_approval_gate/state.json @@ -0,0 +1,40 @@ +{ + "state_version": "1.0", + "entities": {}, + "capability_boundaries": [ + [ + "agent", + "capability_scope_checked" + ], + [ + "capability_scope_checked", + "validate_external_action" + ] + ], + "allowed_tools": [ + "read_context", + "validate_external_action", + "execute_external_action", + "verify_result" + ], + "resource_boundaries": [ + [ + "execute_external_action", + "approved_external_resource" + ] + ], + "permission_scopes": { + "execute_external_action": [ + "requires_human_approval", + "requires_validation_passed" + ] + }, + "capability_scope": { + "agent": [ + "read_context", + "validate_external_action", + "execute_external_action", + "verify_result" + ] + } +} diff --git a/fixtures/mcp_trace_replay_corruptions/mcp_trace_replay_degraded_v1/drop_approval_gate/trace.json b/fixtures/mcp_trace_replay_corruptions/mcp_trace_replay_degraded_v1/drop_approval_gate/trace.json new file mode 100644 index 0000000..0eb1904 --- /dev/null +++ b/fixtures/mcp_trace_replay_corruptions/mcp_trace_replay_degraded_v1/drop_approval_gate/trace.json @@ -0,0 +1,36 @@ +{ + "events": [ + { + "action": "user_request_received", + "step": 1 + }, + { + "action": "capability_scope_checked", + "step": 2 + }, + { + "action": "tool_schema_validated", + "step": 3 + }, + { + "action": "read_context", + "step": 4 + }, + { + "action": "validate_external_action", + "step": 5 + }, + { + "action": "execute_external_action", + "step": 6 + }, + { + "action": "verify_result", + "step": 7 + }, + { + "action": "recovery_path_registered", + "step": 8 + } + ] +} diff --git a/fixtures/mcp_trace_replay_corruptions/mcp_trace_replay_degraded_v1/remove_dependency_edge/dependency_graph.json b/fixtures/mcp_trace_replay_corruptions/mcp_trace_replay_degraded_v1/remove_dependency_edge/dependency_graph.json new file mode 100644 index 0000000..e3149bc --- /dev/null +++ b/fixtures/mcp_trace_replay_corruptions/mcp_trace_replay_degraded_v1/remove_dependency_edge/dependency_graph.json @@ -0,0 +1,126 @@ +{ + "graph_version": "1.0", + "nodes": [ + { + "node_id": "system_start", + "label": "System start", + "metadata": { + "phase": "system" + } + }, + { + "node_id": "user_request_received", + "label": "User request received", + "metadata": { + "phase": "intake" + } + }, + { + "node_id": "capability_scope_checked", + "label": "Capability scope checked", + "metadata": { + "phase": "governance" + } + }, + { + "node_id": "tool_schema_validated", + "label": "Tool schema validated", + "metadata": { + "phase": "validation" + } + }, + { + "node_id": "read_context", + "label": "Read context", + "metadata": { + "phase": "context" + } + }, + { + "node_id": "validate_external_action", + "label": "Validate external action", + "metadata": { + "phase": "validation" + } + }, + { + "node_id": "execute_external_action", + "label": "Execute external action", + "metadata": { + "phase": "execution" + } + }, + { + "node_id": "verify_result", + "label": "Verify result", + "metadata": { + "phase": "verification" + } + }, + { + "node_id": "recovery_path_registered", + "label": "Recovery path registered", + "metadata": { + "phase": "recovery" + } + } + ], + "edges": [ + { + "source": "system_start", + "target": "user_request_received", + "relation": "TEMPORAL", + "metadata": {} + }, + { + "source": "user_request_received", + "target": "capability_scope_checked", + "relation": "PREREQUISITE", + "metadata": {} + }, + { + "source": "capability_scope_checked", + "target": "tool_schema_validated", + "relation": "PREREQUISITE", + "metadata": {} + }, + { + "source": "tool_schema_validated", + "target": "read_context", + "relation": "DATA_FLOW", + "metadata": {} + }, + { + "source": "validate_external_action", + "target": "execute_external_action", + "relation": "CAUSAL", + "metadata": {} + }, + { + "source": "execute_external_action", + "target": "verify_result", + "relation": "CAUSAL", + "metadata": {} + }, + { + "source": "execute_external_action", + "target": "recovery_path_registered", + "relation": "RECOVERY", + "metadata": {} + }, + { + "source": "capability_scope_checked", + "target": "execute_external_action", + "relation": "BLOCKER", + "metadata": { + "state": "enforced" + } + }, + { + "source": "capability_scope_checked", + "target": "validate_external_action", + "relation": "CAUSAL", + "metadata": {} + } + ] +} diff --git a/fixtures/mcp_trace_replay_corruptions/mcp_trace_replay_degraded_v1/remove_dependency_edge/state.json b/fixtures/mcp_trace_replay_corruptions/mcp_trace_replay_degraded_v1/remove_dependency_edge/state.json new file mode 100644 index 0000000..7597431 --- /dev/null +++ b/fixtures/mcp_trace_replay_corruptions/mcp_trace_replay_degraded_v1/remove_dependency_edge/state.json @@ -0,0 +1,44 @@ +{ + "state_version": "1.0", + "entities": {}, + "capability_boundaries": [ + [ + "agent", + "capability_scope_checked" + ], + [ + "capability_scope_checked", + "validate_external_action" + ], + [ + "human_approval", + "execute_external_action" + ] + ], + "allowed_tools": [ + "read_context", + "validate_external_action", + "execute_external_action", + "verify_result" + ], + "resource_boundaries": [ + [ + "execute_external_action", + "approved_external_resource" + ] + ], + "permission_scopes": { + "execute_external_action": [ + "requires_human_approval", + "requires_validation_passed" + ] + }, + "capability_scope": { + "agent": [ + "read_context", + "validate_external_action", + "execute_external_action", + "verify_result" + ] + } +} diff --git a/fixtures/mcp_trace_replay_corruptions/mcp_trace_replay_degraded_v1/remove_dependency_edge/trace.json b/fixtures/mcp_trace_replay_corruptions/mcp_trace_replay_degraded_v1/remove_dependency_edge/trace.json new file mode 100644 index 0000000..0eb1904 --- /dev/null +++ b/fixtures/mcp_trace_replay_corruptions/mcp_trace_replay_degraded_v1/remove_dependency_edge/trace.json @@ -0,0 +1,36 @@ +{ + "events": [ + { + "action": "user_request_received", + "step": 1 + }, + { + "action": "capability_scope_checked", + "step": 2 + }, + { + "action": "tool_schema_validated", + "step": 3 + }, + { + "action": "read_context", + "step": 4 + }, + { + "action": "validate_external_action", + "step": 5 + }, + { + "action": "execute_external_action", + "step": 6 + }, + { + "action": "verify_result", + "step": 7 + }, + { + "action": "recovery_path_registered", + "step": 8 + } + ] +} diff --git a/fixtures/mcp_trace_replay_corruptions/mcp_trace_replay_degraded_v1/truncate_recovery_path/dependency_graph.json b/fixtures/mcp_trace_replay_corruptions/mcp_trace_replay_degraded_v1/truncate_recovery_path/dependency_graph.json new file mode 100644 index 0000000..f2d8fc1 --- /dev/null +++ b/fixtures/mcp_trace_replay_corruptions/mcp_trace_replay_degraded_v1/truncate_recovery_path/dependency_graph.json @@ -0,0 +1,132 @@ +{ + "graph_version": "1.0", + "nodes": [ + { + "node_id": "system_start", + "label": "System start", + "metadata": { + "phase": "system" + } + }, + { + "node_id": "user_request_received", + "label": "User request received", + "metadata": { + "phase": "intake" + } + }, + { + "node_id": "capability_scope_checked", + "label": "Capability scope checked", + "metadata": { + "phase": "governance" + } + }, + { + "node_id": "tool_schema_validated", + "label": "Tool schema validated", + "metadata": { + "phase": "validation" + } + }, + { + "node_id": "read_context", + "label": "Read context", + "metadata": { + "phase": "context" + } + }, + { + "node_id": "validate_external_action", + "label": "Validate external action", + "metadata": { + "phase": "validation" + } + }, + { + "node_id": "execute_external_action", + "label": "Execute external action", + "metadata": { + "phase": "execution" + } + }, + { + "node_id": "verify_result", + "label": "Verify result", + "metadata": { + "phase": "verification" + } + }, + { + "node_id": "recovery_path_registered", + "label": "Recovery path registered", + "metadata": { + "phase": "recovery" + } + } + ], + "edges": [ + { + "source": "system_start", + "target": "user_request_received", + "relation": "TEMPORAL", + "metadata": {} + }, + { + "source": "user_request_received", + "target": "capability_scope_checked", + "relation": "PREREQUISITE", + "metadata": {} + }, + { + "source": "capability_scope_checked", + "target": "tool_schema_validated", + "relation": "PREREQUISITE", + "metadata": {} + }, + { + "source": "tool_schema_validated", + "target": "read_context", + "relation": "DATA_FLOW", + "metadata": {} + }, + { + "source": "read_context", + "target": "validate_external_action", + "relation": "PREREQUISITE", + "metadata": {} + }, + { + "source": "validate_external_action", + "target": "execute_external_action", + "relation": "CAUSAL", + "metadata": {} + }, + { + "source": "execute_external_action", + "target": "verify_result", + "relation": "CAUSAL", + "metadata": {} + }, + { + "source": "execute_external_action", + "target": "recovery_path_registered", + "relation": "RECOVERY", + "metadata": {} + }, + { + "source": "capability_scope_checked", + "target": "execute_external_action", + "relation": "BLOCKER", + "metadata": { + "state": "enforced" + } + }, + { + "source": "capability_scope_checked", + "target": "validate_external_action", + "relation": "CAUSAL", + "metadata": {} + } + ] +} diff --git a/fixtures/mcp_trace_replay_corruptions/mcp_trace_replay_degraded_v1/truncate_recovery_path/state.json b/fixtures/mcp_trace_replay_corruptions/mcp_trace_replay_degraded_v1/truncate_recovery_path/state.json new file mode 100644 index 0000000..7597431 --- /dev/null +++ b/fixtures/mcp_trace_replay_corruptions/mcp_trace_replay_degraded_v1/truncate_recovery_path/state.json @@ -0,0 +1,44 @@ +{ + "state_version": "1.0", + "entities": {}, + "capability_boundaries": [ + [ + "agent", + "capability_scope_checked" + ], + [ + "capability_scope_checked", + "validate_external_action" + ], + [ + "human_approval", + "execute_external_action" + ] + ], + "allowed_tools": [ + "read_context", + "validate_external_action", + "execute_external_action", + "verify_result" + ], + "resource_boundaries": [ + [ + "execute_external_action", + "approved_external_resource" + ] + ], + "permission_scopes": { + "execute_external_action": [ + "requires_human_approval", + "requires_validation_passed" + ] + }, + "capability_scope": { + "agent": [ + "read_context", + "validate_external_action", + "execute_external_action", + "verify_result" + ] + } +} diff --git a/fixtures/mcp_trace_replay_corruptions/mcp_trace_replay_degraded_v1/truncate_recovery_path/trace.json b/fixtures/mcp_trace_replay_corruptions/mcp_trace_replay_degraded_v1/truncate_recovery_path/trace.json new file mode 100644 index 0000000..866773a --- /dev/null +++ b/fixtures/mcp_trace_replay_corruptions/mcp_trace_replay_degraded_v1/truncate_recovery_path/trace.json @@ -0,0 +1,32 @@ +{ + "events": [ + { + "action": "user_request_received", + "step": 1 + }, + { + "action": "capability_scope_checked", + "step": 2 + }, + { + "action": "tool_schema_validated", + "step": 3 + }, + { + "action": "read_context", + "step": 4 + }, + { + "action": "validate_external_action", + "step": 5 + }, + { + "action": "execute_external_action", + "step": 6 + }, + { + "action": "verify_result", + "step": 7 + } + ] +} diff --git a/fixtures/mcp_trace_replay_corruptions/mcp_trace_replay_mild_v1/drop_approval_gate/dependency_graph.json b/fixtures/mcp_trace_replay_corruptions/mcp_trace_replay_mild_v1/drop_approval_gate/dependency_graph.json new file mode 100644 index 0000000..f2d8fc1 --- /dev/null +++ b/fixtures/mcp_trace_replay_corruptions/mcp_trace_replay_mild_v1/drop_approval_gate/dependency_graph.json @@ -0,0 +1,132 @@ +{ + "graph_version": "1.0", + "nodes": [ + { + "node_id": "system_start", + "label": "System start", + "metadata": { + "phase": "system" + } + }, + { + "node_id": "user_request_received", + "label": "User request received", + "metadata": { + "phase": "intake" + } + }, + { + "node_id": "capability_scope_checked", + "label": "Capability scope checked", + "metadata": { + "phase": "governance" + } + }, + { + "node_id": "tool_schema_validated", + "label": "Tool schema validated", + "metadata": { + "phase": "validation" + } + }, + { + "node_id": "read_context", + "label": "Read context", + "metadata": { + "phase": "context" + } + }, + { + "node_id": "validate_external_action", + "label": "Validate external action", + "metadata": { + "phase": "validation" + } + }, + { + "node_id": "execute_external_action", + "label": "Execute external action", + "metadata": { + "phase": "execution" + } + }, + { + "node_id": "verify_result", + "label": "Verify result", + "metadata": { + "phase": "verification" + } + }, + { + "node_id": "recovery_path_registered", + "label": "Recovery path registered", + "metadata": { + "phase": "recovery" + } + } + ], + "edges": [ + { + "source": "system_start", + "target": "user_request_received", + "relation": "TEMPORAL", + "metadata": {} + }, + { + "source": "user_request_received", + "target": "capability_scope_checked", + "relation": "PREREQUISITE", + "metadata": {} + }, + { + "source": "capability_scope_checked", + "target": "tool_schema_validated", + "relation": "PREREQUISITE", + "metadata": {} + }, + { + "source": "tool_schema_validated", + "target": "read_context", + "relation": "DATA_FLOW", + "metadata": {} + }, + { + "source": "read_context", + "target": "validate_external_action", + "relation": "PREREQUISITE", + "metadata": {} + }, + { + "source": "validate_external_action", + "target": "execute_external_action", + "relation": "CAUSAL", + "metadata": {} + }, + { + "source": "execute_external_action", + "target": "verify_result", + "relation": "CAUSAL", + "metadata": {} + }, + { + "source": "execute_external_action", + "target": "recovery_path_registered", + "relation": "RECOVERY", + "metadata": {} + }, + { + "source": "capability_scope_checked", + "target": "execute_external_action", + "relation": "BLOCKER", + "metadata": { + "state": "enforced" + } + }, + { + "source": "capability_scope_checked", + "target": "validate_external_action", + "relation": "CAUSAL", + "metadata": {} + } + ] +} diff --git a/fixtures/mcp_trace_replay_corruptions/mcp_trace_replay_mild_v1/drop_approval_gate/state.json b/fixtures/mcp_trace_replay_corruptions/mcp_trace_replay_mild_v1/drop_approval_gate/state.json new file mode 100644 index 0000000..4fb9820 --- /dev/null +++ b/fixtures/mcp_trace_replay_corruptions/mcp_trace_replay_mild_v1/drop_approval_gate/state.json @@ -0,0 +1,40 @@ +{ + "state_version": "1.0", + "entities": {}, + "capability_boundaries": [ + [ + "agent", + "capability_scope_checked" + ], + [ + "capability_scope_checked", + "validate_external_action" + ] + ], + "allowed_tools": [ + "read_context", + "validate_external_action", + "execute_external_action", + "verify_result" + ], + "resource_boundaries": [ + [ + "execute_external_action", + "approved_external_resource" + ] + ], + "permission_scopes": { + "execute_external_action": [ + "requires_human_approval", + "requires_validation_passed" + ] + }, + "capability_scope": { + "agent": [ + "read_context", + "validate_external_action", + "execute_external_action", + "verify_result" + ] + } +} diff --git a/fixtures/mcp_trace_replay_corruptions/mcp_trace_replay_mild_v1/drop_approval_gate/trace.json b/fixtures/mcp_trace_replay_corruptions/mcp_trace_replay_mild_v1/drop_approval_gate/trace.json new file mode 100644 index 0000000..0eb1904 --- /dev/null +++ b/fixtures/mcp_trace_replay_corruptions/mcp_trace_replay_mild_v1/drop_approval_gate/trace.json @@ -0,0 +1,36 @@ +{ + "events": [ + { + "action": "user_request_received", + "step": 1 + }, + { + "action": "capability_scope_checked", + "step": 2 + }, + { + "action": "tool_schema_validated", + "step": 3 + }, + { + "action": "read_context", + "step": 4 + }, + { + "action": "validate_external_action", + "step": 5 + }, + { + "action": "execute_external_action", + "step": 6 + }, + { + "action": "verify_result", + "step": 7 + }, + { + "action": "recovery_path_registered", + "step": 8 + } + ] +} diff --git a/fixtures/mcp_trace_replay_corruptions/mcp_trace_replay_mild_v1/remove_dependency_edge/dependency_graph.json b/fixtures/mcp_trace_replay_corruptions/mcp_trace_replay_mild_v1/remove_dependency_edge/dependency_graph.json new file mode 100644 index 0000000..e3149bc --- /dev/null +++ b/fixtures/mcp_trace_replay_corruptions/mcp_trace_replay_mild_v1/remove_dependency_edge/dependency_graph.json @@ -0,0 +1,126 @@ +{ + "graph_version": "1.0", + "nodes": [ + { + "node_id": "system_start", + "label": "System start", + "metadata": { + "phase": "system" + } + }, + { + "node_id": "user_request_received", + "label": "User request received", + "metadata": { + "phase": "intake" + } + }, + { + "node_id": "capability_scope_checked", + "label": "Capability scope checked", + "metadata": { + "phase": "governance" + } + }, + { + "node_id": "tool_schema_validated", + "label": "Tool schema validated", + "metadata": { + "phase": "validation" + } + }, + { + "node_id": "read_context", + "label": "Read context", + "metadata": { + "phase": "context" + } + }, + { + "node_id": "validate_external_action", + "label": "Validate external action", + "metadata": { + "phase": "validation" + } + }, + { + "node_id": "execute_external_action", + "label": "Execute external action", + "metadata": { + "phase": "execution" + } + }, + { + "node_id": "verify_result", + "label": "Verify result", + "metadata": { + "phase": "verification" + } + }, + { + "node_id": "recovery_path_registered", + "label": "Recovery path registered", + "metadata": { + "phase": "recovery" + } + } + ], + "edges": [ + { + "source": "system_start", + "target": "user_request_received", + "relation": "TEMPORAL", + "metadata": {} + }, + { + "source": "user_request_received", + "target": "capability_scope_checked", + "relation": "PREREQUISITE", + "metadata": {} + }, + { + "source": "capability_scope_checked", + "target": "tool_schema_validated", + "relation": "PREREQUISITE", + "metadata": {} + }, + { + "source": "tool_schema_validated", + "target": "read_context", + "relation": "DATA_FLOW", + "metadata": {} + }, + { + "source": "validate_external_action", + "target": "execute_external_action", + "relation": "CAUSAL", + "metadata": {} + }, + { + "source": "execute_external_action", + "target": "verify_result", + "relation": "CAUSAL", + "metadata": {} + }, + { + "source": "execute_external_action", + "target": "recovery_path_registered", + "relation": "RECOVERY", + "metadata": {} + }, + { + "source": "capability_scope_checked", + "target": "execute_external_action", + "relation": "BLOCKER", + "metadata": { + "state": "enforced" + } + }, + { + "source": "capability_scope_checked", + "target": "validate_external_action", + "relation": "CAUSAL", + "metadata": {} + } + ] +} diff --git a/fixtures/mcp_trace_replay_corruptions/mcp_trace_replay_mild_v1/remove_dependency_edge/state.json b/fixtures/mcp_trace_replay_corruptions/mcp_trace_replay_mild_v1/remove_dependency_edge/state.json new file mode 100644 index 0000000..7597431 --- /dev/null +++ b/fixtures/mcp_trace_replay_corruptions/mcp_trace_replay_mild_v1/remove_dependency_edge/state.json @@ -0,0 +1,44 @@ +{ + "state_version": "1.0", + "entities": {}, + "capability_boundaries": [ + [ + "agent", + "capability_scope_checked" + ], + [ + "capability_scope_checked", + "validate_external_action" + ], + [ + "human_approval", + "execute_external_action" + ] + ], + "allowed_tools": [ + "read_context", + "validate_external_action", + "execute_external_action", + "verify_result" + ], + "resource_boundaries": [ + [ + "execute_external_action", + "approved_external_resource" + ] + ], + "permission_scopes": { + "execute_external_action": [ + "requires_human_approval", + "requires_validation_passed" + ] + }, + "capability_scope": { + "agent": [ + "read_context", + "validate_external_action", + "execute_external_action", + "verify_result" + ] + } +} diff --git a/fixtures/mcp_trace_replay_corruptions/mcp_trace_replay_mild_v1/remove_dependency_edge/trace.json b/fixtures/mcp_trace_replay_corruptions/mcp_trace_replay_mild_v1/remove_dependency_edge/trace.json new file mode 100644 index 0000000..0eb1904 --- /dev/null +++ b/fixtures/mcp_trace_replay_corruptions/mcp_trace_replay_mild_v1/remove_dependency_edge/trace.json @@ -0,0 +1,36 @@ +{ + "events": [ + { + "action": "user_request_received", + "step": 1 + }, + { + "action": "capability_scope_checked", + "step": 2 + }, + { + "action": "tool_schema_validated", + "step": 3 + }, + { + "action": "read_context", + "step": 4 + }, + { + "action": "validate_external_action", + "step": 5 + }, + { + "action": "execute_external_action", + "step": 6 + }, + { + "action": "verify_result", + "step": 7 + }, + { + "action": "recovery_path_registered", + "step": 8 + } + ] +} diff --git a/fixtures/mcp_trace_replay_corruptions/mcp_trace_replay_mild_v1/truncate_recovery_path/dependency_graph.json b/fixtures/mcp_trace_replay_corruptions/mcp_trace_replay_mild_v1/truncate_recovery_path/dependency_graph.json new file mode 100644 index 0000000..f2d8fc1 --- /dev/null +++ b/fixtures/mcp_trace_replay_corruptions/mcp_trace_replay_mild_v1/truncate_recovery_path/dependency_graph.json @@ -0,0 +1,132 @@ +{ + "graph_version": "1.0", + "nodes": [ + { + "node_id": "system_start", + "label": "System start", + "metadata": { + "phase": "system" + } + }, + { + "node_id": "user_request_received", + "label": "User request received", + "metadata": { + "phase": "intake" + } + }, + { + "node_id": "capability_scope_checked", + "label": "Capability scope checked", + "metadata": { + "phase": "governance" + } + }, + { + "node_id": "tool_schema_validated", + "label": "Tool schema validated", + "metadata": { + "phase": "validation" + } + }, + { + "node_id": "read_context", + "label": "Read context", + "metadata": { + "phase": "context" + } + }, + { + "node_id": "validate_external_action", + "label": "Validate external action", + "metadata": { + "phase": "validation" + } + }, + { + "node_id": "execute_external_action", + "label": "Execute external action", + "metadata": { + "phase": "execution" + } + }, + { + "node_id": "verify_result", + "label": "Verify result", + "metadata": { + "phase": "verification" + } + }, + { + "node_id": "recovery_path_registered", + "label": "Recovery path registered", + "metadata": { + "phase": "recovery" + } + } + ], + "edges": [ + { + "source": "system_start", + "target": "user_request_received", + "relation": "TEMPORAL", + "metadata": {} + }, + { + "source": "user_request_received", + "target": "capability_scope_checked", + "relation": "PREREQUISITE", + "metadata": {} + }, + { + "source": "capability_scope_checked", + "target": "tool_schema_validated", + "relation": "PREREQUISITE", + "metadata": {} + }, + { + "source": "tool_schema_validated", + "target": "read_context", + "relation": "DATA_FLOW", + "metadata": {} + }, + { + "source": "read_context", + "target": "validate_external_action", + "relation": "PREREQUISITE", + "metadata": {} + }, + { + "source": "validate_external_action", + "target": "execute_external_action", + "relation": "CAUSAL", + "metadata": {} + }, + { + "source": "execute_external_action", + "target": "verify_result", + "relation": "CAUSAL", + "metadata": {} + }, + { + "source": "execute_external_action", + "target": "recovery_path_registered", + "relation": "RECOVERY", + "metadata": {} + }, + { + "source": "capability_scope_checked", + "target": "execute_external_action", + "relation": "BLOCKER", + "metadata": { + "state": "enforced" + } + }, + { + "source": "capability_scope_checked", + "target": "validate_external_action", + "relation": "CAUSAL", + "metadata": {} + } + ] +} diff --git a/fixtures/mcp_trace_replay_corruptions/mcp_trace_replay_mild_v1/truncate_recovery_path/state.json b/fixtures/mcp_trace_replay_corruptions/mcp_trace_replay_mild_v1/truncate_recovery_path/state.json new file mode 100644 index 0000000..7597431 --- /dev/null +++ b/fixtures/mcp_trace_replay_corruptions/mcp_trace_replay_mild_v1/truncate_recovery_path/state.json @@ -0,0 +1,44 @@ +{ + "state_version": "1.0", + "entities": {}, + "capability_boundaries": [ + [ + "agent", + "capability_scope_checked" + ], + [ + "capability_scope_checked", + "validate_external_action" + ], + [ + "human_approval", + "execute_external_action" + ] + ], + "allowed_tools": [ + "read_context", + "validate_external_action", + "execute_external_action", + "verify_result" + ], + "resource_boundaries": [ + [ + "execute_external_action", + "approved_external_resource" + ] + ], + "permission_scopes": { + "execute_external_action": [ + "requires_human_approval", + "requires_validation_passed" + ] + }, + "capability_scope": { + "agent": [ + "read_context", + "validate_external_action", + "execute_external_action", + "verify_result" + ] + } +} diff --git a/fixtures/mcp_trace_replay_corruptions/mcp_trace_replay_mild_v1/truncate_recovery_path/trace.json b/fixtures/mcp_trace_replay_corruptions/mcp_trace_replay_mild_v1/truncate_recovery_path/trace.json new file mode 100644 index 0000000..866773a --- /dev/null +++ b/fixtures/mcp_trace_replay_corruptions/mcp_trace_replay_mild_v1/truncate_recovery_path/trace.json @@ -0,0 +1,32 @@ +{ + "events": [ + { + "action": "user_request_received", + "step": 1 + }, + { + "action": "capability_scope_checked", + "step": 2 + }, + { + "action": "tool_schema_validated", + "step": 3 + }, + { + "action": "read_context", + "step": 4 + }, + { + "action": "validate_external_action", + "step": 5 + }, + { + "action": "execute_external_action", + "step": 6 + }, + { + "action": "verify_result", + "step": 7 + } + ] +} diff --git a/fixtures/mcp_trace_replay_corruptions/mcp_trace_replay_moderate_v1/drop_approval_gate/dependency_graph.json b/fixtures/mcp_trace_replay_corruptions/mcp_trace_replay_moderate_v1/drop_approval_gate/dependency_graph.json new file mode 100644 index 0000000..f2d8fc1 --- /dev/null +++ b/fixtures/mcp_trace_replay_corruptions/mcp_trace_replay_moderate_v1/drop_approval_gate/dependency_graph.json @@ -0,0 +1,132 @@ +{ + "graph_version": "1.0", + "nodes": [ + { + "node_id": "system_start", + "label": "System start", + "metadata": { + "phase": "system" + } + }, + { + "node_id": "user_request_received", + "label": "User request received", + "metadata": { + "phase": "intake" + } + }, + { + "node_id": "capability_scope_checked", + "label": "Capability scope checked", + "metadata": { + "phase": "governance" + } + }, + { + "node_id": "tool_schema_validated", + "label": "Tool schema validated", + "metadata": { + "phase": "validation" + } + }, + { + "node_id": "read_context", + "label": "Read context", + "metadata": { + "phase": "context" + } + }, + { + "node_id": "validate_external_action", + "label": "Validate external action", + "metadata": { + "phase": "validation" + } + }, + { + "node_id": "execute_external_action", + "label": "Execute external action", + "metadata": { + "phase": "execution" + } + }, + { + "node_id": "verify_result", + "label": "Verify result", + "metadata": { + "phase": "verification" + } + }, + { + "node_id": "recovery_path_registered", + "label": "Recovery path registered", + "metadata": { + "phase": "recovery" + } + } + ], + "edges": [ + { + "source": "system_start", + "target": "user_request_received", + "relation": "TEMPORAL", + "metadata": {} + }, + { + "source": "user_request_received", + "target": "capability_scope_checked", + "relation": "PREREQUISITE", + "metadata": {} + }, + { + "source": "capability_scope_checked", + "target": "tool_schema_validated", + "relation": "PREREQUISITE", + "metadata": {} + }, + { + "source": "tool_schema_validated", + "target": "read_context", + "relation": "DATA_FLOW", + "metadata": {} + }, + { + "source": "read_context", + "target": "validate_external_action", + "relation": "PREREQUISITE", + "metadata": {} + }, + { + "source": "validate_external_action", + "target": "execute_external_action", + "relation": "CAUSAL", + "metadata": {} + }, + { + "source": "execute_external_action", + "target": "verify_result", + "relation": "CAUSAL", + "metadata": {} + }, + { + "source": "execute_external_action", + "target": "recovery_path_registered", + "relation": "RECOVERY", + "metadata": {} + }, + { + "source": "capability_scope_checked", + "target": "execute_external_action", + "relation": "BLOCKER", + "metadata": { + "state": "enforced" + } + }, + { + "source": "capability_scope_checked", + "target": "validate_external_action", + "relation": "CAUSAL", + "metadata": {} + } + ] +} diff --git a/fixtures/mcp_trace_replay_corruptions/mcp_trace_replay_moderate_v1/drop_approval_gate/state.json b/fixtures/mcp_trace_replay_corruptions/mcp_trace_replay_moderate_v1/drop_approval_gate/state.json new file mode 100644 index 0000000..4fb9820 --- /dev/null +++ b/fixtures/mcp_trace_replay_corruptions/mcp_trace_replay_moderate_v1/drop_approval_gate/state.json @@ -0,0 +1,40 @@ +{ + "state_version": "1.0", + "entities": {}, + "capability_boundaries": [ + [ + "agent", + "capability_scope_checked" + ], + [ + "capability_scope_checked", + "validate_external_action" + ] + ], + "allowed_tools": [ + "read_context", + "validate_external_action", + "execute_external_action", + "verify_result" + ], + "resource_boundaries": [ + [ + "execute_external_action", + "approved_external_resource" + ] + ], + "permission_scopes": { + "execute_external_action": [ + "requires_human_approval", + "requires_validation_passed" + ] + }, + "capability_scope": { + "agent": [ + "read_context", + "validate_external_action", + "execute_external_action", + "verify_result" + ] + } +} diff --git a/fixtures/mcp_trace_replay_corruptions/mcp_trace_replay_moderate_v1/drop_approval_gate/trace.json b/fixtures/mcp_trace_replay_corruptions/mcp_trace_replay_moderate_v1/drop_approval_gate/trace.json new file mode 100644 index 0000000..0eb1904 --- /dev/null +++ b/fixtures/mcp_trace_replay_corruptions/mcp_trace_replay_moderate_v1/drop_approval_gate/trace.json @@ -0,0 +1,36 @@ +{ + "events": [ + { + "action": "user_request_received", + "step": 1 + }, + { + "action": "capability_scope_checked", + "step": 2 + }, + { + "action": "tool_schema_validated", + "step": 3 + }, + { + "action": "read_context", + "step": 4 + }, + { + "action": "validate_external_action", + "step": 5 + }, + { + "action": "execute_external_action", + "step": 6 + }, + { + "action": "verify_result", + "step": 7 + }, + { + "action": "recovery_path_registered", + "step": 8 + } + ] +} diff --git a/fixtures/mcp_trace_replay_corruptions/mcp_trace_replay_moderate_v1/remove_dependency_edge/dependency_graph.json b/fixtures/mcp_trace_replay_corruptions/mcp_trace_replay_moderate_v1/remove_dependency_edge/dependency_graph.json new file mode 100644 index 0000000..e3149bc --- /dev/null +++ b/fixtures/mcp_trace_replay_corruptions/mcp_trace_replay_moderate_v1/remove_dependency_edge/dependency_graph.json @@ -0,0 +1,126 @@ +{ + "graph_version": "1.0", + "nodes": [ + { + "node_id": "system_start", + "label": "System start", + "metadata": { + "phase": "system" + } + }, + { + "node_id": "user_request_received", + "label": "User request received", + "metadata": { + "phase": "intake" + } + }, + { + "node_id": "capability_scope_checked", + "label": "Capability scope checked", + "metadata": { + "phase": "governance" + } + }, + { + "node_id": "tool_schema_validated", + "label": "Tool schema validated", + "metadata": { + "phase": "validation" + } + }, + { + "node_id": "read_context", + "label": "Read context", + "metadata": { + "phase": "context" + } + }, + { + "node_id": "validate_external_action", + "label": "Validate external action", + "metadata": { + "phase": "validation" + } + }, + { + "node_id": "execute_external_action", + "label": "Execute external action", + "metadata": { + "phase": "execution" + } + }, + { + "node_id": "verify_result", + "label": "Verify result", + "metadata": { + "phase": "verification" + } + }, + { + "node_id": "recovery_path_registered", + "label": "Recovery path registered", + "metadata": { + "phase": "recovery" + } + } + ], + "edges": [ + { + "source": "system_start", + "target": "user_request_received", + "relation": "TEMPORAL", + "metadata": {} + }, + { + "source": "user_request_received", + "target": "capability_scope_checked", + "relation": "PREREQUISITE", + "metadata": {} + }, + { + "source": "capability_scope_checked", + "target": "tool_schema_validated", + "relation": "PREREQUISITE", + "metadata": {} + }, + { + "source": "tool_schema_validated", + "target": "read_context", + "relation": "DATA_FLOW", + "metadata": {} + }, + { + "source": "validate_external_action", + "target": "execute_external_action", + "relation": "CAUSAL", + "metadata": {} + }, + { + "source": "execute_external_action", + "target": "verify_result", + "relation": "CAUSAL", + "metadata": {} + }, + { + "source": "execute_external_action", + "target": "recovery_path_registered", + "relation": "RECOVERY", + "metadata": {} + }, + { + "source": "capability_scope_checked", + "target": "execute_external_action", + "relation": "BLOCKER", + "metadata": { + "state": "enforced" + } + }, + { + "source": "capability_scope_checked", + "target": "validate_external_action", + "relation": "CAUSAL", + "metadata": {} + } + ] +} diff --git a/fixtures/mcp_trace_replay_corruptions/mcp_trace_replay_moderate_v1/remove_dependency_edge/state.json b/fixtures/mcp_trace_replay_corruptions/mcp_trace_replay_moderate_v1/remove_dependency_edge/state.json new file mode 100644 index 0000000..7597431 --- /dev/null +++ b/fixtures/mcp_trace_replay_corruptions/mcp_trace_replay_moderate_v1/remove_dependency_edge/state.json @@ -0,0 +1,44 @@ +{ + "state_version": "1.0", + "entities": {}, + "capability_boundaries": [ + [ + "agent", + "capability_scope_checked" + ], + [ + "capability_scope_checked", + "validate_external_action" + ], + [ + "human_approval", + "execute_external_action" + ] + ], + "allowed_tools": [ + "read_context", + "validate_external_action", + "execute_external_action", + "verify_result" + ], + "resource_boundaries": [ + [ + "execute_external_action", + "approved_external_resource" + ] + ], + "permission_scopes": { + "execute_external_action": [ + "requires_human_approval", + "requires_validation_passed" + ] + }, + "capability_scope": { + "agent": [ + "read_context", + "validate_external_action", + "execute_external_action", + "verify_result" + ] + } +} diff --git a/fixtures/mcp_trace_replay_corruptions/mcp_trace_replay_moderate_v1/remove_dependency_edge/trace.json b/fixtures/mcp_trace_replay_corruptions/mcp_trace_replay_moderate_v1/remove_dependency_edge/trace.json new file mode 100644 index 0000000..0eb1904 --- /dev/null +++ b/fixtures/mcp_trace_replay_corruptions/mcp_trace_replay_moderate_v1/remove_dependency_edge/trace.json @@ -0,0 +1,36 @@ +{ + "events": [ + { + "action": "user_request_received", + "step": 1 + }, + { + "action": "capability_scope_checked", + "step": 2 + }, + { + "action": "tool_schema_validated", + "step": 3 + }, + { + "action": "read_context", + "step": 4 + }, + { + "action": "validate_external_action", + "step": 5 + }, + { + "action": "execute_external_action", + "step": 6 + }, + { + "action": "verify_result", + "step": 7 + }, + { + "action": "recovery_path_registered", + "step": 8 + } + ] +} diff --git a/fixtures/mcp_trace_replay_corruptions/mcp_trace_replay_moderate_v1/truncate_recovery_path/dependency_graph.json b/fixtures/mcp_trace_replay_corruptions/mcp_trace_replay_moderate_v1/truncate_recovery_path/dependency_graph.json new file mode 100644 index 0000000..f2d8fc1 --- /dev/null +++ b/fixtures/mcp_trace_replay_corruptions/mcp_trace_replay_moderate_v1/truncate_recovery_path/dependency_graph.json @@ -0,0 +1,132 @@ +{ + "graph_version": "1.0", + "nodes": [ + { + "node_id": "system_start", + "label": "System start", + "metadata": { + "phase": "system" + } + }, + { + "node_id": "user_request_received", + "label": "User request received", + "metadata": { + "phase": "intake" + } + }, + { + "node_id": "capability_scope_checked", + "label": "Capability scope checked", + "metadata": { + "phase": "governance" + } + }, + { + "node_id": "tool_schema_validated", + "label": "Tool schema validated", + "metadata": { + "phase": "validation" + } + }, + { + "node_id": "read_context", + "label": "Read context", + "metadata": { + "phase": "context" + } + }, + { + "node_id": "validate_external_action", + "label": "Validate external action", + "metadata": { + "phase": "validation" + } + }, + { + "node_id": "execute_external_action", + "label": "Execute external action", + "metadata": { + "phase": "execution" + } + }, + { + "node_id": "verify_result", + "label": "Verify result", + "metadata": { + "phase": "verification" + } + }, + { + "node_id": "recovery_path_registered", + "label": "Recovery path registered", + "metadata": { + "phase": "recovery" + } + } + ], + "edges": [ + { + "source": "system_start", + "target": "user_request_received", + "relation": "TEMPORAL", + "metadata": {} + }, + { + "source": "user_request_received", + "target": "capability_scope_checked", + "relation": "PREREQUISITE", + "metadata": {} + }, + { + "source": "capability_scope_checked", + "target": "tool_schema_validated", + "relation": "PREREQUISITE", + "metadata": {} + }, + { + "source": "tool_schema_validated", + "target": "read_context", + "relation": "DATA_FLOW", + "metadata": {} + }, + { + "source": "read_context", + "target": "validate_external_action", + "relation": "PREREQUISITE", + "metadata": {} + }, + { + "source": "validate_external_action", + "target": "execute_external_action", + "relation": "CAUSAL", + "metadata": {} + }, + { + "source": "execute_external_action", + "target": "verify_result", + "relation": "CAUSAL", + "metadata": {} + }, + { + "source": "execute_external_action", + "target": "recovery_path_registered", + "relation": "RECOVERY", + "metadata": {} + }, + { + "source": "capability_scope_checked", + "target": "execute_external_action", + "relation": "BLOCKER", + "metadata": { + "state": "enforced" + } + }, + { + "source": "capability_scope_checked", + "target": "validate_external_action", + "relation": "CAUSAL", + "metadata": {} + } + ] +} diff --git a/fixtures/mcp_trace_replay_corruptions/mcp_trace_replay_moderate_v1/truncate_recovery_path/state.json b/fixtures/mcp_trace_replay_corruptions/mcp_trace_replay_moderate_v1/truncate_recovery_path/state.json new file mode 100644 index 0000000..7597431 --- /dev/null +++ b/fixtures/mcp_trace_replay_corruptions/mcp_trace_replay_moderate_v1/truncate_recovery_path/state.json @@ -0,0 +1,44 @@ +{ + "state_version": "1.0", + "entities": {}, + "capability_boundaries": [ + [ + "agent", + "capability_scope_checked" + ], + [ + "capability_scope_checked", + "validate_external_action" + ], + [ + "human_approval", + "execute_external_action" + ] + ], + "allowed_tools": [ + "read_context", + "validate_external_action", + "execute_external_action", + "verify_result" + ], + "resource_boundaries": [ + [ + "execute_external_action", + "approved_external_resource" + ] + ], + "permission_scopes": { + "execute_external_action": [ + "requires_human_approval", + "requires_validation_passed" + ] + }, + "capability_scope": { + "agent": [ + "read_context", + "validate_external_action", + "execute_external_action", + "verify_result" + ] + } +} diff --git a/fixtures/mcp_trace_replay_corruptions/mcp_trace_replay_moderate_v1/truncate_recovery_path/trace.json b/fixtures/mcp_trace_replay_corruptions/mcp_trace_replay_moderate_v1/truncate_recovery_path/trace.json new file mode 100644 index 0000000..866773a --- /dev/null +++ b/fixtures/mcp_trace_replay_corruptions/mcp_trace_replay_moderate_v1/truncate_recovery_path/trace.json @@ -0,0 +1,32 @@ +{ + "events": [ + { + "action": "user_request_received", + "step": 1 + }, + { + "action": "capability_scope_checked", + "step": 2 + }, + { + "action": "tool_schema_validated", + "step": 3 + }, + { + "action": "read_context", + "step": 4 + }, + { + "action": "validate_external_action", + "step": 5 + }, + { + "action": "execute_external_action", + "step": 6 + }, + { + "action": "verify_result", + "step": 7 + } + ] +} diff --git a/scripts/materialize_mcp_trace_corruptions.py b/scripts/materialize_mcp_trace_corruptions.py new file mode 100644 index 0000000..b69975f --- /dev/null +++ b/scripts/materialize_mcp_trace_corruptions.py @@ -0,0 +1,172 @@ +"""Materialize deterministic MCP trace corruption fixtures from committed manifest.""" + +from __future__ import annotations + +import json +import shutil +from pathlib import Path +from typing import Any + +REPO_ROOT = Path(__file__).resolve().parents[1] +MANIFEST_PATH = REPO_ROOT / "artifacts" / "mcp_trace_corruption_manifest.json" +OUTPUT_ROOT = REPO_ROOT / "fixtures" / "mcp_trace_replay_corruptions" +REQUIRED_FIXTURE_FILES: tuple[str, ...] = ("trace.json", "dependency_graph.json", "state.json") +SELECTED_OPERATORS: tuple[str, ...] = ( + "DROP_APPROVAL_GATE", + "REMOVE_DEPENDENCY_EDGE", + "TRUNCATE_RECOVERY_PATH", +) + + +def _repo_relative(path: Path) -> str: + return path.relative_to(REPO_ROOT).as_posix() + + +def _source_slug(source_fixture: str) -> str: + return source_fixture.rstrip("/").rsplit("/", maxsplit=1)[-1] + + +def _split_corruption_id(corruption_id: str) -> tuple[str, str]: + parts = corruption_id.split("::", maxsplit=1) + if len(parts) != 2 or not parts[0] or not parts[1]: + raise RuntimeError(f"Invalid corruption_id format: {corruption_id}") + return parts[0], parts[1] + + +def _as_list(value: Any, *, field: str) -> list[Any]: + if value is None: + return [] + if not isinstance(value, list): + raise RuntimeError(f"Expected list field {field}") + return value + + +def _load_json(path: Path) -> dict[str, Any]: + try: + payload = json.loads(path.read_text(encoding="utf-8")) + except FileNotFoundError as exc: + raise RuntimeError(f"Required JSON file is missing: {_repo_relative(path)}") from exc + except json.JSONDecodeError as exc: + raise RuntimeError(f"Invalid JSON in {_repo_relative(path)}: {exc}") from exc + + if not isinstance(payload, dict): + raise RuntimeError(f"Expected JSON object in {_repo_relative(path)}") + return payload + + +def _write_json(path: Path, payload: dict[str, Any]) -> None: + path.write_text(json.dumps(payload, indent=2, sort_keys=False, ensure_ascii=False) + "\n", encoding="utf-8") + + +def _materialize_drop_approval_gate(state: dict[str, Any]) -> dict[str, Any]: + boundaries = _as_list(state.get("capability_boundaries"), field="state.capability_boundaries") + + removed = False + mutated_boundaries: list[Any] = [] + for boundary in boundaries: + if boundary == ["human_approval", "execute_external_action"] and not removed: + removed = True + continue + mutated_boundaries.append(boundary) + + if not removed: + raise RuntimeError("Operator DROP_APPROVAL_GATE not applicable: missing human approval boundary") + + state["capability_boundaries"] = mutated_boundaries + return state + + +def _materialize_remove_dependency_edge(graph: dict[str, Any]) -> dict[str, Any]: + edges = _as_list(graph.get("edges"), field="dependency_graph.edges") + + removed = False + mutated_edges: list[Any] = [] + for edge in edges: + if ( + isinstance(edge, dict) + and edge.get("source") == "read_context" + and edge.get("target") == "validate_external_action" + and not removed + ): + removed = True + continue + mutated_edges.append(edge) + + if not removed: + raise RuntimeError( + "Operator REMOVE_DEPENDENCY_EDGE not applicable: missing read_context->validate_external_action edge" + ) + + graph["edges"] = mutated_edges + return graph + + +def _materialize_truncate_recovery_path(trace: dict[str, Any]) -> dict[str, Any]: + events = _as_list(trace.get("events"), field="trace.events") + if not events: + raise RuntimeError("Operator TRUNCATE_RECOVERY_PATH not applicable: trace has no events") + + terminal = events[-1] + if not isinstance(terminal, dict) or terminal.get("action") != "recovery_path_registered": + raise RuntimeError( + "Operator TRUNCATE_RECOVERY_PATH not applicable: terminal action is not recovery_path_registered" + ) + + trace["events"] = events[:-1] + return trace + + +def materialize_mcp_trace_corruptions(output_root: Path = OUTPUT_ROOT) -> Path: + manifest = _load_json(MANIFEST_PATH) + entries = _as_list(manifest.get("corruptions"), field="manifest.corruptions") + + output_root.mkdir(parents=True, exist_ok=True) + + for entry in entries: + if not isinstance(entry, dict): + raise RuntimeError("Each manifest corruption entry must be an object") + + operator = entry.get("operator") + if operator not in SELECTED_OPERATORS: + continue + + source_fixture = entry.get("source_fixture") + corruption_id = entry.get("corruption_id") + if not isinstance(source_fixture, str) or not isinstance(corruption_id, str): + raise RuntimeError("Manifest entry must include string source_fixture and corruption_id") + + source_slug, operator_slug = _split_corruption_id(corruption_id) + if source_slug != _source_slug(source_fixture): + raise RuntimeError( + f"Manifest corruption_id/source_fixture mismatch: {corruption_id} vs {source_fixture}" + ) + + source_original = REPO_ROOT / source_fixture / "original" + missing = [name for name in REQUIRED_FIXTURE_FILES if not (source_original / name).exists()] + if missing: + raise RuntimeError( + f"Incomplete MCP fixture {_repo_relative(source_original)}; missing: {', '.join(missing)}" + ) + + target_dir = output_root / source_slug / operator_slug + target_dir.mkdir(parents=True, exist_ok=True) + + for fixture_file in REQUIRED_FIXTURE_FILES: + shutil.copyfile(source_original / fixture_file, target_dir / fixture_file) + + if operator == "DROP_APPROVAL_GATE": + state = _load_json(target_dir / "state.json") + _write_json(target_dir / "state.json", _materialize_drop_approval_gate(state)) + elif operator == "REMOVE_DEPENDENCY_EDGE": + graph = _load_json(target_dir / "dependency_graph.json") + _write_json(target_dir / "dependency_graph.json", _materialize_remove_dependency_edge(graph)) + elif operator == "TRUNCATE_RECOVERY_PATH": + trace = _load_json(target_dir / "trace.json") + _write_json(target_dir / "trace.json", _materialize_truncate_recovery_path(trace)) + + return output_root + + +if __name__ == "__main__": + path = materialize_mcp_trace_corruptions() + print(path.relative_to(REPO_ROOT).as_posix()) diff --git a/tests/test_mcp_trace_corruption_materialization.py b/tests/test_mcp_trace_corruption_materialization.py new file mode 100644 index 0000000..3fa4595 --- /dev/null +++ b/tests/test_mcp_trace_corruption_materialization.py @@ -0,0 +1,147 @@ +from __future__ import annotations + +import filecmp +import json +from pathlib import Path + +from scripts.materialize_mcp_trace_corruptions import ( + OUTPUT_ROOT, + SELECTED_OPERATORS, + materialize_mcp_trace_corruptions, +) +from src.validation.failure_taxonomy import FAILURE_TAXONOMY + +REPO_ROOT = Path(__file__).resolve().parents[1] +MANIFEST_PATH = REPO_ROOT / "artifacts" / "mcp_trace_corruption_manifest.json" +FORBIDDEN_TOKENS = ('"generated_at"', '"timestamp"', '"host"', '"user"', '"env"', "/workspace/") +REQUIRED_FILES = ("trace.json", "dependency_graph.json", "state.json") + + +def _load_manifest() -> dict[str, object]: + return json.loads(MANIFEST_PATH.read_text(encoding="utf-8")) + + +def _materialized_entries() -> list[dict[str, object]]: + manifest = _load_manifest() + return [ + entry + for entry in manifest["corruptions"] + if entry["operator"] in SELECTED_OPERATORS + ] + + +def _split_corruption_id(corruption_id: str) -> tuple[str, str]: + parts = corruption_id.split("::", maxsplit=1) + assert len(parts) == 2 + assert parts[0] + assert parts[1] + return parts[0], parts[1] + + +def test_materialized_corruption_directories_exist() -> None: + assert OUTPUT_ROOT.exists() + for entry in _materialized_entries(): + source_slug, operator_slug = _split_corruption_id(entry["corruption_id"]) + assert (OUTPUT_ROOT / source_slug / operator_slug).exists() + + +def test_only_selected_operators_are_materialized() -> None: + expected = { + _split_corruption_id(entry["corruption_id"]) + for entry in _materialized_entries() + } + found: set[tuple[str, str]] = set() + + for source_dir in OUTPUT_ROOT.iterdir(): + if not source_dir.is_dir(): + continue + for operator_dir in source_dir.iterdir(): + if not operator_dir.is_dir(): + continue + found.add((source_dir.name, operator_dir.name)) + + assert found == expected + + +def test_materialized_fixtures_preserve_file_shape() -> None: + for entry in _materialized_entries(): + source_slug, operator_slug = _split_corruption_id(entry["corruption_id"]) + fixture_dir = OUTPUT_ROOT / source_slug / operator_slug + assert sorted(p.name for p in fixture_dir.iterdir() if p.is_file()) == sorted(REQUIRED_FILES) + + +def test_source_fixtures_exist_for_materialized_entries() -> None: + for entry in _materialized_entries(): + source_fixture = entry["source_fixture"] + source_original = REPO_ROOT / source_fixture / "original" + assert source_original.exists() + for name in REQUIRED_FILES: + assert (source_original / name).exists() + + +def test_materialized_entries_have_consistent_manifest_identity() -> None: + for entry in _materialized_entries(): + source_slug, operator_slug = _split_corruption_id(entry["corruption_id"]) + assert source_slug == Path(entry["source_fixture"]).name + assert operator_slug in { + "drop_approval_gate", + "remove_dependency_edge", + "truncate_recovery_path", + } + + +def test_only_intended_file_surface_changes_per_operator() -> None: + for entry in _materialized_entries(): + operator = entry["operator"] + source_slug, operator_slug = _split_corruption_id(entry["corruption_id"]) + source_original = REPO_ROOT / entry["source_fixture"] / "original" + materialized = OUTPUT_ROOT / source_slug / operator_slug + + trace_same = filecmp.cmp(source_original / "trace.json", materialized / "trace.json", shallow=False) + graph_same = filecmp.cmp( + source_original / "dependency_graph.json", + materialized / "dependency_graph.json", + shallow=False, + ) + state_same = filecmp.cmp(source_original / "state.json", materialized / "state.json", shallow=False) + + if operator == "DROP_APPROVAL_GATE": + assert not state_same + assert trace_same + assert graph_same + elif operator == "REMOVE_DEPENDENCY_EDGE": + assert not graph_same + assert trace_same + assert state_same + elif operator == "TRUNCATE_RECOVERY_PATH": + assert not trace_same + assert graph_same + assert state_same + + +def test_materialized_fixtures_have_no_time_or_environment_fields() -> None: + for path in OUTPUT_ROOT.glob("**/*.json"): + text = path.read_text(encoding="utf-8").lower() + for token in FORBIDDEN_TOKENS: + assert token not in text + + +def test_materialization_reproduces_committed_output(tmp_path: Path) -> None: + generated_root = tmp_path / "generated_corruptions" + materialize_mcp_trace_corruptions(generated_root) + + expected_files = sorted(p.relative_to(OUTPUT_ROOT) for p in OUTPUT_ROOT.glob("**/*.json")) + generated_files = sorted(p.relative_to(generated_root) for p in generated_root.glob("**/*.json")) + assert generated_files == expected_files + + for relative_path in expected_files: + expected_text = (OUTPUT_ROOT / relative_path).read_text(encoding="utf-8") + generated_text = (generated_root / relative_path).read_text(encoding="utf-8") + assert generated_text == expected_text + + +def test_materialized_entries_match_manifest_operators_and_labels() -> None: + registered_labels = set(FAILURE_TAXONOMY) + for entry in _materialized_entries(): + assert entry["operator"] in SELECTED_OPERATORS + assert entry["expected_failure_label"] in registered_labels