Documents both @ZohaibHassan16's original fallback implementation and
the follow-up fixes by @KaifAhmad1: isinstance regression, add_node
signature bug, add_edge spurious kwarg, timezone handling, BFS
find_edges hoist, duplicate import removal, and full test coverage.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace isinstance(graph_store, ContextGraph) with type() is ContextGraph
in all 12 guards across decision_query.py and decision_recorder.py.
Fixes 2 regressions where Mock(spec=ContextGraph) triggered fallback
paths, causing TypeError on iteration of mock return values.
- Hoist find_edges() calls out of the BFS while-loop in trace_decision_path
so edges are fetched once per call instead of once per visited node,
eliminating O(nodes * total_edges) repeated full-graph fetches.
- Expand test_decision_query_fallback.py: keep the original integration
test and add 13 targeted unit tests covering all 7 DecisionQuery and
4 DecisionRecorder ContextGraph fallback methods, including tz-aware/naive
datetime mixing and Mock guard validation.
Result: 353 passed, 0 failed (was 338 passed, 2 failed on this branch)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
app.py:
- Fix unclosed '(' in generic_error_handler (two implementations were merged,
leaving the return JSONResponse( call with no closing paren)
- Remove duplicate 'from fastapi import FastAPI, Request' import
- Remove unused 'import traceback'
- Remove duplicate static file mount (was mounted twice: once conditionally,
once unconditionally creating the dir — FastAPI raises on duplicate mounts)
decisions.py:
- Remove stub 'return ComplianceResponse(compliant=True)' with unclosed '('
that was left in front of the real edge-scan implementation
temporal.py:
- Remove blocking get_nodes/get_edges calls (without asyncio.to_thread) that
were left as dead code above the correct async versions
- Fix empty 'except Exception:' clause before 'except ImportError:' that
caused a SyntaxError
tests/explorer/test_explorer_api.py:
- Remove all merge-artifact duplicate class definitions (TestAnalytics x2,
TestReasoning x2, TestAnnotations x2) — Python silently used the second
definition, hiding the first; collapsed into single canonical classes
- Fix test_snapshot_at referencing undefined 'body' (no request was made);
merged its assertions into test_snapshot_now
- Fix test_compliance asserting isinstance(body, list) on a dict response;
the displaced precedents-check code is now in test_precedents where it
belongs
- Fix test_compliance_with_violation using wrong session reference
- Remove duplicate node-lookup and duplicate assertions throughout
- Add test_search_content_populated: asserts search results carry non-empty
content (regression guard for the to_dict envelope fix)
- Add test_import_edge_metadata_preserved: asserts edge metadata survives the
import round-trip (regression guard for the properties/metadata fallback fix)
All 51 tests pass.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- session.search(): normalise node.to_dict() "properties" envelope to flat
{id, type, content, metadata} so /api/graph/search returns populated content
and properties instead of empty strings (Qodo bug #3)
- context_graph.add_edges(): fall back to "metadata" key when "properties" is
absent so edges imported from find_edges()/build_graph_dict() format don't
silently lose their metadata (Qodo bug #2)
- enrich.predict_links(): wrap the O(n) scoring loop in asyncio.to_thread() so
it never blocks the event loop on large graphs (Qodo bug #1)
- session.py: remove duplicate __init__ annotations assignment, duplicate
property definitions (un-locked first set), and dead-code double-query
inside get_nodes()/get_edges() left over from the merge
- enrich.py: remove unreachable code block after early return in predict_links
and duplicate nodes fetch in detect_duplicates left over from the merge
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Bugs fixed:
- enrich.py: predict_links called predictor.predict_links() with wrong
signature (graph_dict as graph_store, node_id as node_labels, top_n
instead of top_k). Rewrote to iterate candidate nodes and call
score_link(session.graph, src, candidate) directly.
- enrich.py: detect_duplicates called session.get_nodes() synchronously
in an async handler, blocking the event loop. Wrapped in to_thread().
- export_import.py: temp file was leaked on export exception. Now always
cleaned up via try/finally. Moved `import os` to module level.
- pyproject.toml: missing comma between two strings in the `all` extra
caused a TOML syntax error breaking `pip install semantica[all]`.
- app.py: generic Exception handler swallowed HTTPException(503) raised
by get_session dependency. Now re-raises HTTPException explicitly.
- decisions.py: compliance endpoint imported PolicyEngine then discarded
it, always returning compliant=True. Replaced with in-graph check:
scans for violates/non_compliant/breaches edges from the decision node.
- app.py: removed unused `import traceback`.
Refactor:
- session.py: added build_graph_dict(node_ids=None) method to eliminate
_build_graph_dict() duplication across graph.py, analytics.py, and
export_import.py (three identical copies).
- session.py: all 8 lazy analytics properties now initialise under _lock
to prevent double-instantiation under concurrent requests.
- graph.py: find_path now dispatches to dijkstra_shortest_path or
bfs_shortest_path based on the `algorithm` query param (was always BFS).
- annotations.py: removed unnecessary get_annotations() round-trip in
create_annotation — add_annotation mutates ann_data in-place.
- temporal.py: split bare `except Exception` into ImportError (silent)
and Exception (logs warning), so real bugs are no longer hidden.
Tests (49 total, all passing):
- Added TestEnrichExtract, TestLinkPrediction, TestDedup classes.
- Added test_compliance_with_violation to verify real violation detection.
- Added test_snapshot_at_excludes_temporal_node, test_diff assertions,
test_export_json_subset, test_import_with_edges, test_import_unsupported_format.
- Strengthened analytics, search, and annotation assertions.
- Reasoning test now asserts response shape when status is 200.
Co-authored-by: ZohaibHassan16 <zohaibhassan16@users.noreply.github.com>
Co-authored-by: KaifAhmad1 <kaifahmad1@users.noreply.github.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Documents the removal of the overwritten regex pattern and unreachable
return statement in _match_pattern, and the surfacing of regex errors.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Package & distribution
- pyproject.toml: add integrations* to packages.find include so pip
install semantica[agno] ships the integration
context_store.py
- upsert_memory(): run NERExtractor after store() to index entities
into the ContextGraph
- delete_memory() / drop_table() / clear(): call AgentContext.forget()
to propagate deletions to vector/graph storage
- find_precedents(): pass limit parameter to find_precedents_advanced()
- retrieve(): pass limit as max_results to AgentContext.retrieve()
- add get_context_for_prompt() for automatic system-prompt injection
knowledge_graph.py
- __init__: wire graph_builder.graph_store = self._graph so build()
persists into the ContextGraph
- add internal AgentContext for vector retrieval (shared ContextGraph)
- search(): use AgentContext.retrieve() for vector similarity; keyword
scoring as fallback
- _ingest_text(): add paragraph-level chunking before NER/relation
extraction (parse → split → NER → relation extract → graph build)
- get_graph_context(): return structured subgraph with edge types via
ContextGraph.get_neighbors()
- load_urls(): validate scheme (http/https only) to prevent SSRF
decision_kit.py
- check_policy(): replace broken PolicyEngine.check_compliance() call
with inline _eval_rule() that evaluates simple field-op-value rules;
return compliant=False (not True) on failure — closes security bug
kg_toolkit.py
- add_to_graph(): fix add_node(node_id=, node_type=) and
add_edge(source_id=, target_id=, edge_type=) to match real API
- query_graph(): use find_nodes() (no label param) + keyword filter
- find_related(): use get_neighbors(node_id=) returning List[Dict]
- infer_facts() / export_subgraph(): use find_nodes() public API
instead of private _nodes dict
shared_context.py
- _AgentScopedStore: store shared context as self._context (not
self._ctx) so all inherited AgnoContextStore methods work correctly
tests/integrations/agno/test_kg_toolkit.py
- _FakeGraph: rewrite to match real ContextGraph signatures —
find_nodes(node_type=), add_node(node_id, node_type, **),
add_edge(source_id, target_id, edge_type, **),
get_neighbors(node_id, hops=1, ...) returning List[Dict]
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>