mirror of
https://github.com/semantica-agi/semantica.git
synced 2026-08-29 04:26:20 +00:00
181 lines
6.3 KiB
Python
181 lines
6.3 KiB
Python
"""Tests for decision trace capture convenience API."""
|
|
|
|
from datetime import datetime
|
|
import logging
|
|
from unittest.mock import Mock
|
|
|
|
from semantica.context.decision_methods import (
|
|
_append_immutable_trace_events,
|
|
capture_decision_trace,
|
|
)
|
|
from semantica.context.decision_models import Decision
|
|
|
|
|
|
def _sample_decision() -> Decision:
|
|
return Decision(
|
|
decision_id="decision_trace_test_001",
|
|
category="credit_approval",
|
|
scenario="Credit line increase for long-term customer",
|
|
reasoning="Strong payment history and low utilization",
|
|
outcome="approved",
|
|
confidence=0.92,
|
|
timestamp=datetime.now(),
|
|
decision_maker="ai_agent",
|
|
)
|
|
|
|
|
|
def test_capture_decision_trace_without_graph_store_is_backward_compatible(caplog):
|
|
decision = _sample_decision()
|
|
with caplog.at_level(logging.WARNING):
|
|
decision_id = capture_decision_trace(
|
|
decision,
|
|
cross_system_context={"crm": {"arr": 120000}},
|
|
policy_ids=[{"policy_id": "renewal_discount_policy", "version": "3.2"}],
|
|
)
|
|
assert decision_id == decision.decision_id
|
|
assert "capture_decision_trace skipped persistence (no graph_store)" in caplog.text
|
|
assert f"decision_id={decision.decision_id}" in caplog.text
|
|
assert f"decision_maker={decision.decision_maker}" in caplog.text
|
|
assert f"outcome={decision.outcome}" in caplog.text
|
|
|
|
|
|
def test_capture_decision_trace_with_graph_store_records_trace_events():
|
|
decision = _sample_decision()
|
|
graph_store = Mock()
|
|
|
|
def _execute_query(query, params=None, *args, **kwargs):
|
|
if "RETURN t.trace_id as trace_id" in query:
|
|
# Simulate existing chain head so NEXT_TRACE_EVENT is exercised.
|
|
return {
|
|
"records": [
|
|
{
|
|
"trace_id": "decision_trace_test_001:3",
|
|
"event_index": 3,
|
|
"event_hash": "prev_hash_123",
|
|
}
|
|
]
|
|
}
|
|
if "RETURN p.policy_id as policy_id, p.version as version" in query:
|
|
return {
|
|
"records": [
|
|
{"policy_id": "renewal_discount_policy_v3_2", "version": "3.2"}
|
|
]
|
|
}
|
|
return {"records": []}
|
|
|
|
graph_store.execute_query = Mock(side_effect=_execute_query)
|
|
|
|
decision_id = capture_decision_trace(
|
|
decision=decision,
|
|
cross_system_context={"crm": {"arr": 120000}},
|
|
graph_store=graph_store,
|
|
entities=["customer_123"],
|
|
source_documents=["renewal_note_001"],
|
|
policy_ids=["renewal_discount_policy_v3_2"],
|
|
approvals=[
|
|
{
|
|
"approver": "vp_finance",
|
|
"approval_method": "slack_dm",
|
|
"approval_context": "Approved due to SEV-1 impact history",
|
|
}
|
|
],
|
|
precedents=[
|
|
{
|
|
"precedent_id": "decision_legacy_001",
|
|
"relationship_type": "similar_scenario",
|
|
}
|
|
],
|
|
immutable_audit_log=True,
|
|
)
|
|
|
|
assert decision_id == decision.decision_id
|
|
calls = graph_store.execute_query.call_args_list
|
|
queries = [c[0][0] for c in calls]
|
|
params_list = [c[0][1] if len(c[0]) > 1 else {} for c in calls]
|
|
|
|
assert any("CREATE (t:DecisionTraceEvent" in q for q in queries)
|
|
assert any("MERGE (d)-[:HAS_TRACE_EVENT]->(t)" in q for q in queries)
|
|
assert any("MERGE (prev)-[:NEXT_TRACE_EVENT]->(curr)" in q for q in queries)
|
|
|
|
event_types = [
|
|
p.get("event_type")
|
|
for p in params_list
|
|
if isinstance(p, dict) and "event_type" in p
|
|
]
|
|
assert "DECISION_RECORDED" in event_types
|
|
assert "CROSS_SYSTEM_CONTEXT_CAPTURED" in event_types
|
|
assert "POLICIES_APPLIED" in event_types
|
|
assert "APPROVAL_CHAIN_RECORDED" in event_types
|
|
assert "PRECEDENTS_LINKED" in event_types
|
|
|
|
|
|
def test_capture_decision_trace_accepts_legacy_payload_shapes():
|
|
decision = _sample_decision()
|
|
graph_store = Mock()
|
|
graph_store.execute_query = Mock(return_value=[{"t": {"trace_id": "decision_trace_test_001:4", "event_index": 4, "event_hash": "abc"}}])
|
|
|
|
decision_id = capture_decision_trace(
|
|
decision=decision,
|
|
cross_system_context={"crm": {"arr": 120000}},
|
|
graph_store=graph_store,
|
|
entities="customer_legacy_001",
|
|
source_documents="doc_legacy_001",
|
|
policy_ids="policy_legacy_v1",
|
|
exceptions={"policy_id": "policy_legacy_v1", "reason": "legacy exception"},
|
|
approvals={"approver": "vp_ops", "approval_method": "email"},
|
|
precedents=["decision_legacy_001"],
|
|
immutable_audit_log=True,
|
|
)
|
|
|
|
assert decision_id == decision.decision_id
|
|
assert graph_store.execute_query.call_count > 0
|
|
|
|
|
|
def test_capture_decision_trace_accepts_versioned_policy_refs():
|
|
decision = _sample_decision()
|
|
graph_store = Mock()
|
|
graph_store.execute_query = Mock(
|
|
return_value={"records": [{"policy_id": "renewal_discount_policy", "version": "3.2"}]}
|
|
)
|
|
|
|
decision_id = capture_decision_trace(
|
|
decision=decision,
|
|
cross_system_context={"crm": {"arr": 120000}},
|
|
graph_store=graph_store,
|
|
policy_ids=[{"policy_id": "renewal_discount_policy", "version": "3.2"}],
|
|
immutable_audit_log=False,
|
|
)
|
|
|
|
assert decision_id == decision.decision_id
|
|
policy_calls = [
|
|
c for c in graph_store.execute_query.call_args_list
|
|
if "policy_version" in c[0][1]
|
|
]
|
|
assert policy_calls
|
|
assert policy_calls[0][0][1]["policy_version"] == "3.2"
|
|
|
|
|
|
def test_append_immutable_trace_events_logs_lookup_failure_and_continues(caplog):
|
|
graph_store = Mock()
|
|
calls = {"n": 0}
|
|
|
|
def _execute_query(*args, **kwargs):
|
|
calls["n"] += 1
|
|
if calls["n"] == 1:
|
|
raise RuntimeError("lookup failed")
|
|
return {"records": []}
|
|
|
|
graph_store.execute_query = Mock(side_effect=_execute_query)
|
|
|
|
with caplog.at_level(logging.WARNING):
|
|
_append_immutable_trace_events(
|
|
graph_store=graph_store,
|
|
decision_id="decision_trace_test_001",
|
|
events=[{"event_type": "DECISION_RECORDED", "payload": {"ok": True}}],
|
|
logger=logging.getLogger("test_logger"),
|
|
)
|
|
|
|
assert "Failed to lookup previous immutable trace event" in caplog.text
|
|
assert "decision_id=decision_trace_test_001" in caplog.text
|
|
assert graph_store.execute_query.call_count >= 2
|