From b309451398bf71c5284145ec7a0dfe70129580ad Mon Sep 17 00:00:00 2001 From: KaifAhmad1 Date: Sat, 14 Mar 2026 14:47:20 +0530 Subject: [PATCH] Fix review issues in context explainability PR --- semantica/context/context_graph.py | 18 ++++++++-- tests/context/test_context.py | 35 +++++++++++++++++++ ...st_030_context_graph_realworld_extended.py | 9 +++-- 3 files changed, 58 insertions(+), 4 deletions(-) diff --git a/semantica/context/context_graph.py b/semantica/context/context_graph.py index 18041204..6cae5ff9 100644 --- a/semantica/context/context_graph.py +++ b/semantica/context/context_graph.py @@ -430,7 +430,7 @@ class ContextGraph: "id": node.node_id, "content": node.content, "type": node.node_type, - "metadata": node.properties, + "metadata": (node.properties or {}).copy(), }) return result @@ -2220,6 +2220,20 @@ class ContextGraph: def _add_decision_to_graph(self, decision: Dict[str, Any]) -> None: """Add decision to context graph.""" try: + protected_properties = { + "category", + "scenario", + "reasoning", + "outcome", + "confidence", + "timestamp", + "decision_maker", + } + safe_metadata = { + key: value + for key, value in (decision.get("metadata") or {}).items() + if key not in protected_properties + } extra_properties = { key: value for key, value in decision.items() @@ -2248,7 +2262,7 @@ class ContextGraph: scenario=decision["scenario"], decision_maker=decision.get("decision_maker", ""), reasoning=decision["reasoning"], - **(decision.get("metadata") or {}), + **safe_metadata, **extra_properties, ) diff --git a/tests/context/test_context.py b/tests/context/test_context.py index 6575a541..54f705f1 100644 --- a/tests/context/test_context.py +++ b/tests/context/test_context.py @@ -109,6 +109,17 @@ class TestContextModule(unittest.TestCase): self.assertEqual(neighbors[0]["id"], "n2") self.assertEqual(neighbors[0]["relationship"], "knows") + def test_get_nodes_by_label_returns_metadata_copy(self): + graph = ContextGraph() + graph.add_node("n1", "person", "Alice", role="engineer") + + nodes = graph.get_nodes_by_label("person") + self.assertEqual(len(nodes), 1) + + nodes[0]["metadata"]["role"] = "mutated" + + self.assertEqual(graph.get_node_property("n1", "role"), "engineer") + def test_context_graph_preserves_full_decision_text(self): graph = ContextGraph() scenario = "Launch regional expansion plan " + ("X" * 140) @@ -134,6 +145,30 @@ class TestContextModule(unittest.TestCase): self.assertEqual(graph.nodes[root_id].properties["scenario"], scenario) self.assertEqual(chain[0].scenario, scenario) + def test_record_decision_metadata_cannot_override_core_fields(self): + graph = ContextGraph() + decision_id = graph.record_decision( + category="strategy", + scenario="Open LATAM expansion program", + reasoning="High growth potential", + outcome="approved", + confidence=0.9, + metadata={ + "scenario": "metadata override", + "category": "metadata category", + "outcome": "metadata outcome", + "custom_note": "keep me", + }, + ) + + node = graph.nodes[decision_id] + + self.assertEqual(node.content, "Open LATAM expansion program") + self.assertEqual(node.properties["scenario"], "Open LATAM expansion program") + self.assertEqual(node.properties["category"], "strategy") + self.assertEqual(node.properties["outcome"], "approved") + self.assertEqual(node.properties["custom_note"], "keep me") + # --- AgentMemory Tests --- def test_agent_memory_store(self): memory = AgentMemory(vector_store=self.mock_vector_store) diff --git a/tests/test_030_context_graph_realworld_extended.py b/tests/test_030_context_graph_realworld_extended.py index 49ba5bbc..28d0f8f0 100644 --- a/tests/test_030_context_graph_realworld_extended.py +++ b/tests/test_030_context_graph_realworld_extended.py @@ -347,9 +347,14 @@ class TestContextGraphAdvancedDecisionMethods: result = g.analyze_decision_influence(ids["alice"]) # alice, bob, and carol all share underwriter_ai_v5 — they should appear in influence direct = result["direct_influence"] - # At minimum the category-shared decisions should appear assert isinstance(direct, list) - assert result["total_influenced"] >= 0 # May be 0 if no category overlap + assert all(isinstance(item, dict) for item in direct) + assert all( + {"decision_id", "scenario", "outcome", "category"}.issubset(item.keys()) + for item in direct + ) + direct_ids = {item["decision_id"] for item in direct} + assert ids["bob"] in direct_ids def test_analyze_decision_influence_category_cross(self): """Decisions in same category appear in influence set."""