* security: sanitize Cypher labels/relationship types/property keys (GHSA-482h-hw99-h62p) Node labels and property keys passed to create_node/create_relationship were interpolated directly into Cypher strings in the Neptune, Neo4j, and FalkorDB graph stores. Property values are parameterized, but labels and keys can't be bound as parameters, and nothing validated them, so a document-derived entity type or property name could close the current Cypher token early and append arbitrary statements (e.g. DETACH DELETE), running with the application's database credentials. - New shared semantica/graph_store/query_sanitize.py: sanitize_identifier() generalizes age_store.py's existing _sanitize_label/_sanitize_rel_type (the only backend that already validated this) into a helper the other backends can import without an import cycle with graph_store.py/methods.py. - Applied at every label/relationship-type/property-key interpolation site in amazon_neptune.py, neo4j_store.py, falkordb_store.py, graph_store.py (degree_centrality's own query builder), and methods.py (update_relationship's own query builder) — create_node, create_nodes, create_relationship, get_nodes, get_relationships, get_neighbors, shortest_path, update_node, create_index, and all relationship-type filters. - depth/max_depth path-length parameters are also cast to int before interpolation as defense-in-depth (they're already typed int, but Python doesn't enforce that at runtime). Added tests/graph_store/test_cypher_injection.py (12 tests covering the sanitizer directly and reproducing the advisory's injection payload against Neptune/Neo4j/FalkorDB create_node/create_relationship — asserts the malicious query is never built or sent), plus regression tests for graph_store.py's degree_centrality and methods.py's update_relationship. Full graph_store test suite (224 tests) passes with no regressions. * fix(graph-store): prevent depth-based Cypher injection * test(graph-store): tighten injection regression assertions * docs(changelog): add PR #910 (GHSA-482h Cypher injection) entry --------- Co-authored-by: Sameer6305 <sskadam6305@gmail.com>
162 KiB
Changelog
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[Unreleased]
Added
-
Embedded Oxigraph backend for
TripletStore(#838, closes #834) by @Linxiushen- Added
OxigraphStore(semantica/triplet_store/oxigraph_store.py), an in-process SPARQL 1.1 store via the optionalpyoxigraphdependency — no external server (Blazegraph/Jena/RDF4J/Anzo) required, fixing the confusing plain connection-error failureTripletStorepreviously produced with no server running (no local Docker daemon, no Java, CI, or a fresh laptop) - Runs fully in memory by default, or persists to a local directory via
TripletStore(backend="oxigraph", path=...); reopening the same directory resumes existing data - Full CRUD, native batch loading (
Store.extend), named-graph scoping (graph=on add/query), and SPARQL SELECT/ASK/CONSTRUCT/DESCRIBE result mapping matching the existing backend contract; reusessparql_escaping.pyfor datatype-IRI resolution instead of reimplementing it, and preserves RDF literal datatype/language metadata across writes, reads, and query results - New optional
semantica[tripletstore-oxigraph]extra (pyoxigraph>=0.5.0), included in theallextra; the import is lazy, soTripletStoreand the rest of Semantica keep working withoutpyoxigraphinstalled - Wired into
TripletStore(backend="oxigraph", added toSUPPORTED_BACKENDSandNAMED_GRAPH_CAPABLE_BACKENDS) and exported fromsemantica.triplet_store; README, module reference, glossary, and usage guide updated with install/configuration examples - Fixed along the way: a missing
pyoxigraphinstall surfaced as a generic wrappedProcessingErrorinstead of the underlyingImportErrorand its install hint, becauseTripletStore._initialize_store_backend()'s broadexcept Exceptioncaught and rewrapped it;ImportErroris now re-raised as-is so thepip install "semantica[tripletstore-oxigraph]"hint reaches the caller - New integration tests in
tests/triplet_store/test_oxigraph_store.pycovering persistence/reopen, named-graph isolation, SELECT/ASK/CONSTRUCT result shapes, and the missing-dependency error message; skipped automatically whenpyoxigraphisn't installed, and not yet exercised in CI since it doesn't install the optional extra or run the Python test suite
- Added
-
PROV-O trust blockers and general spec completeness for
ProvenanceManager(#825) by @KaifAhmad1- Invalidation instead of hard delete: new
ProvenanceManager.invalidate(entity_id, agent_id, reason=None)tombstones an entry — archives its pre-invalidation state under a stable versioned key, then appends the invalidated entry (invalidated,invalidated_at_time,invalidated_by,invalidation_reason) — instead of mutating or deleting it, so an audit can prove a fact existed, was reviewed, and was retracted.ProvenanceManager.clear()remains the bulk dev/test store-reset utility it always was; it was not repurposed - Hash-chained integrity: every entry now carries
sequence_id/previous_checksum, chaining it to the entry immediately before it in insertion order. NewProvenanceManager.verify_chain()walks the chain and reports any break, including a row hard-deleted directly from the underlying table — something a lone per-row SHA-256 checksum can never detect on its own.compute_checksum()now also coversagent_id/agent_type, the lineage-link fields, and the invalidation fields, closing several fields that previously weren't tamper-evident - Typed Agent/Activity:
agent_idwas a dead field — notrack_*method read it from kwargs, so it was always the"semantica"default regardless of what callers passed; fixed, and paired with newAgentRecord(id, agent_type, is_automated)/ActivityRecord(id, activity_type, started_at_time, ended_at_time)dataclasses (pass viaagent=/activity=kwargs) so a human reviewer, an LLM call, and an automated pipeline stage are now distinguishable, and activities carry real start/end timing. Wired through all 18*_provenance.pywrapper modules andtrack_entity/track_relationship/track_chunk/track_property_source - Versioning vs. derivation split: new
previous_version_id("this corrects a prior version of the same fact") andderived_from_id("this was derived from a different source entity") fields, additive alongside the legacy combinedparent_entity_idso existing readers are unaffected - Downstream lineage traversal: new
get_descendants()/trace_descendants()(reverse BFS in bothInMemoryStorageandSQLiteStorage), closing the gap flagged insemantica/explorer/routes/provenance.pywheredirection="downstream"was dead code with no reverse lookup to feed it; the Explorer's/api/provenancelineage response now merges both directions - W3C PROV-O qualified relations:
export_prov()now emitsprov:qualifiedAssociation/hadRole(distinguishing "approved by" from "generated by" for sign-off workflows),qualifiedGeneration/Generation,qualifiedUsage/Usage,qualifiedDerivation/Derivation,qualifiedInvalidation/Invalidation,wasAssociatedWith(Activity→Agent),actedOnBehalfOf(Agent→Agent delegation), andwasInformedBy(Activity→Activity, via a newinformed_by=[...]kwarg), alongside the existing plain triples - Bitemporal + Bundle support:
revision_type/supersedes/valid_from/valid_untilfields (plain caller-supplied passthrough, matching the deprecatedkg.ProvenanceTracker's actual contract) plus newrevision_history()andquery_recorded_between()methods, closing the two "no direct equivalent yet" rows indocs/migration/kg-provenance-tracker.md;bundle_idemitsprov:Bundle/hadMembermembership triples to partition provenance by source/dataset/ingestion-run - Configurable, interlinked namespace:
export_prov(base_uri=...)/--base-uriCLI flag, defaulting to a newProvenanceManager.DEFAULT_BASE_URI(https://semantica.dev/ns#) thatRDFExporter'sNamespaceManagerandOWLExporter's defaultontology_urinow both reuse, so KG-exported, OWL-exported, and PROV-exported URIs for the sameentity_idco-resolve instead of three independently-hardcoded placeholder domains - New CLI commands:
semantica provenance invalidate|verify-chain|descendants - Fixed along the way:
track_entities_batch()silently absorbed batch-level typed kwargs (agent_id,entity_type,activity_id) into the opaquemetadataJSON blob instead of forwarding them, so the documented banking example indocs/guides/provenance.mdnever actually worked as written - Fixed along the way:
compute_checksum()had to excludeentity_iditself from the hash —track_entity()'s versioning archives a prior value by copying it to a new key ("X"→"X:v:<timestamp>"), and hashingentity_idmeant that legitimate relabel permanently orphaned any other entry that had already chained itsprevious_checksumfrom the pre-relabel value, surfacing as a false-positive "broken chain." Archival and invalidation are now always a pure relabel (unchanged checksum/sequence position) followed by a fresh chained append, never an in-place mutation of an already-chained entry - Fixed along the way:
InMemoryStorage.get_chain_head()ignored the already-committed chain head whenever the current transaction had staged any entries, understating the head and corrupting the next append's chain link - Fixed along the way: several new
ProvenanceEntryfields were initially wired into the dataclass andexport_prov()but not intoSQLiteStorage's DDL/INSERT/row-mapping —InMemoryStoragestores the dataclass directly so it masked the gap. Added a permanent regression test (test_all_fields_round_trip_through_sqlite) asserting every field survives a SQLite round trip, to catch this class of bug for any future field additions - Flagged, not fixed (separate, pre-existing issues independent of #825):
semantica/pipeline/pipeline_provenance.pyimports a nonexistent module and wraps aPipelinedataclass with norun()method, soPipelineWithProvenancehas never worked; most of the 18 wrapper modules' backing classes are themselves missing or incomplete (e.g.context.context_manager,deduplication.deduplicator,normalize.normalizerdon't exist;EmbeddingGeneratorexists but has no.embed());kg_provenance.pypassesentity_typeinside itsmetadata={}dict instead of as a top-leveltrack_entity()kwarg across most of its ~30 call sites, so it never actually populates the real field - Extensive new test coverage across
tests/provenance/test_manager.py,test_schemas.py, andtest_storage.py(invalidation, hash-chain verification including a simulated hard-delete-detection case and an interleaved-chaining stress test, agent/activity typing, versioning/derivation split, downstream lineage, qualified export triples, bitemporal methods, Bundle export, and namespace interlinking)
- Invalidation instead of hard delete: new
-
Altair Anzo triplet store backend (#813) by @KaifAhmad1
- Added
AnzoStore(semantica/triplet_store/anzo_store.py), a fourth peer toBlazegraphStore/RDF4JStore/JenaStorespeaking plain SPARQL 1.1 over HTTP — no new dependency, since Anzo has no official Python SDK but needs none - The one structural difference from the existing backends: Anzo addresses data by a dataset/graphmart URI (
dataset_uri, required) rather than a short namespace/repository name, so the endpoint path (<endpoint>/sparql/<store_type>/<url-encoded_dataset_uri>) percent-encodes it;store_typedefaults to"graphmart"and can be set to"dataset" - Reuses the shared
sparql_escaping.pyliteral-escaping, datatype-IRI resolution, and CONSTRUCT-detection helpers rather than reimplementing them, matchingBlazegraphStore's CONSTRUCT/bindingsexecute_sparqlcontract exactly - Wired into
TripletStore(backend="anzo", added toSUPPORTED_BACKENDSandNAMED_GRAPH_CAPABLE_BACKENDS) andconfig.py(TRIPLET_STORE_ANZO_ENDPOINTenv var /anzo_endpointconfig key), and exported fromsemantica.triplet_store - 32 new tests in
tests/triplet_store/test_anzo_store.py(mocked HTTP, no live Anzo instance needed), including dataset-URI percent-encoding cases that don't apply to the other backends - Bulk loading uses SPARQL
INSERT DATA(the same approachBlazegraphStoreuses) rather than Anzo's separate HTTP Client Interface, keeping thebulk_load()contract identical across backends
- Added
-
Comprehensive unit and security test suite for the
/api/sparqlExplorer route (#773) by @Sameer6305- Added
tests/explorer/test_sparql_route.py(34 tests) covering the SPARQL Explorer route (semantica/explorer/routes/sparql.py), which executes arbitrary SPARQL queries against an in-memory rdflib projection of the live graph and previously had zero test coverage - Verified read-only allowlist enforcement against write and mutation queries (
INSERT DATA,DELETE DATA,DELETE WHERE,DROP ALL,CLEAR ALL,LOAD,CREATE GRAPH,MODIFY, comments, and multi-statement injections likeSELECT ... ; DROP ALL), confirming rejected queries short-circuit before any graph is built or queried - Verified resource-limiting behavior, confirming row capping (
_SPARQL_MAX_ROWS) truncates results and setstruncated: true, query timeout (_SPARQL_TIMEOUT_S) returns a clean error message without crashing, and concurrency semaphore (_SPARQL_MAX_CONCURRENT) prevents thread starvation under load - Verified RDF projection fidelity for node properties and edge relationships, and error formatting for malformed SPARQL syntax with line and column extraction
- Follow-up review fixes (#805): extracted the duplicated row-cap-and-truncate loop (previously copy-pasted between the
CONSTRUCT/DESCRIBEandSELECTbranches) into a shared_cap_rows()helper so the_SPARQL_MAX_ROWScap is enforced identically by both; addedtest_row_cap_truncates_construct_results, since the truncation path forCONSTRUCT/DESCRIBEresults had no direct test coverage even thoughSELECTtruncation did
- Added
-
Global default persistent storage for
ProvenanceManager, plus a workingprovenanceCLI (#795, #802) by @Sameer6305 and @KaifAhmad1- Every ingestion/processing module (
kg_provenance.py,pipeline_provenance.py, and 20+ other call sites) instantiated its ownProvenanceManager()with nostorage_path, so all of them silently fell back toInMemoryStorageand the SQLite audit trail was never actually written.ProvenanceManager.set_default_storage_path(path)now sets a class-level default that every no-arg instantiation picks up, andSemantica.__init__wiresconfig.provenance.storage_pathinto it automatically during orchestrator init - Added the thread-safe
default_storage_path(path)context manager (semantica.provenance.default_storage_path) for test isolation — it stacks nested overrides and guarantees restoration of the previous default on exit, even on exception, so tests can't leak global state into each other - Fixed
ProvenanceManager.__init__raisingTypeErroron the CLI'sconfig=kwarg, and implemented the four methods the CLI already called but that didn't exist on the class:lineage(),audit_log(),export_prov()(W3C PROV-O turtle/ntriples/jsonld viardflib), andcheck()— unblockingsemantica provenance lineage|audit|export|checkend-to-end - Follow-up review fixes:
track_entityno longer aliases a caller-suppliedused_entitieslist (it copied the reference and later mutated it in place via.append(), which could corrupt a list the caller still held); removed dead fallback branches inorchestrator.py/manager.pyleft over from not realizingConfig.get()already resolves dotted paths; added a--dry-runoption toprovenance auditto matchprovenance export(previously only the global--dry-runflag worked, not a local one); andprovenance check --strictno longer prints a green "✓" success line immediately before failing — a failing check now renders as a warning before theClickExceptionis raised
- Every ingestion/processing module (
-
Markdown round-trip export/import for
AgentMemory(#765, #786) by @SaurabhScripts and @Sameer6305AgentMemory.export(format="markdown")andimport_data(format="markdown")add a human-editable, diff-friendly alternative to the existing JSON/dict serialization: one Markdown file per memory item, withid,created_at,updated_at, andtype/kindin required YAML frontmatter and the memory content as the Markdown body- Exporting without a
destinationreturns a single memory as a Markdown string; exporting a set requires a destination directory and writes one stable, content-hashed filename per memory ID, so re-exporting an unchanged set is byte-for-byte idempotent - Importing upserts by ID: unknown IDs create new memories, known IDs replace them atomically (local state and vector store are only mutated after the whole batch validates cleanly), and unchanged re-imports are a deterministic no-op
- Malformed frontmatter, duplicate IDs within an import batch, and duplicate YAML keys are all rejected before any memory is mutated, with actionable error messages
- Export refuses to overwrite symbolic links and replaces files atomically; import safely compares timezone-aware and timezone-naive timestamps so retention, recency sorting, and date filters stay correct across both
- Entities and relationships round-trip as memory-local provenance only — Markdown import intentionally does not write into
ContextGraph, matching the MVP scope agreed on in #765 - Documented the file contract and workflow in
docs/reference/context.md; 43 new tests intests/context/test_agent_memory_markdown.pycover round-trip losslessness, idempotency, validation errors, rollback on failure, and vector-store sync ordering
Fixed
-
VectorStore.search_vectors()returned inconsistent result shapes across backend implementations (#853, closes #845) by @Sameer6305, reviewed by @KaifAhmad1- Every built-in backend (FAISS, Milvus, pgvector, Pinecone, Qdrant, SQLite-vec, Weaviate, in-memory) now returns the same canonical
SearchResultshape (id,score,metadata,vector,distance), instead of some backends omittingvector/metadata/distanceor, for Weaviate, returning a backend-specificpropertieskey instead ofmetadata - Added a
SearchResultTypedDict(semantica/vector_store/vector_store.py, exported fromsemantica.vector_store) documenting the contract;metadatanow always defaults to{}rather than being absent, andidacceptsUnion[str, int]to accommodate Milvus/Qdrant's native integer IDs without casting - Review fix: the score-normalization formula added for Pinecone and Qdrant (
1.0 / (1.0 + max(0.0, 1.0 - score))) clamped every raw score>= 1.0to an identical1.0, silently collapsing result ranking whenever the raw score could exceed 1 — which happens routinely for dot-product-metric indexes (unbounded), as opposed to cosine (bounded to[-1, 1]). Replaced with(score / (1 + |score|) + 1) / 2, which is strictly monotonic and bounded in(0, 1)for any real input, so ranking order is preserved regardless of metric or vector normalization - Added
test_qdrant_unbounded_dot_product_scores_preserve_rankingandtest_pinecone_unbounded_dotproduct_scores_preserve_ranking(tests/vector_store/test_search_result_schema.py) asserting normalized scores stay strictly ordered and bounded for raw scores well above 1.0, the case the original formula silently collapsed and the existing tests (which only used scores< 1) never exercised - Left out of scope, per the original PR: Weaviate's
similarity_search()still isn't wired intoVectorStore.search_vectors()'s backend dispatch; Milvus's collection schema still has no metadata column so its results always returnmetadata: {}; andinclude_vectorssupport (populating thevectorfield) is not yet implemented for any backend
- Every built-in backend (FAISS, Milvus, pgvector, Pinecone, Qdrant, SQLite-vec, Weaviate, in-memory) now returns the same canonical
-
DecisionEmbeddingPipeline.find_similar_decisions()crashed withAttributeErrorfor anyVectorStorebackend other thaninmemory(#842, closes #839) by @Sameer6305_get_candidate_embeddings()iteratedVectorStore.vectors/VectorStore.metadatadirectly, internal dicts only populated forbackend="inmemory"; every persistent backend (FAISS, Pinecone, Qdrant, Milvus, ...) raisedAttributeError. It now fetches candidates via the backend-agnosticVectorStore.search_vectors(), reading metadata via ares.get("metadata") or res.get("payload")fallback for backends that key it differently- Backends such as FAISS don't return the raw vector for each hit;
find_similar_decisions()and_find_semantic_similar()now fall back to the search-provided score (normalized fromdistancewhen present) as the semantic similarity for those candidates instead of computing cosine similarity against a zero placeholder vector get_decision_statistics()had the identical bug iteratingstore.metadata.values(); it now returns a limited stats payload with an explanatorywarningfield for backends that don't expose a full in-memory metadata dict, instead of crashing- Fixed along the way:
_get_candidate_embeddings()'s expand-and-retry loop (which widens the search pool when post-filtering leaves too few matches) discarded every candidate it had found once the pool hit its cap (limit * 10) without ever collectinglimitmatches or getting a short page back from the backend — the loop fell through without executing the branch that assigns results, silently returning[]even when matching candidates existed. It now falls back to the last batch collected instead of dropping it - Added end-to-end regression tests against real
inmemoryandfaissbackends (no mocks) plus a targeted unit test for the expand-and-retry loop's fallback behavior
-
QdrantStore.search_vectors()returned results keyed by"payload"instead of"metadata"(#841, closes #840) by @divyankshahQdrantCollection.search_points()built its result dicts as{"id", "score", "payload"}, whilePineconeStore.search_vectors()and every other backend consumed byHybridSearchuse"metadata". This silently dropped Qdrant metadata from results and madeHybridSearch.filter_by_metadata()reject every candidate whenever a filter was applied, since it looks upresult["metadata"]and got nothing back- Normalized
search_points()to return"metadata"instead of"payload", matching the existing convention; no other module reads the old key, so the rename is a straight fix rather than a partial one - Extended
tests/vector_store/test_vector_store_deepdive.py::test_qdrant_storeto assert the returned key is"metadata"(not"payload") and thatHybridSearch.filter_by_metadata()correctly matches against Qdrant results end-to-end
-
Explorer Temporal panel never rendered after clicking the toolbar button (#830, #836) by @Sameer6305
- The panel stayed permanently stuck on "Loading temporal…" in
npm run dev, with repeating "Maximum update depth exceeded" errors in the browser console. Two independent render loops were responsible: - Diagnostics state churn:
handleDiagnosticsChangeunconditionally calledsetGraphDiagnosticsStateon every invocation.buildEffectAvailability(insideGraphCanvas's diagnosticsuseEffect) always returns a new object, so each call scheduled a re-render that immediately retriggered the effect. Fixed by comparing the incoming snapshot field-by-field against the last accepted value vialastDiagnosticsRefbefore callingsetState - scrubberTime churn: React 18 concurrent mode re-ran
TimelinePanel'suseEffectwith a structurally-newDateobject for the same timestamp when speculative renders discardeduseMemocaches, causing repeatedsetScrubberTimecalls that propagated intotemporalStatechurn and retriggered the diagnostics effect. Fixed by deduplicating by millisecond value viaonTimeChange/lastScrubberMsRef - Bonus:
temporal-overlay'sshouldLoadpredicate was changed to gate strictly onpanelState["temporal-panel"], removing the|| temporalState?.currentTimebranch that caused eager loading on every scrubber update and continuously cancelled in-flightload()completions - Bonus:
temporalStateremoved from the plugin-loadinguseEffectdependency array; predicates extracted intopluginRegistryPredicates.tsand wired throughGraphWorkspace.tsxso regression tests exercise the production code rather than a local copy - The
scrubberTime-churn fix was also applied to the equivalent (but currently unused/unmounted)GraphWorkspaceShell.tsx, which shares the sameTimelinePanelintegration pattern but does not have the diagnostics-churn code path - Follow-up review fix: the diagnostics dedup's
structureLayercomparison now also coversdisabledReason,curveCount,bridgeCurveCount, andbackboneCurveCount(previously onlycacheKey/lastDrawAt/enabledwere compared, so a puredisabledReasontransition could leave the dev-only diagnostics panel stale) - Follow-up review fix:
test:graph-store,test:graph-workspace, and the newtest:plugin-registryregression test are now run in CI (.github/workflows/ci.yml) — previously none of the Explorer frontend'snode --testsuites executed anywhere in CI, onlynpm run build, so this fix's own regression coverage (and all prior frontend test coverage) provided no protection against silent regressions
- The panel stayed permanently stuck on "Loading temporal…" in
-
HybridSearch.search()crashed withAttributeErrorfor anyVectorStorebackend other thaninmemory(#833, #837) by @KaifAhmad1HybridSearch.search()readself.vector_store.vectorsdirectly, an internal dictVectorStoreonly populates forbackend="inmemory"; every other backend (faiss, weaviate, qdrant, milvus, pinecone, pgvector, sqlite) raisedAttributeError, makingHybridSearchunusable against any real store. It now delegates toVectorStore.search_vectors()(the backend-agnostic public API) for non-inmemory backends, appliesmetadata_filteras a post-filter over the returned candidates, and normalizes results to a consistent{id, score, distance, metadata}shape- Fixed along the way:
vector_idscould stayNonewhen callers passed explicitvectors/metadatawithoutvector_ids, crashing downstream list indexing — now defaulted to generated positional IDs - Fixed along the way: a
query_vectorpassed as a plain list crashed backend stores (e.g.FAISSStore.search_similar) that call.ndimon it — now normalized to a numpy array up front - Fixed along the way:
VectorStore.store_vectors()silently dropped metadata for FAISS (and anyadd_vectors-only backend) because it calledadd_vectors(vectors, **options)without forwardingmetadata, even thoughFAISSStore.add_vectors()accepts it — this blockedHybridSearch's metadata filtering from ever matching anything on FAISS - Follow-up review fixes: the legacy
top_kkwarg was read but left inoptions, then forwarded via**optionsintoVectorStore.search_vectors(), colliding with backends (sqlite, pgvector) that pass an explicittop_k=kto their ownsearch()and raisingTypeError: got multiple values for keyword argument 'top_k'— now popped instead of just read;VectorStore.search_vectors()'s dispatch only recognized backend methods namedsearch/search_similar, so delegation still hitNotImplementedErrorfor qdrant/milvus/pinecone, which name their methodsearch_vectors()with a differently-named count parameter (limitvsk) — added a third dispatch branch that binds the count positionally so it works regardless of the backend's parameter name; a missingdistancein backend-delegated results defaulted to the rawscore, silently reusing the local path's cosine-similarity convention (distance = 1 - score) even for backends using unrelated metrics (L2, inner product) — now left asNoneinstead of a fabricated, metric-inconsistent value - Verified across all 7 supported backends:
inmemory/faiss/sqlitework live end-to-end;pgvector's dispatch reachesPgVectorStore.add()/.search()(blocked only by no Postgres server in the verification sandbox);qdrant/milvus/pineconenow reach their realsearch_vectors()method instead of crashing, though their storage side (store_vectors()) still doesn't recognizeinsert_vectors/upsert_vectors, andweaviateremains entirely unwired (add_objects/query_vectors) on both sides — both are separate, pre-existing gaps independent of this fix, left for a follow-up
-
VectorStore.store_vectors()silently dropped metadata for FAISS (and anyadd_vectors-only) backend (#832, #835) by @KaifAhmad1store_vectors()fell into a branch that calledself._backend_store.add_vectors(vectors, **options)withoutmetadatawhenever the backend exposedadd_vectors()but neitheradd()norstore_vectors()— true forFAISSStore, the backend most real usage configures for genuine ANN search. Every caller that stores vectors with metadata (e.g.AgentMemory._store_memory_vector(), used internally byAgentContext.store()) lost that metadata once it reached FAISS, with no error or warning- Downstream,
ContextRetriever._retrieve_from_vector()recovers a result's text viametadata.get("content", ""), which was always""for any vector stored this way;_rank_and_merge()then embedded that empty string, trippingTextEmbedder.embed_text()'s empty-text rejection and masking the real bug as a spuriousTextEmbedderfailure recorded by the progress tracker store_vectors()now forwardsmetadatatoadd_vectors(), but only when the backend'sadd_vectors()signature actually accepts it (checked viainspect.signature, accepting either an explicitmetadataparameter or a**kwargscatch-all), so a future/custom backend with a stricter signature raises noTypeError- Follow-up review fix: the
inspect.signature()probe is wrapped intry/except (ValueError, TypeError), consistent with the identical pattern already used inProvenanceManager.trace_lineage(), so signature introspection failing on an unusual callable can no longer abortstore_vectors()before it even attempts to call the backend
-
AgnoDecisionKit.check_policysilently treated unevaluable policy rules as compliant (#778, #822) by @Sameer6305_eval_rule()previouslyreturnedTruewhen a rule referenced a field missing from the decision payload, or when the rule string didn't match the expected<field> <op> <value>format — the docstring's claim that exceptions never silently returncompliant=Truedidn't cover this, since neither path raised- Both cases now raise
ValueErrorinstead, which routes throughcheck_policy's existing exception handler and records awarningsentry (e.g."Could not evaluate rule 'minimum_score >= 0.9': rule references undefined field 'minimum_score'") instead of disappearing with no signal violations/compliantare unaffected — an unevaluable rule is not counted as a violation, since it's genuinely unknown whether it would have passed; this matches the existingcompliant/violations/warningsshape already used byContextGraph.enforce_decision_policy- This is additive:
warningswas already part of the return contract and populated for other exception cases, so no caller that only checkscompliantis affected, and no existing test assertswarnings == []for a payload that hits either of these paths - Follow-up review fix:
check_policydecodedpolicy_ruleswithjson.loadsand iterated the result without checking it was actually a list; a JSON-encoded bare string (e.g.policy_rules='"confidence >= 0.7"') decodes to astr, so iterating it evaluated one "rule" per character — combined with the fix above, an 18-character rule string produced 17 warnings instead of being treated as the single rule it was meant to be. A decoded string is now wrapped as a single-element rule list; any other non-list shape (number, object, etc.) or non-string list element now produces exactly onewarningsentry instead of silently misbehaving or being iterated character-by-character - Follow-up review fix:
_eval_ruleuseddata.get(field) is Noneto detect a missing field, which can't distinguish a genuinely absent key from a key explicitly present with a JSONnullvalue — both produced the same "undefined field" warning, misdiagnosing nullable fields. Field presence is now checked withfield not in datafirst; a present-but-nullvalue now raises a distinct"field {field!r} is null — cannot evaluate rule"message instead of the misleading "undefined field" one - Follow-up review fix:
check_policyonly checked thatdecision_datawas valid JSON, not that it decoded to an object. When it decoded to a list,field not in datasilently became list-membership testing instead of a key check (e.g."confidence" not in ["confidence", 0.95]isFalse), so a matching rule fell through todata["confidence"], which raised a raw, confusingTypeError: list indices must be integers or slices, not strinstead of any meaningful diagnostic; numbers/strings/bools produced similarly opaqueTypeErrors.check_policynow rejects anydecision_datathat doesn't decode to a JSON object upfront with a single clearviolationsentry, the same way it already rejects malformed JSON - Added 15 tests to
tests/integrations/agno/test_decision_kit.pycovering the missing-field case (the issue's traced example), the malformed-rule-string case, the bare-JSON-stringpolicy_rulesamplification case, non-list/non-stringpolicy_rulesshapes, the missing-key-vs-null-value distinction, non-objectdecision_datashapes (list/number/string/bool/null), and regression checks confirming normal rule evaluation on present fields is unchanged
-
No cycle detection for SKOS concepts at write time (#774, #819) by @mikemikimike, reviewed by @Sameer6305 and @KaifAhmad1
- Added cycle detection (
validate_skos_hierarchy) forskos:broaderandskos:narrowerrelationships inContextGraph.add_edge()andContextGraph.add_edges(), preventing direct 2-node cycles, self-loops, and multi-hop hierarchy cycles - Added
GraphSession.add_nodes_and_edges()to validate SKOS hierarchy edges upfront under lock before node insertion, preventing partial-write leaks where nodes remain after a cyclic edge is rejected - Updated vocabulary, ontology (
/api/ontology/load,/api/ontology/create), and JSON/CSV import routes to useadd_nodes_and_edges()and return HTTP 422 with actionable error messages when a cycle is detected - Follow-up fix by @KaifAhmad1:
validate_skos_hierarchy()previously re-walked every SKOS hierarchy edge already in the graph on each write, so one pre-existing cycle anywhere (e.g. legacy data) blocked all unrelated future writes; it now only traverses concepts touched by the edges being written, while still checking against existing edges for cycles that span old and new data - Follow-up fix by @KaifAhmad1: in
/api/ontology/load,except HTTPException: raisewas unreachable because a broaderexcept Exceptionclause above it already matchedHTTPException, so a 422 raised after a successfulOntologyIngestorparse was silently swallowed and reprocessed via the fallback RDF parser; reordered the clauses so the deliberate 422 always propagates - Follow-up fix (#775):
/api/ontology/{uri}/refreshwas missed by the original sweep and still calledsession.add_nodes()thensession.add_edges()as two independent operations, so a cyclic SKOS edge rejected byadd_edges()left the nodes from the precedingadd_nodes()call committed to the graph; switched tosession.add_nodes_and_edges()with the sameexcept ValueError→ HTTP 422 handling already used by/api/ontology/loadand/api/ontology/create. Audited every otheradd_nodes()/add_edges()pairing in the repo (GraphStore,graph_builder.py,agent_memory.py,context_graph.py.load(),enrich.py) — none shareGraphSession's SKOS-cycle-validation write path, so none were changed
- Added cycle detection (
-
Agno
_AgentScopedStore.upsert_memorysilently swallowed decision recording failures (#779)upsert_memory()now logslogger.warning("[%s] record_decision failed: %s", self._role, exc, exc_info=True)whenrecord_decision()fails, matching the error-logging convention used forstore()in the same method with traceback context preserved- Preserves graceful fallback behavior:
record_decision()remains optional andupsert_memory()continues without propagating the exception - Added regression coverage in
tests/integrations/agno/test_shared_context.pyfor bothstore()andrecord_decision()warning paths
-
AgnoDecisionKit/AgnoKGToolkitsilently swallowed Agno tool registration failures (#780, #818) by @Sameer6305 and @KaifAhmad1- Removed the
try/except: passwrapped aroundself.register(fn)in both toolkits'__init__; when Agno is installed, a registration failure now propagates immediately instead of leaving the toolkit half-registered with no signal to the caller - Graceful degradation when Agno isn't installed (
AGNO_AVAILABLE=False) is unchanged —_toolsis still populated so callers can introspect available tools without the package - Fixed a related duplicate-entry bug:
self._toolswas appended to unconditionally beforeregister()ran, which could double-count a tool when Agno's ownToolkit.register()also tracks it inself._tools - This is a behavior change for callers that construct these toolkits expecting instantiation to always succeed — audited: no in-repo call site relies on the old silent-failure behavior
- Expanded
tests/integrations/agno/test_decision_kit.pyandtest_kg_toolkit.pywith coverage for registration invocation counts, failure propagation, graceful degradation, and no-duplicate-_toolsassertions
- Removed the
-
ProvenanceManagertracking methods silently swallowed failures without logging and returned fabricated entries (#783)track_relationship(),track_chunk(), andtrack_property_source()now returnOptional[ProvenanceEntry](Noneon storage failure, consistent with #782'strack_entityfix) instead of a fabricated populated object_save_entry()now always logs on any storage failure, including previously-silent per-item batch failurestrack_entities_batch()andtrack_chunks_batch()'s rare block-level transaction failures are now logged toosource_tracker.py'strack_sources_batch()no longer counts failed tracking calls in its stats
-
MCP
handle_get_causal_chainreturned an empty-but-valid-looking response when bothCausalChainAnalyzerand the graph fallback were unavailable (#781, #817) by @Sameer6305 and @KaifAhmad1- Returns an explicit
{"error": "Causal chain analysis is not supported on this graph backend", "chain": []}instead of{"chain": [], "count": 0, "direction": ...}, letting clients distinguish "unsupported" from a legitimately empty chain - The fallback path now introspects
graph.get_causal_chain's signature to forwarddirection/max_depth(or adepthkwarg, or nothing, depending on what the backend accepts) instead of always calling with justdecision_id, matching the primary analyzer path's behavior - Hardened input handling: non-dict
args, non-stringdecision_id(previously a latentAttributeErroron.strip()), andmax_depthclamped to(0, 100]with a safe default on invalid input - Added
tests/test_mcp_decisions_causal_chain.py(11 tests) covering the unsupported-backend, fallback-forwarding, and validation/exception paths across multiple backend signature shapes - Follow-up review fix: the signature-detection try/except previously caught the actual call's exceptions in the same block used for introspection failures, so a genuine bug inside a backend's
get_causal_chain(raising an unrelatedTypeError) was misread as a signature mismatch and the backend was invoked a second time with identical arguments before the real error surfaced. Signature introspection and the resulting call are now split into separate try/excepts so a successfully-introspected call is made exactly once; addedtest_internal_typeerror_calls_backend_only_onceto lock this in
- Returns an explicit
-
ProvenanceManager.track_entitypersisted partial history and returned fabricated entries on storage failure (#782, #816) by @Sameer6305 and @KaifAhmad1track_entity()'s two-step write (history archive + primary update) is now atomic — if either write fails, the whole operation rolls back via the existing #807transaction()mechanism, instead of silently persisting a partial statetrack_entity()'s return type is nowOptional[ProvenanceEntry]: on failure it returns a safe deep copy of the pre-failure existing entry (if one existed) orNone(if this was a brand-new, never-successfully-tracked entity) — never a fabricated object claiming values that were never actually persisted- This is a behavior change for callers that inspect the return value without checking for
Nonefirst — audited: 0 of 47 production call sites in the repo currently dereference the return value, so this is safe today, but any NEW caller must handleNone InMemoryStoragegained real transactional rollback (staging-buffer based) to match this guarantee — previouslytransaction()was a no-op
-
ProvenanceManagerduplicated the same checksum/persist/exception-swallow block across 4 tracking methods (#784, #815) by @Sameer6305 and @KaifAhmad1- Consolidated the repeated
entry.checksum = compute_checksum(entry)/try: self.storage.store(entry) except Exception: passblock used bytrack_entity,track_relationship,track_chunk, andtrack_property_sourceinto a singleProvenanceManager._save_entry()helper, preserving the existing graceful-failure behavior and the batch_conn/re-raise semantics from #807 - Added 4 regression tests (
tests/provenance/test_manager.py) covering storage-failure swallowing for each of the four tracking methods, none of which had coverage for this path before - Follow-up review fix: the initial refactor of
track_entity's exception fallback (the branch that runs when a failure happens before the entry is built, e.g. a retrieve error inside the atomic transaction) routed through_save_entry(), which made a newself.storage.store(entry)call outside the already-failed transaction — a real behavioral change from the original code (which only computed a checksum on that path) that could have reintroduced the exact race #807'sBEGIN IMMEDIATEtransaction serialization was meant to prevent. Reverted that branch to only compute the checksum, and addedtest_track_entity_pre_build_failure_fallback_skips_storeassertingstorage.storeis never called on that path
- Consolidated the repeated
-
SQLiteStorageandProvenanceManagerconnection churn, non-atomic writes, and batch tracking overhead (#807) by @Sameer6305- Scoped a single SQLite connection to the full duration of each public storage method call (
track_entity(),store(),retrieve_all(),clear()) instead of opening independent connections per internal SQL statement, reducing connection churn by ~67% while closing the handle before the public method returns to preserve Windows filesystem unlink safety - Implemented the
SQLiteStorage.transaction()context manager with Write-Ahead Logging (PRAGMA journal_mode=WAL),busy_timeout=5000,synchronous=NORMAL, and immediate write transactions (BEGIN IMMEDIATE), ensuring concurrent read-modify-write sequences (including history version ID generation) are serialized without lock contention or data loss - Added block-level transaction sharing to
track_entities_batch()andtrack_chunks_batch(), reducing SQLite commit overhead by ~99.9% for large batches and deferringtracked_countincrements until successful commit so rolled-back items are never reported as successes - Preserved 100% backward compatibility for custom storage backends overriding
trace_lineage(self, entity_id)by inspecting signatures dynamically before passingmax_depth, and optimized BFS lineage queries with batched IN-clause lookups per frontier level - Follow-up fix:
retrieve()andtrace_lineage()were initially routed throughtransaction()too, so plain reads took the sameBEGIN IMMEDIATEwriter lock as read-modify-write calls, serializing every read behind every other read/write and defeating the WAL concurrency this PR was meant to add. They now use a dedicated_read_connection()(configured, no explicitBEGIN) so reads no longer contend for the writer lock - Follow-up fix:
track_entity()/track_chunk()caught all internal storage exceptions unconditionally, so when called fromtrack_entities_batch()/track_chunks_batch()'s shared per-block transaction, a single item's storage failure (e.g. non-JSON-serializable metadata) was swallowed inside the call and never surfaced to the batch loop's per-itemexcept, inflatingtracked_countfor entries that were never persisted. Both methods now re-raise when invoked with a shared_conn(batch context) while still degrading gracefully on standalone calls, so batch counts match what's actually committed - Added 8 dedicated regression tests in
tests/provenance/test_sqlite_storage_performance_807.pycovering PRAGMA configuration, Windows unlink safety, batch transaction sharing, BFSmax_depth, rollback count accuracy, custom storage backward compatibility, concurrent read-modify-write serialization, and connection cleanup guards on configuration error
- Scoped a single SQLite connection to the full duration of each public storage method call (
-
Closed remaining
ProvenanceManagerstorage-failure test-coverage gaps identified by a #785 audit (#785)- An audit of
tests/provenance/(filed against a claim that zero tests exercisedstorage.store()failures) found #782/#783/#784/#807 had already closed most of the gap, but two residual surfaces had no test:track_relationship(),track_chunk(), andtrack_property_source()'s storage-failure-swallowing contract (returnsNone, logs, persists nothing) was only verified againstInMemoryStorage, neverSQLiteStorage; andtrack_chunks_batch()had no test for per-item_save_entryfailure logging or for the block-level transaction-failure log message, even thoughtrack_entities_batch()had both - No production code changed — #782/#783/#784/#807 already implemented the correct behavior; this closes the coverage gap proving it holds on both backends
- Added
test_track_relationship_storage_error_swallowed_sqlite,test_track_chunk_storage_error_swallowed_sqlite,test_track_property_source_storage_error_swallowed_sqlite,test_chunks_batch_logs_per_item_failure_memory, andtest_track_chunks_batch_block_level_transaction_failure_logstotests/provenance/test_manager.py - Read-path failure coverage (
get_lineage()/trace_lineage()/get_provenance()/clear()propagating a raised storage exception) remains untested and is a candidate for a follow-up issue, since none of those methods currently wrap the underlying storage call in a try/except
- An audit of
-
Explorer's Provenance UI used a naive 2-hop graph traversal instead of the audit-grade
ProvenanceManagerbackend (#792, #809) by @Sameer6305semantica/explorer/routes/provenance.pynever imported or calledProvenanceManager(semantica/provenance/manager.py);/api/provenanceand/api/provenance/reportbuilt their lineage response entirely from a naive 2-hop networkx traversal over the live graph instead of querying the SQLite-backed, checksummed audit log. Both endpoints now querysession.provenance_manager.get_lineage(node_id)first, and a new_transform_audit_lineage()maps the W3C PROV-O entries into the exact{"nodes": [...], "edges": [...]}shapeLineageDiagram.tsxalready expects — no frontend changes required- Falls back to the original 2-hop traversal, never a 500: no audit records for a node, a
ProvenanceManagerstorage failure (corrupted DB, permissions), or a failed SHA-256 integrity check on any entry in the lineage chain all degrade cleanly to the naive path. A newsource: "audit" | "graph_traversal"field on the response discloses which path actually served the data ProvenanceManager.get_lineage()now returnsintegrity_verified, computed by re-verifying every entry's checksum before it's trusted; a single tampered or corrupted entry anywhere in the lineage chain now falls the entire response back to graph traversal rather than serving partially-verified audit data- Replaced an initial classmethod-based
ProvenanceManager.set_default_storage_path()approach (caught in review before merge — it would have let any two sessions/apps in the same process silently share and overwrite each other's storage path, including across unrelated test runs) withprovenance_storage_paththreaded throughGraphSession.__init__andcreate_app(...), so each session'sProvenanceManageris independently scoped - Disclosed limitation:
ProvenanceManager.trace_lineage()/get_lineage()only walkparent_entity_id/used_entitiesbackward, so the audit path currently surfaces upstream lineage only — the naive fallback remains the only source for downstream/descendant relationships untilProvenanceManagergains a reverse lookup - New
tests/explorer/test_provenance_manager_wiring.py(8 tests): the audit path via a real multi-hoptrack_entity()chain, empty-record fallback, simulated storage-failure degradation (asserts200, not500), checksum-tamper fallback, evidence-field preservation,create_app()storage-path wiring, and cross-session storage isolation, confirmed order-invariant acrosstests/explorer/andtests/provenance/in both execution orders
-
POST /shacl/validateand the/healthSHACL dimension never ran live SHACL validation (#772, #804) by @Sameer6305 and @KaifAhmad1/shacl/validatehad no data graph to validate submitted shapes against — only a Turtle syntax check. Added_data_graph_turtle_for_uri(), which serializes the loaded ontology's nodes/edges into an RDF/Turtle instance graph (CURIE resolution across owl/rdfs/skos/dct/dc, arbitrary node-property projection, typed individuals) and wires both/shacl/validateand the/healthSHACL dimension toOntologyEngine.validate_graph()via pySHACL, returning realconforms/violations instead of a hardcodedstatus="unavailable"stub- Fixed a cross-ontology namespace leak in
_node_belongs_to_ontology: its prefix fallback (_extract_namespace()) split only on the last/, so sibling ontologies sharing a domain (e.g..../onto-aand.../onto-b) could match entities across ontologies that shouldn't be related; fixed by comparing against the full URI stem via the new_ontology_namespace()helper - Added resource guardrails to
/shacl/validateto close a DoS risk flagged in review: a submitted-Turtle byte cap (SEMANTICA_MAX_SHACL_TURTLE_BYTES, default 256 KB), a parsed-triple cap (SEMANTICA_MAX_SHACL_TRIPLES, default 1,000), a validation timeout (SEMANTICA_MAX_SHACL_TIMEOUT, default 15s), and a global concurrency semaphore (SEMANTICA_MAX_SHACL_CONCURRENCY, default 4) - Fixed
HealthDimension.statusbeing set to"error"on a real (non-ImportError) validation exception, which isn't a valid value on that model — Pydantic construction raised and turned the whole/healthendpoint into a 422 on any real bug; now reportsstatus="critical"(already a valid value) with a regression test forcing this exact path - Follow-up review fixes: reverted an unrelated regression that had crept into this PR —
POST /api/ontology/createhad gone back to silently swallowingOntologyEngine.from_data/from_textfailures into a near-empty "minimal" ontology instead of raisingHTTPException(500), undoing the earlier #770/#787 fix for the same endpoint (and breakingTestOntologyCreateFailures, which wasn't run before this PR's initial merge request);sh:Warning/sh:Info-severity pySHACL results were silently dropped from the/shacl/validateresponse — a shape using non-Violationseverities could reportconforms=Falsewith an emptyviolationslist and no explanation, so warnings/infos are now folded into the response'sviolationsarray; and/healthwas independently re-fetching and re-truncation-checking the same ontology's nodes/edges once for the generated SHACL shapes and once for the data graph — both now share a single fetch via_fetch_analysis_graph() - New regression tests:
TestOntologyCreateFailures(pre-existing, now passing again),test_shacl_validate_surfaces_warning_severity_results,test_health_dedupes_node_edge_fetch, plus the existing 26-testtests/explorer/test_ontology_subissue3.pysuite (28/28 passing) and the pre-existingtests/ontology/suite (83/83 passing)
-
Neptune cookbook CloudFormation stack exposed the database port to the entire internet and had no network audit trail (code scanning alert #28, #26, #27,
AC_AWS_0276/AC_AWS_0369/AC_AWS_0148) by @KaifAhmad1cookbook/introduction/neptune-setup.yaml's security group let anyone on0.0.0.0/0reach the Neptune Bolt/OpenCypher port (8182); it now requires aClientCidrparameter (CIDR-validated, no default) so the stack can't be created without the deployer explicitly scoping access to their own IP or VPN/office range- Added
AWS::EC2::FlowLogplus a dedicated CloudWatch Logs group and IAM role so all traffic in the stack's VPC is now logged - Left the account-wide IAM password policy check (
AC_AWS_0148) unimplemented as a stack resource on purpose:AWS::IAM::AccountPasswordPolicyis an account singleton, and wiring it into a disposable per-learner tutorial stack would mean creating or deleting this stack also mutates or removes the account's real password policy — suppressed with a documentedts:skip=AC_AWS_0148explaining why, rather than "fixed" - Updated
21_Amazon_Neptune_Store.ipynb'saws cloudformation create-stackinstructions, prerequisites, and cost table to match the new requiredClientCidrparameter and flow-log line item
-
Follow-up to the knowledge-explorer Helm chart default-namespace/seccomp scanner findings reopening (code scanning alert #846, #847, #848, #68, #63,
CKV_K8S_21/AC_K8S_0086/AC_K8S_0080) by @KaifAhmad1- The
checkov.io/skip1metadata annotation added previously (see theCKV_K8S_21entry below) evidently isn't being honored by the Microsoft Defender for DevOps scan — the same finding reopened under new alert numbers on the currentmain. Added the more standard# checkov:skip=CKV_K8S_21and# ts:skip=AC_K8S_0086inline comments at the top oftemplates/deployment.yaml,templates/service.yaml, andtemplates/configmap.yamlas a second suppression path (matching the convention already used indeploy/gcp/cloudrun-service.yaml), plus# ts:skip=AC_K8S_0080ontemplates/deployment.yamlfor the seccomp finding, which trips for the same root cause: terrascan's static template scan never resolves{{ toYaml .Values.podSecurityContext }}, even thoughvalues.yamlsetsseccompProfile.type: RuntimeDefaultcorrectly - Confirmed the
deploy/kubernetes/*(non-Helm) manifests already had TLS and seccomp configured correctly, so no code change was needed there for the corresponding alerts (#61 and the non-Helm seccomp finding) — expected to close on the next scan - Documented both suppression mechanisms and the reasoning in
.checkov.yaml - Residual risk: this environment could not run checkov/terrascan locally to confirm the inline comments are actually honored during a Helm-rendered scan; if the alerts are still open after the next scan, the reliable fallback is splitting the CI checkov/terrascan invocation so
deploy/helm/is scanned with these specific checks excluded via--skip-checkinstead of relying on in-file suppression
- The
-
react-hooks/set-state-in-effectcascading renders across 12 Explorer workspace files (#769, #796) by @Sameer6305 and @KaifAhmad1- Replaced synchronous
setStatecalls insideuseEffectbodies with React's recommended "adjust state during render" pattern (if (x !== prevX) { setPrevX(x); ...setState... }) acrossOntologyWorkspace,ManageWorkspace,LineageWorkspace, andGraphWorkspace, and inlined async data-fetching effects withignoreflags to prevent race conditions and stale writes after unmount - Fixed a regression the inlining itself introduced:
AlignmentsTab.tsx,KGOverviewTab.tsx,OntologyManager.tsx, andVersionsTab.tsxeach duplicated their existing fetch callback (reload/fetchOverview/fetchRegistry/loadVersions+loadProposals) into a second, inline copy for the mount effect, and the copy silently dropped thesetError/flashMsgcalls the original had — re-introducing, on the very first page load, the exact error-swallowing behavior that #767/#790 had already fixed for these same files. The inline copies now mirror the original's error handling (including207partial-success messages) exactly - Fixed
LineageDiagram.tsxonly clearing the previously-rendered nodes/edges when the newactiveIdwas falsy instead of on every id change, so switching directly between two lineage views briefly kept showing the previous view's stale diagram instead of clearing before the new fetch resolved GraphWorkspace.tsxandGraphLoadingOverlay.tsxstill have unrelatedreact-hooks/set-state-in-effectviolations outside this PR's 12-file scope (confirmed vianpx eslint .); left as follow-up work rather than expanding this PR further
- Replaced synchronous
-
Checkov flagged the knowledge-explorer Helm chart for using the default Kubernetes namespace (code scanning alert #779, #778, #777,
CKV_K8S_21) by @KaifAhmad1templates/service.yaml,templates/deployment.yaml, andtemplates/configmap.yamlall already setmetadata.namespaceto{{ .Release.Namespace }}, which is only bound athelm install/helm templatetime; Checkov's helm framework renders the chart without a namespace override, so it always resolves todefaultand tripsCKV_K8S_21even though the chart is namespace-agnostic by design- Added a
checkov.io/skip1: CKV_K8S_21metadata annotation to each of the three files to suppress the scanner artifact false-positive properly in Helm templates, and documented the reasoning in.checkov.yaml
-
No React error boundaries around lazy-loaded Explorer workspaces — a single render error crashed the whole app (#768, #794) by @Sameer6305
- Added an
ErrorBoundaryclass component (explorer/src/ErrorBoundary.tsx) and wrapped each lazy-loaded workspace's<Suspense>block inApp.tsxwith it, keyed on the active sub-view so navigating away from and back to a crashed tab remounts it cleanly - Failed retries are capped at 3 before the fallback UI switches from "Try Again" to a "Reload Application" dead-end, preventing infinite retry loops on deterministic crashes; raw error/stack details are logged via
console.erroronly and never rendered into the fallback UI - Fixed the retry counter so it resets after a retry actually succeeds and stays error-free for a few seconds, instead of never resetting (which could permanently exhaust the retry budget on unrelated, individually-recoverable transient errors) or resetting on the very next commit (which could fire prematurely while
Suspensewas still showing its fallback)
- Added an
-
Explorer frontend workspaces silently swallowed network/server errors (#767, #790) by @Sameer6305
ShaclStudio.tsx,VersionsTab.tsx,SKOSVocabularyManager.tsx,EntityResolutionTab.tsx,LineageDiagram.tsx,DecisionWorkspace.tsx,KGOverviewTab.tsx,OntologyManager.tsx,OntologySearch.tsx,ReasoningWorkspace.tsx, andSparqlWorkspace.tsxnow render a visible error banner instead of onlyconsole.error()-ing failed fetches- Added explicit
response.status === 207(Multi-Status) handling across these workspaces so partial backend failures surface a warning instead of reading as a full success (response.okistruefor all 2xx codes, including 207) - Added defensive JSON parsing so an unexpected non-JSON (e.g. HTML 500) response body no longer crashes the app with
SyntaxError: Unexpected token < in JSON - Fixed
KGOverviewTab.tsxdropping the/api/graph/nodespartial-success warning whenever/api/graph/statsalso returned 207 — both warnings are now shown (appended) instead of one being silently discarded - Fixed
HealthTab.tsx's registry load still using a bare.catch(() => {})that swallowed errors identically to the pattern fixed elsewhere in this same folder; failures now populate the existing error banner - Fixed
AlignmentsTab.tsx'sreload()usingPromise.allSettledbut never handling the"rejected"branches for the registry/alignments fetches, so both failures previously vanished with no error surfaced and no logging
-
tests/explorer/test_explorer_api.pyfailed withTypeError: Client.__init__() got an unexpected keyword argument 'app'on current httpx (#788, #789) by @Sameer6305httpx>=0.28.0removed theapp=kwarg that Starlette'sTestClientrelies on to wrap a FastAPI app for testing;httpxwasn't pinned anywhere inpyproject.toml, so different environments could independently resolve an incompatible transitive version and hit the same break- Added an explicit
httpx<0.28.0constraint to the main[project.dependencies]array (not just a dev extra), so it applies globally across production, dev, and CI installs - Without the pin, the full test suite fails to even complete collection (fails immediately on
tests/explorer/test_vocabulary.pywith the sameTestClienterror); with it,tests/explorer/test_explorer_api.pygoes from 7 failed/12 passed/58 errors to 77 passed, 0 errors
-
Explorer backend routes returned HTTP 200 with error/empty bodies on failure, defeating frontend error handling (#770, #787) by @Sameer6305 and @KaifAhmad1
GET /api/temporal/patternsnow raisesHTTPException(500)on a genuine computation failure instead of silently returning an empty-but-validTemporalPatternResponse; theImportErrorfallback (optionalkgextra not installed) is unchanged and still degrades gracefully to an empty listPOST /api/ontology/createnow raisesHTTPException(500)when ontology generation fails in either thesample_dataorschema_textmode, instead of silently falling back to a partial/minimal ontology with a misleadingnodes_addedcountGET /api/analyticssetsresponse.status_code = 207(Multi-Status) when some, but not all, of the requested metrics fail, and raisesHTTPException(500)when every requested metric fails — a plain 2xx (including 207) reads as success to callers that only checkresponse.ok, so an all-failed request now surfaces as a hard error rather than a body full of{"error": ...}- Added regression tests covering all three failure paths (
test_patterns_failure_returns_500,test_analytics_partial_failure_returns_207,test_analytics_total_failure_returns_500, and twoTestOntologyCreateFailurescases)
Security
-
Cypher injection via unvalidated node labels, relationship types, and property keys (#910, GHSA-482h-hw99-h62p) by @KaifAhmad1
- Node labels and property keys passed to
create_node/create_relationshipwere interpolated directly into Cypher strings in the Neptune, Neo4j, and FalkorDB graph stores. Property values are parameterized, but labels and keys can't be bound as query parameters, and nothing validated them — so a document-derived entity type or property name (the normal ingest path) could close the current Cypher token early and append arbitrary statements (e.g.DETACH DELETE), running with the application's database credentials - New shared
semantica/graph_store/query_sanitize.py:sanitize_identifier()generalizesage_store.py's existing_sanitize_label/_sanitize_rel_type(the only backend that already validated this) into a helper the other backends import without an import cycle withgraph_store.py/methods.py - Applied at every label/relationship-type/property-key interpolation site in
amazon_neptune.py,neo4j_store.py,falkordb_store.py,graph_store.py(degree_centrality's own query builder), andmethods.py(update_relationship's own query builder) — coverscreate_node,create_nodes,create_relationship,get_nodes,get_relationships,get_neighbors,shortest_path,update_node,create_index, and all relationship-type filters across the three backends - Fixed along the way (caught in review, by @Sameer6305):
depth/max_depthpath-length parameters are meant to be integers, butNeo4jStore.get_neighbors()/shortest_path()interpolated them into the Cypher variable-length-path syntax (*1..{depth}) without coercion — unlike the Neptune/FalkorDB equivalents, which already cast toint(). A stringdepth(e.g."1]->(x) DETACH DELETE x //") reached the query verbatim. Added the sameint()coercion Neptune/FalkorDB already had, plusGraphStore.get_neighbors()'shops/depthalias resolution - New
tests/graph_store/test_cypher_injection.py(unit tests onsanitize_identifierplus the labels/keys/rel-types injection payload run against Neptune/Neo4j/FalkorDBcreate_node/create_relationship, asserting the malicious query is never built or sent) and the depth-coercion regression above; plus additions totests/test_graph_store.py(degree_centrality) andtests/test_graph_store_methods.py(update_relationship). Full graph_store suite: 224+ tests passing
- Node labels and property keys passed to
-
4 critical/high vulnerabilities in the Explorer API and vector store: RCE, SSRF, XXE, and DoS, plus Cypher/SPARQL injection hardening found along the way (#898) by @Sunil56224972
- [CWE-502] Arbitrary code execution via
pickle.load():VectorStore.save()/load()usedpicklefor the on-diskstore_data.pkl; a crafted.pklfile placed in the store directory (file upload, shared filesystem, or supply-chain compromise) could execute arbitrary code on deserialization. Replaced with JSON — vectors and metadata are fully JSON-serializable, so nothing is lost — andload()now refuses any legacy.pklfile it finds with a migration error rather than deserializing it - [CWE-918] SSRF via redirect bypass in
ontology.py's URL fetcher:_validate_fetch_url()correctly blocked private/loopback/reserved addresses on the caller-supplied URL, but_fetch_url_sync()fetched withallow_redirects=True, so a validated public first hop could 302 tohttp://169.254.169.254/...(cloud instance metadata) or an internal service, andrequestsfollowed it with no re-check. Redirects are now followed manually, capped at 5 hops, with_validate_fetch_url()re-run against every hop's target — including relativeLocationheaders, resolved viaurljoin()before validation — and every response (redirect or final) is explicitly closed to avoid leaking connections back to the pool - [CWE-611] XXE injection in the RDF/XML parser:
_safe_parse_rdf()depended ondefusedxmlfor XXE protection, butdefusedxmlwasn't declared inpyproject.toml'sexplorerextra, so it was silently absent in normal installs and the code fell back to a bare warning plus unsafe parsing — a crafted RDF/XML ontology with an external entity could read arbitrary server files. Addeddefusedxml>=0.7.1to the extra, and_safe_parse_rdf()now fails closed: it raises rather than parsing untrusted RDF/XML ifdefusedxmlisn't importable, replacing an earlier regex-based DOCTYPE-stripping fallback that was reviewed and rejected as bypassable - [CWE-770] DoS via unbounded SPARQL graph materialization:
_build_rdflib_graph()loaded up to 999,999 nodes and 999,999 edges into memory per query, and with up to 4 concurrent SPARQL requests permitted, an attacker could exhaust server memory. Added a 50,000 node/edge cap (_SPARQL_MAX_GRAPH_NODES); oversized graphs now return a clean error instead of attempting materialization - Cypher injection via Apache AGE's
graph_nameand$$-delimiter breakout:graph_namewas interpolated unvalidated intocypher('{graph_name}', $$ ... $$), and raw Cypher query text containing$$could close AGE's dollar-quoted string delimiter early and append arbitrary SQL.graph_nameis now validated against the same identifier allowlistage_store.pyalready used for labels/relationship types, and any query containing$$is rejected outright - SPARQL Explorer route (
/api/sparql) hardened against comment/PREFIX-hiding bypass:_is_read_only_query()now strips comments and PREFIX/BASE declarations before checking the leading keyword, and additionally scans the full query body for SPARQL Update keywords (INSERT/DELETE/DROP/LOAD/CLEAR/CREATE/COPY/MOVE/ADD) — soSELECT ... ; DROP ALLis now rejected by the keyword scan itself rather than relying solely on rdflib's parser - Fixed along the way (maintainer follow-up, addressing automated review findings and a regression introduced across several rounds of iteration on the original fix):
VectorStore.save()'s numpy handling usedlist(v)for the JSON fallback path, which producesnumpy.float32elements thatjson.dump()can't serialize — changed tov.tolist()- the SPARQL graph-size
ValueErrorwas raised outsideexecute_sparql()'s exception handling and surfaced as an unhandled 500 instead of a clean API error — moved inside - every streamed
requestsresponse in the ontology redirect loop, including the one actually read and returned, is now closed in afinallyblock — a connection-pool leak that a rework of the redirect logic had briefly reintroduced after an earlier fix - a later commit meant to add opt-in API-key auth (
explorer/auth.py, gated onEXPLORER_API_KEY) instead replaced and silently disabled theDepends(require_auth)enforcement already merged intomainfor GHSA-j4mq-hprp-987v (Critical — unauthenticated Explorer API), removed the/ws/graph-updateshandshake check, and — unlikerequire_auth— failed open (allowed all requests) whenever its key was unset. Merging that version would have silently reverted an already-fixed Critical CVE the moment this branch landed. Removedexplorer/auth.py; restored the per-routerDepends(require_auth)wiring and the WebSocket auth check; kept the one genuine improvement in that commit (addingX-API-Keyto the CORSallow_headerslist) by folding it into the existing CORS config - the new SPARQL keyword-scan's comment-stripping regex (
#[^\n]*) also matched the#inside standard RDF namespace IRIs (e.g..../1999/02/22-rdf-syntax-ns#), corrupting any query with a normalrdf:/rdfs:-stylePREFIXdeclaration — caught because the hardening's own bundled tests failed against two of its own cases. Fixed by only treating#as a comment-start at line-start or after whitespace; the companionPREFIX/BASEregex was also fixed to accept bareBASE <...>declarations, which have no prefix-name token between the keyword and the IRI
- New/updated regression tests:
tests/explorer/test_ontology_ssrf.py(redirect re-validation, relative-redirect resolution, response closing, redirect-cap enforcement),tests/test_security_regression.py(Cypher/SPARQL injection, XXE, numpy serialization, SSRF redirect handling), plus additions totests/explorer/test_sparql_route.py,tests/vector_store/test_vector_store.py, andtests/explorer/test_explorer_auth.py - Note: the Cypher-injection hardening here is scoped to
age_store.py'sgraph_name/$$breakout, found while reviewing this PR. The broader label/property-key/relationship-type injection across the Neptune, Neo4j, and FalkorDB backends (GHSA-482h-hw99-h62p, #910) and the triplet-store SPARQL injection across Blazegraph/RDF4J/Jena (GHSA-8vgg-8mr4-r236, #911) are covered by separate, still-open PRs, as is the unauthenticated-Explorer-API fix referenced above (GHSA-j4mq-hprp-987v, #909, already merged)
- [CWE-502] Arbitrary code execution via
-
CI/CD supply-chain hardening against mutable-tag Action compromise (LiteLLM/Trivy-class attack) (#824) by @KaifAhmad1
- Every third-party GitHub Action across all 8 workflows is now pinned to a full commit SHA instead of a mutable tag (
@v7→@3d3c42e... # v7), closing the exact vector used against LiteLLM in March 2026 (a compromised Trivy Action tag stole a long-lived publishing token) - Added
verify-action-pins.yml+.github/scripts/verify-action-pins.sh: a CI check that fails closed on anyuses:reference that isn't a full SHA (catching a newly introduced mutable tag, not just auditing existing pins) and re-verifies every pin against the GitHub API on each workflow change, on push tomain, and weekly; an unresolvable API lookup is treated as a failure rather than a silent skip release.yml: scopedpermissionsto the job level (workflow default is nowcontents: read), added aconcurrencygroup so simultaneous tag pushes can't race the publish job, and added SLSA build provenance attestation (actions/attest-build-provenance) for every released wheel- Created a protected
pypiGitHub Environment (required reviewer, restricted tov*tag deployments) and enabled branch protection onmain(required PR review with stale-approval dismissal, required status checks, no force-push/deletion, required conversation resolution) — PyPI publishing already used Trusted Publishing (OIDC) with no long-lived token - Grouped Dependabot's
github-actionsupdates into a single PR
- Every third-party GitHub Action across all 8 workflows is now pinned to a full commit SHA instead of a mutable tag (
-
security-scan.yml's Safety dependency-vulnerability check was silently non-functional (#824) by @KaifAhmad1safety check --json --output safety-report.jsonis invalid in Safety 3.x (--outputnow selects a console format, not a file path); the command errored on every run, swallowed by|| true, so no report was ever produced and the job always fell back to a generic "scan completed" message with the vulnerability count hardcoded to 0- Switched to
--save-json, the correct flag for writing a JSON report to disk; also fixedvuln.package→vuln.package_nameand Semgrep'sissue.rule_id→issue.check_id(both producedundefinedin the PR comment) - The job never installed Semantica's own dependencies before scanning, so Safety was auditing the scanner tools' own transitive deps, not the project's; added
pip install -e ".[llm-litellm]"so the actual dependency tree — including the LiteLLM extra — is what gets scanned - Rewrote the PR-comment builder: every line previously used
\\ninside JS template literals, which renders as the literal text\nrather than a newline, producing an unreadable wall of text; now builds real line arrays and collapses long finding lists into a<details>block - Added the
pull-requests: writepermission the comment-posting step was missing (silently failing via its own try/catch on every prior run)
-
pypdf2==3.0.1removed (CVE-2023-36464) (#824) by @KaifAhmad1- Surfaced by the Safety fix above: PyPDF2 is a discontinued project (merged into
pypdf) permanently frozen at the vulnerable 3.0.1 with no patched release possible.grep -rn "import PyPDF2"found zero real usages anywhere in the codebase — it was only referenced in docstrings describing aPyPDF2.PdfReader()fallback for PDF parsing that was never actually implemented (pdfplumberdoes the real work). Removed the dependency and corrected the stale docstrings inparse/__init__.py,parse/methods.py,parse/pdf_parser.py, andingest/email_ingestor.py
- Surfaced by the Safety fix above: PyPDF2 is a discontinued project (merged into
-
10 Bandit B324 false positives suppressed (non-cryptographic MD5 use) (#824) by @KaifAhmad1
- Surfaced by the same Safety fix restoring a working CI gate: Bandit's HIGH-severity check was blocking on 10 pre-existing
hashlib.md5()calls, all generating short deterministic cache keys, entity IDs, or IRI suffixes from non-secret input — none used for passwords, tokens, or verifying untrusted data - Bandit's own message suggests
usedforsecurity=False, but that keyword argument needs Python 3.9+ andpyproject.tomldeclaresrequires-python = ">=3.8"; used a targeted# nosec B324with a one-line justification instead, which suppresses only this check with no runtime behavior change on any supported Python version
- Surfaced by the same Safety fix restoring a working CI gate: Bandit's HIGH-severity check was blocking on 10 pre-existing
[0.6.0] - 2026-07-21
Added
-
Named-graph support for
JenaStoreviaDatasetmigration (#756, #757) by @Sameer6305 and @KaifAhmad1JenaStorenow backs ontordflib.Dataset(default_union=False)instead ofrdflib.Graph, closing #756 and fully closing out the #754/#756 cross-backend named-graph parity effort across Blazegraph, RDF4J, and Jenadefault_union=Falseis explicitly set so existingexecute_sparql()/get_triplets()calls that don't passgraph=keep seeing only the default graph, not a union across all named graphsadd_triplets()accepts agraph=option: when supplied, triples are written to that named graph (4-tuple add viaDataset.graph(uri)); when omitted, behavior is unchanged (3-tuple add routes to the default graph)- Fixed a pre-existing bug where the remote-endpoint path instantiated the read-only rdflib
SPARQLStoreinstead ofSPARQLUpdateStore, so everyadd_triplets()call against a remote Fuseki endpoint silently failed (TypeErrorswallowed,success=True/added=0returned); also fixed a constructor bug whereself.endpointwas alwaysNoneregardless of howJenaStorewas called, making the remote path unreachable in practice serialize()now logs a warning instead of silently dropping named-graph content when the requested format (turtle,xml,n3, …) can only serialize the default graph; useformat="trig"orformat="nquads"to include all graphscreate_model()'striplet_countnow documented as counting across all graphs (default + named), not just the default graph, matching theDataset-wide semanticsdelete_triplet()remains scoped to the default graph only (named-graph parity for delete is an explicit follow-up, matching the maintainer's scoping of this migration toadd_triplets); the removal is passedself.graph.default_graphexplicitly as its context, sinceDataset.remove()on a bare 3-tuple resolves to a wildcard context internally and would otherwise delete matching triples out of every named graph too — a follow-up fix to the initial PR #757 for a bug that had no test coverage- 9 new tests covering
Datasetconstruction,default_union=Falseconfirmation, named-graph write isolation,serialize()warning behavior, anddelete_triplet()'s default-graph scoping
-
SPARQL CONSTRUCT query templates (#752, #322, #755, #754) by @Sameer6305
- Added parameterized, injection-safe
CONSTRUCTtemplates (ConstructTemplate,ParameterDescriptor,ConstructTemplateRegistry) - Extended CONSTRUCT execution support from Blazegraph-only to the RDF4J and Jena backends (#755), closing #754
RDF4JStore.execute_sparqlgains a CONSTRUCT-aware path (Accept: text/turtle, rdflib Turtle parsing, the same(s, p, o, metadata)4-tuple contract) and named-graph writes via RDF4J's RESTcontextparameterJenaStore.execute_sparqlgains the equivalent CONSTRUCT-aware path over its in-processrdflib.Graph_CONSTRUCT_QUERY_REmoved tosparql_escaping.pyas a shared, backend-agnostic constant used by all three backends
- Added pipeline integration via the
construct_templatestep type
- Added parameterized, injection-safe
-
Databricks Connector (Unity Catalog + Delta Lake ingestion) (#747) by @KaifAhmad1
- Added
DatabricksIngestor(semantica/ingest/databricks_ingestor.py), mirroringSnowflakeIngestor's structure and public API shape: aDatabricksConnectorconnection handler, aDatabricksDatadataclass, and an optional-import guard fordatabricks-sdk/databricks-sql-connector - Supports personal access token and OAuth M2M (service principal
client_id/client_secret) authentication, configurable via constructor args orDATABRICKS_*environment variables ingest_table()/ingest_query()run against a SQL warehouse or cluster viadatabricks-sql-connector, withwhere/order_by/limit/offsetsupport and the same identifier-escaping and unsafe-ORDER BYrejection asSnowflakeIngestor; each call closes the SQL connection it opened unless one is already open (e.g. via thewith DatabricksIngestor(...)context manager), which reuses and closes it exactly once instead of leaking a second connection per callget_table_schema(),list_catalogs(),list_schemas(), andlist_tables()introspect Unity Catalog viadatabricks-sdk'sWorkspaceClient, validating both catalog and schema are resolved before calling the SDK;get_table_lineage()calls Unity Catalog's table-lineage REST API for upstream/downstreamTable --DEPENDS_ON--> Tabledependencies, plus an opt-ininclude_column_lineage=Truethat resolves per-column lineage via the column-lineage APIexport_as_documents()converts ingested rows into Semantica document dicts for KG construction, matchingSnowflakeIngestor.export_as_documents()'s shape- Registered as a lazy export in
semantica.ingest(DatabricksIngestor,DatabricksData,DatabricksConnector) and as thedb-databricksoptional extra (pip install "semantica[db-databricks]") inpyproject.toml, included indb-all - New
docs/integrations/databricks.mdpage modeled ondocs/integrations/snowflake.md, plus aDatabricksIngestorsection and table row indocs/reference/ingest.mdand cross-links between the two integration pages - 35 unit tests in
tests/test_databricks_ingestor.pycovering both auth methods, table/query ingestion, connection lifecycle (including reuse under the context manager), pagination, unsafeORDER BYrejection, catalog/schema validation, schema/catalog/table listing, table and column lineage, document export, and the missing-dependency error path, closing #747
- Added
-
SQLite Vector Store Backend (
sqlite-vec) (#726) by @Luffy2208 and @KaifAhmad1- Added
SQLiteVecStore(semantica/vector_store/sqlite_vec_store.py), a disk-backed local vector store using thesqlite-vecextension'svec0virtual tables, closing #240 - Supports Cosine and L2 distance metrics, dynamic JSON metadata filtering, read-only mode, and an in-memory (
:memory:) mode - Registered as the
"sqlite"backend inVectorStore.SUPPORTED_BACKENDS, withdb_path/sqlite_pathconfig and aVECTOR_STORE_SQLITE_PATHenvironment variable - Batched
add/delete/getandexecutemany-basedupdateto avoid per-row round trips; optionaluse_wal=Trueenablesjournal_mode=WAL+synchronous=NORMALfor improved write concurrency - Lazy-imports
sqlite-vecso the dependency stays fully optional (pip install semantica[vectorstore-sqlite]); table names and metadata filter keys are validated against a strict identifier pattern before SQL interpolation - Fixes
VectorStore.update_vectors/delete_vectorsto delegate to the active backend store instead of only mutating in-memory state, correcting existing behavior for all non-inmemorybackends - 25 unit and integration tests in
tests/vector_store/test_sqlite_vec_store.pycovering init, add, search, get, update, delete, read-only mode, and stats
- Added
Fixed
-
kg.ProvenanceTrackercompatibility wrapper out of sync withProvenanceManager, causing 9 pre-existing test failures (#744, #751) by @Sameer6305 and @KaifAhmad1kg.ProvenanceTrackerwas a standalone in-memory implementation that never delegated to the unifiedProvenanceManagerbackend; its own test suite asserted the existence ofget_lineage,track_relationship,track_entities_batch,get_provenance, and_use_unified, none of which were ever implemented, plus a staleget_all_sources()assertion expecting"timestamp"instead of the actual"recorded_at"key- Rather than completing the abandoned compatibility layer,
kg.ProvenanceTrackerand its remaining supported methods (track_entity,get_all_sources,query_recorded_between,revision_history,export_audit_log) now emitDeprecationWarnings pointing callers tosemantica.provenance.ProvenanceManager - Removed/rewrote the 9 tests that only exercised the never-implemented compatibility methods to instead verify the observable behavior of the still-supported API, and corrected the stale
get_all_sources()assertion - Added the previously-missing
docs/migration/kg-provenance-tracker.mdmigration guide referenced by every new deprecation warning, with a method-mapping table toProvenanceManagerand a before/after example, closing #744
-
ProvenanceManager.track_entitysilently overrides an explicitparent_entity_id/derived_fromon re-track (#742) by @Sameer6305track_entity()resolvedparent_idvia a documented precedence chain (parent_entity_idkwarg >metadata["derived_from"]> source-as-known-entity-id fallback), but the history-preservation block that runs afterward unconditionally overwrote that resolved value with an auto-generatedf"{entity_id}:v:{existing.last_updated}"history pointer whenever the entity was being re-tracked, discarding whatever parent the caller had just explicitly supplied with no warningtrack_entity()now records whether the precedence chain already resolved an explicit parent (parent_entity_idkwarg,metadata["derived_from"], or the source-as-known-entity-id fallback) before the history block runs, and only falls back to the auto-generated history pointer when the caller supplied no explicit parent on that call- The archived history entry for the previous version is still kept reachable in
get_lineage()viaused_entities(BFS-traversed byInMemoryStorage.trace_lineage()) even when an explicit parent is supplied, so re-tracking with a new parent no longer orphans the prior version from the lineage chain; when no explicit parent is supplied,used_entitiesis left alone sinceparent_entity_idalready points at the same history id, avoiding a duplicate self-reference - Added
test_retrack_with_explicit_parent_overrides_history_link,test_retrack_without_explicit_parent_still_uses_history_link,test_retrack_with_derived_from_overrides_history_link, andtest_retrack_history_reachable_via_used_entitiesregression tests, closing #742
-
ProvenanceManager.get_lineagedoes not link entities that share a source URL (#735) by @KaifAhmad1track_entity()'s only auto-linking logic looked upsourceas if it were an existing entity'sentity_id, so passing the same real URL/DOI assourcefor two conceptually linked entities (e.g. a document and a decision derived from it) never produced a parent link, leavingget_lineage()returning a chain of length 1metadata["derived_from"]was preserved and echoed back in the output JSON but was never consulted by any linking or traversal code, so the caller's explicit relationship was silently inerttrack_entity()now treatsmetadata["derived_from"]as an explicit parent link (unlessparent_entity_idwas already passed directly), soInMemoryStorage.trace_lineage()'s existing BFS overparent_entity_idpicks it up for freemetadata["derived_from"]is now recognized on anycollections.abc.Mapping, not just a concretedict, so e.g.types.MappingProxyTypemetadata still creates the parent linkget_lineage()'s metadata aggregation now applies the queried entity's own metadata last so it wins over ancestor metadata on conflicting keys, matching the documented "most recent entry's metadata takes precedence" behavior — previouslytrace_lineage()'s BFS order caused ancestor metadata (now reachable viaderived_fromchains) to silently overwrite the queried entity's own values- Added 9 regression/edge-case tests in
tests/provenance/test_manager.pycovering the happy path, explicitparent_entity_idprecedence overderived_from, precedence over thesource-as-known-entity-id fallback, aderived_frompointing at a never-tracked entity, non-string/empty-stringderived_fromvalues being ignored, a self-referencingderived_fromnot hanging traversal, multi-hopderived_fromchains, metadata precedence between a queried entity and its ancestors, and non-dictMappingmetadata, closing #735
-
Reasoner.add_rulehad no deduplication, doubling rules and silently emptyingforward_chain()on rerun (#732) by @KaifAhmad1add_rule()unconditionally appended toself.rules, so re-running the same setup code on an existingReasonerinstance (e.g. re-executing a Jupyter cell) duplicated every rule; sinceforward_chain()only records a conclusion if it isn't already inself.facts, the second run's duplicated rules matched but produced no new results, with no error or warningadd_rule()now compares an incoming rule'srule_type,conditions, andconclusionagainst existing rules and returns the existingRuleinstead of appending a duplicate, keeping repeatedadd_rule()calls with the same definition idempotent- Added
test_add_rule_deduplicates_identical_rule,test_add_rule_deduplication_is_idempotent_across_forward_chain, andtest_add_rule_does_not_dedupe_distinct_rulesregression tests
-
InferenceResult.premisesalways empty fromforward_chain/backward_chain(#739) by @Sameer6305_match_rule()discarded matched facts and returned only instantiated conclusions, soExplanationGeneratoralways produced empty premises lists regardless of which facts actually satisfied a rule, closing #733_match_rule()now returns(conclusion, matched_facts)tuples;forward_chain()threads those facts intoInferenceResult(premises=...), merging premises when the same conclusion is derived more than once within a pass_prove_goal()'s base cases (goal already a known fact; goal matched via pattern unification) now returnpremises=[goal]/premises=[fact]instead of[]- Facts are matched against a
sorted()snapshot instead of the rawsetso rule matching and premise selection are deterministic - Added
test_forward_chaining_premisesregression test mirroring the existing backward-chaining premises test
-
Missing
shacloptional-dependency extra (#736) by @Sameer6305pip install semantica[shacl]referenced no matching extra inpyproject.toml, sopyshaclwas never installed despite being documented as the fix inontology_validator.py'sImportErrormessage, the Explorer API, the healthcare cookbook notebook, and the changelog- Added
shacl = ["pyshacl>=0.25.0"]to[project.optional-dependencies]and foldedshaclinto theallextra
-
NodeEmbedderAttributeErrormasked inContextGraph.analyze_graph_with_kg(#734) by @Sameer6305analyze_graph_with_kg()called a non-existentNodeEmbedder.generate_embeddings(), and the surrounding broadexcept Exceptionswallowed the resultingAttributeError, silently returning{"error": "Graph analysis failed due to an internal error"}fromget_causal_chain()'s supporting analytics andget_decision_insights()- Rewired the call site to the real
NodeEmbedder.compute_embeddings(graph_store, node_labels, relationship_types)API, derivingnode_labels/relationship_typesfromself.node_type_index/self.edge_type_index - Added a dedicated
except AttributeErrorbranch that logs distinctly and re-raises, so a broken internal method call surfaces as a diagnosable error instead of being indistinguishable from a legitimately empty analysis result
[0.5.1] - 2026-06-29
Added
-
Apache Arrow & Feather File Ingestion (#705) by @Luffy2208
- Added
ArrowIngestor(semantica/ingest/arrow_ingestor.py) for reading.arrow,.feather, and.ipcfiles via PyArrow - Supports Arrow IPC File format (random-access), Arrow IPC Stream format, Feather v1 and v2
- Selective column reads, optional row limits, and batch-aware iteration that stops early without scanning the full file
extract_schema()andextract_metadata()convenience methods for schema/metadata inspection without reading row data_ArrowReaderWrapperprovides a unified interface across all three reader types, preventing stream exhaustion during schema inspectioningest_arrow()convenience function andingest(..., source_type="arrow")unified dispatch- Automatic Arrow format detection in
ingest()by file extension (.arrow,.feather,.ipc) and by Arrow IPC magic bytes (ARROW1\x00\x00) inFileTypeDetector - Registry integration under the
arrowtask namespace withfile,schema, andmetadatamethods - Lazy-import exports of
ArrowIngestor,ArrowData, andingest_arrowfromsemantica.ingest - Optional dependency group:
pip install semantica[ingest-arrow]; included inpip install semantica[all] - 34 tests covering schema extraction, metadata inspection, row limits, column selection, multi-batch reading, IPC stream format, Feather ingestion, empty datasets, null values, magic-byte detection, and failure modes
- Added
-
Knowledge Explorer Deployment Templates (#684) by @ZohaibHassan16 and @KaifAhmad1
- Added
deploy/directory with ready-to-use templates for 7 platforms, closing #681 - Docker — fixed
Dockerfilepath (was broken on clean checkout), added non-root user,HEALTHCHECK,.dockerignore; fixeddocker-compose.ymlto start Explorer alongside FalkorDB on a shared network; addeddocker-compose.dev.ymlwith source volume-mounts for hot-reload (docker compose upbrings up the full stack in one command) - Railway —
deploy/railway/railway.tomlwith Dockerfile builder, healthcheck path, restart policy, and env vars wired from the Railway Redis plugin - Render —
deploy/render/render.yamlBlueprint provisioning the web service and a Redis instance together with cross-linked env vars - Fly.io —
deploy/fly/fly.tomlwith region, 512 MB VM, auto-stop, HTTP healthcheck, and a short README with fourflyctlcommands to deploy from zero - GCP Cloud Run —
deploy/gcp/cloudbuild.yaml(build → push → deploy pipeline) anddeploy/gcp/cloudrun-service.yaml(scale-to-zero, Secret Manager env vars, liveness probe) - Azure Container Apps —
deploy/azure/azure.yaml,main.bicep(Container App + managed environment, HTTP ingress, HPA min 0 / max 10, liveness probe), andmain.parameters.json; deployable withazd up - Kubernetes + Helm — raw manifests (
namespace,configmap,secret.example,deploymentwith 2 replicas + rolling update,service,ingresswith cert-manager TLS,kustomization); Helm chart withChart.yaml,values.yaml,values.prod.yaml, HPA template, andhelm lint-passing templates; all templates carrynamespace: {{ .Release.Namespace }} - Added
/api/healthendpoint returning{"status": "ok"}used by all platform healthchecks - Wired
ALLOWED_ORIGINS,FALKORDB_HOST, andFALKORDB_PORTfrom environment variables insemantica/explorer/app.py - Security hardened: non-root containers,
readOnlyRootFilesystem,NetworkPolicywith explicit ingress/egress selectors,seccompProfile: RuntimeDefault, capabilities dropped; secrets viasecret.yaml.exampletemplates only — no committed credentials
- Added
Fixed
-
Arrow ingestion double full-scan on every data read (#705) by @KaifAhmad1
ingest_filepreviously called_file_metadata(a full batch scan) before_read_batches, meaning every read scanned the entire file twice; for alimit=1read on a large file the metadata pass visited every batch while the data pass read only one; replaced with a single-pass_read_batches_with_infothat collects batch metadata as a side effect of the data read;_file_metadatais now only invoked forinclude_data=False
-
Dead
num_record_batchesproperty on_ArrowReaderWrappermaterialised all table batches (#705) by @KaifAhmad1- The property was never called by production code but its
is_tablebranch calledto_batches()purely to takelen(), materialising the entire table in memory just for a count; property removed
- The property was never called by production code but its
-
Arrow
_open_filechained the wrong exception (#705) by @KaifAhmad1- The fallback cascade (IPC file → IPC stream → Feather) raised
from feather_err, surfacing the least diagnostic error in the Python traceback chain; changed tofrom file_errso the IPC file open error — the most informative signal for unrecognised formats — appears as__cause__
- The fallback cascade (IPC file → IPC stream → Feather) raised
-
Neo4j Bulk CSV Export (#665) by @Luffy2208
- Added
Neo4jCSVExporterfor generating Neo4j bulk-import CSV files compatible withneo4j-admin database import - Produces deterministic
nodes.csvandrelationships.csvwith stable node IDs — reuses existing graph IDs or derives reproducible SHA-256 content-based IDs when none are present - Multi-label support via Neo4j
:LABELconvention with configurablelabel_separator(default;) - Alphabetically sorted property columns and deterministic row ordering for reproducible output across permuted inputs
- Relationship endpoint resolution: aliases (
name,text,label) automatically mapped to stable node IDs - Nested property serialisation to canonical JSON; flat scalar values written directly
dry_run()method for pre-flight CSV validation without writing filesvalidate_export()for post-write integrity checks (unique:id, consistent column widths, valid endpoint references)export_nodes()andexport_relationships()for partial exportsstrict=Truemode raisesValidationErroron unresolved relationship endpointsexport_neo4j_csv()convenience function andformat="neo4j_csv"/format="neo4j-csv"dispatch inexport_knowledge_graph()- Registry integration under the
neo4j_csvtask namespace - Documentation added to
semantica/export/export_usage.mdwith usage examples, mapping assumptions, andneo4j-adminimport command - 13 tests covering headers, node/relationship CSV structure, multi-label, missing properties, deterministic output, CSV quoting/escaping, Unicode, empty graphs, dry-run, duplicate ID detection, ambiguous alias handling, nested property serialisation, and
KnowledgeGraphintegration
- Added
Fixed
-
Neo4j CSV exporter
_write_csvcrashed withTypeErroron dialect kwargs (#665) by @KaifAhmad1- Passing
delimiter=,encoding=, or any caller kwarg toexport_neo4j_csvcausedcsv.writerto receive unknown or duplicate keyword arguments;_write_csvnow whitelists only validcsv.writerdialect params (quotechar,doublequote,skipinitialspace,escapechar,strict)
- Passing
-
export_neo4j_csvdouble-passed kwargs to both the constructor andexport()(#665) by @KaifAhmad1- Constructor-level settings (
node_file_name,relationship_file_name,encoding,delimiter,label_separator,strict) were merged into config for the constructor then re-forwarded as**kwargstoexport_knowledge_graph, causing dialect params to collide; kwargs are now split intoinit_kwargsandcall_kwargsbefore forwarding
- Constructor-level settings (
-
Dead
node_id_lookupdict removed from_prepare_export(#665) by @KaifAhmad1- The
{original_index → stable_id}mapping was built on every export but never consumed; removed to avoid misleading future readers
- The
-
Dropped ambiguous
format="neo4j"alias fromexport_knowledge_graphdispatch (#665) by @KaifAhmad1"neo4j"is used throughout the codebase to identify the live Bolt/Cypher graph store backend; routing it silently to the offline bulk-CSV exporter would have confused callers; only"neo4j_csv"and"neo4j-csv"are accepted
-
export_usage.mddocumented non-existent constructor and function parameters (#665) by @KaifAhmad1- Examples showed
node_label_sep(correct:label_separator),strict_validation(correct:strict), andnodes_path/rels_pathkwargs that do not exist; all three examples corrected to match the actual API
- Examples showed
-
Public API Ingestion Support (#602) by @Luffy2208
- Added
PublicAPIIngestorclass built on top ofRESTIngestorfor credential-free REST endpoints - Added
PublicAPIExampleandPublicAPIExamplescatalog with 6 pre-configured no-auth examples:jsonplaceholder_posts,jsonplaceholder_users,jsonplaceholder_todos— fake REST resources for testingrest_countries_all— country reference datadata_gov_datasets— Data.gov CKAN catalog searchopen_meteo_forecast— weather forecast (Berlin sample)
- Added
PublicAPIDetectiondataclass for endpoint-level public/no-auth detection - Endpoint-level public API detection via
detect_public_api()(informational, never raises) - No-auth validation: rejects
Authorization,X-Api-Key, and all common auth headers before sending the request - Auth credential detection in URL query strings (
api_key=,token=,access_token=, etc.) - Polite rate limiting with per-request and per-ingestor
rate_limit_delaycontrols - Response parsing for JSON, CSV, and XML with
response_format="auto"content-type detection - HTML response guard —
text/htmlresponses are never misclassified as XML - Nested
record_pathdot-notation extraction (e.g."result.results"for Data.gov envelope) _to_recordsnormalization with automatic envelope unwrapping foritems,data,results,recordskeysbatch_public_apis()for multi-endpoint ingestion with optionalfail_fastingest_examples()for bulk example ingestionsample_response()fixtures onPublicAPIExamplesfor mocked unit tests without live network callsingest_public_api()convenience function andingest(..., source_type="public_api")unified dispatchsource_type="api"alias supported iningest()- Registry integration:
public_apiandapitask namespaces withendpoint,example,detect,batch,examplesmethods - Lazy-import exports of
RESTIngestor,APIData,PublicAPIIngestor,PublicAPIExample,PublicAPIExamples,PublicAPIDetectionfromsemantica.ingest - Documentation: updated
docs/reference/ingest.md,docs/modules.md, andsemantica/ingest/ingest_usage.mdwith full usage examples - 18 mocked tests covering JSON/CSV/XML parsing, nested record extraction, auth rejection, detection, string boolean config, batch dispatch, and unified
ingest()routing - 3 optional-import tests covering
defusedxmlfallback path and import isolation without web-scraping backends
- Added
Fixed
-
Public API XML parsing hardened against malicious payloads (#602) by @Luffy2208
- Replaced stdlib
xml.etree.ElementTreewithdefusedxml.ElementTree(XXE/entity-expansion safe); falls back to a hardenedlxmlparser (resolve_entities=False,no_network=True,load_dtd=False,huge_tree=False) whendefusedxmlis not installed - Added regression test asserting XXE entity payloads raise
ProcessingError
- Replaced stdlib
-
validate_no_authconfig value not honoured when passed as a string (#602) by @Luffy2208bool("false")evaluated toTrue, makingvalidate_no_auth=Falseimpossible via config files or environment variables; replaced with explicit_coerce_bool()that maps"false","0","no","off"→Falseand rejects unrecognised strings withValidationError
-
Auth credential detection extended to URL query strings (#602) by @Sameer6305
detect_public_api()andingest_public_api()now scan the endpoint URL itself for auth parameters (api_key,token,access_token, etc.) viaurllib.parse.parse_qs, not only request headers and explicitparams=dicts- Added regression tests for URL auth rejection (3 parametrized cases)
-
ingest_examplesandbatch_public_apismutable options mutation (#602)- Shared
**optionsdict was passed by reference across loop iterations; mutable values such asparamsdicts were silently mutated after the first call, causing subsequent calls to receive a different (partially modified) options set; fixed by deep-copying options on each iteration
- Shared
-
rate_limit_delayforwarded twice iningest_public_apimethod dispatcher (#602)rate_limit_delaywas consumed by thePublicAPIIngestorconstructor viaconfigbut also leaked intorequest_kwargsforwarded to the ingestor method; added to theconfig_only_keystrip list so it is consumed once at construction time only
-
XML File Ingestion Support (#560) by @Luffy2208
- Added
XMLIngestorclass withlxmlbackend for parsing local XML files - Nested element hierarchy and flat element list extraction
- Namespace and prefix extraction with collision handling
- Attribute and element metadata extraction
- Optional XSD schema validation with detailed error reporting
- Optional DTD validation (internal and external)
- Secure-by-default parser (
resolve_entities=False,no_network=True) blocking XXE attacks ingest_xml()convenience function andingest_file(..., method="xml")support- Unified
.xmlauto-detection viaingest("file.xml") - Directory ingestion with recursive scanning and
fail_fastsupport ingest_string()for in-memory XML bytes/str ingestion- Comprehensive test coverage (8/8 tests passing)
- Added
Fixed
-
NERExtractor LLM method returning pattern-based output on custom gateways (#554, PR #556) by @KaifAhmad1
NERExtractor(method="llm")silently fell back to regex/pattern extraction when used with OpenAI-compatible enterprise or self-hosted gateways (Qwen, LLaMA proxies, internal routing layers). Returned entities carriedextraction_method='pattern'even though the LLM itself was producing correct tool-call output. Three root causes fixed:-
Silent exception swallowing —
exc_info=Truewas missing from the method-failureWARNINGinNERExtractor.extract_entities. The full gateway-rejection traceback was invisible in logs even withDEBUGlevel enabled, making the failure impossible to diagnose without reading source code. -
response_format=json_objectsent to incompatible gateways —OpenAIProvider.generate_structuredunconditionally includedresponse_format={"type": "json_object"}in every API call. Custom/enterprise gateways frequently reject this parameter, causing both theinstructorpath and the manual repair loop to fail with the same error on every retry, eventually triggering_extract_fallback(pattern extraction). -
No fallback in the
generate_typedmanual repair loop — whengenerate_structureditself raised (due to gateway rejection), the repair loop retried the identical failing call up tomax_retriestimes before giving up. There was no path to recover via plaingenerate()+ JSON parsing.
Additional fixes applied during PR review:
- Mode.JSON retry in
generate_typednow stripsresponse_formatfromcreate_kwargsbefore forwarding to the retry client, preventing incompatible kwargs from being sent to a client configured for a different instructor mode. exc_info=Trueadded to thegenerate_structuredfallback warning in the manual repair loop for consistent observability across all failure paths.- Removed dead duplicate
is_availabledefinition inGroqProvider— Python silently kept only the second definition; the first was unreachable. OpenAIProvider._init_clientnow validatesbase_urlscheme at construction time. Non-HTTP(S) schemes (file://,ftp://,javascript:, etc.) raiseValueErrorimmediately, preventing SSRF ifbase_urloriginates from configuration rather than hardcoded values.
17 regression tests added in
tests/test_issue_554_fixes.pycovering all bug paths, including harshalizode's exact gateway configuration. -
Security
-
GitHub Actions workflow permissions hardened — added explicit
permissions: contents: read+security-events: writeblock todefender-for-devops.yml, resolving CodeQL alert actions/missing-workflow-permissions (CWE: principle of least privilege). -
DOMPurify upgraded to 3.4.0+ via npm overrides —
monaco-editorpinneddompurifyat 3.2.7; addedoverridesinexplorer/package.jsonto force^3.4.0(resolved to 3.4.10). Fixes 6 Dependabot alerts:- Prototype pollution → XSS bypass via
CUSTOM_ELEMENT_HANDLINGfallback (CVE-2026-41238 / GHSA-v9jr-rg53-9pgp) - Mutation-XSS via re-contextualization into raw-text wrappers (GHSA-h8r8-wccr-v5f2)
SAFE_FOR_TEMPLATESbypass inRETURN_DOMmode (CVE-2026-41239 / GHSA-crv5-9vww-q3g8)ADD_TAGSfunction-predicate bypassesFORBID_TAGS(GHSA-39q2-94rc-95cp / GHSA-h7mw-gpvr-xq4m)ADD_ATTRpredicate skips URI validation, allowingjavascript:URLs (GHSA-cjmm-f4jc-qw8r)USE_PROFILESprototype pollution allows event handlers (GHSA-cj63-jhhr-wcxv)
- Prototype pollution → XSS bypass via
-
uuidupgraded to 13.0.1+ via npm overrides — bumped from 13.0.0 to 13.0.2, fixing missing buffer bounds check inv3/v5/v6APIs that allowed silent partial writes into caller-provided buffers (CVE-2026-41907 / GHSA-w5hq-g745-h8pq). -
Vite upgraded from 5.x to 6.4.3 — resolves path traversal in optimised-deps
.maphandling (CVE-2026-39365 / GHSA-4w7w-66w2-5vf9) and the esbuild dev-server CORS issue (GHSA-4w7w-66w2-5vf9). Bundled esbuild updated from 0.21.5 → 0.25.12. -
esbuild forced to 0.28.1+ via npm override — vite 6.4.3 bundles esbuild 0.25.12 which is vulnerable to missing binary integrity verification in the Deno distribution module (GHSA-gv7w-rqvm-qjhr); added
"esbuild": "^0.28.1"tooverridesinexplorer/package.json.npm auditnow reports 0 vulnerabilities (Dependabot #15). -
Leaked Groq API keys removed from cookbook notebooks — 6 hardcoded
GROQ_API_KEYvalues (gsk_...) stripped from configuration cells insupply_chain/01,intelligence/01,cybersecurity/01,cybersecurity/02,finance/01, andblockchain/02; fallback replaced with empty string (secret scanning alerts #1–#6). Keys were already publicly exposed — rotate them in the Groq console. -
Leaked Groq API keys removed from additional cookbook notebooks — 6 distinct hardcoded
GROQ_API_KEYvalues stripped from 4 additional notebooks:advanced_rag/01_GraphRAG_Complete,advanced_rag/02_RAG_vs_GraphRAG_Comparison,blockchain/01_DeFi_Protocol_Intelligence, andbiomedical/01_Drug_Discovery_Pipeline(secret scanning alerts #1–#6). Affected keys:gsk_SLLE0...,gsk_S4dBVJ...,gsk_SLOv6...,gsk_lR6Qcj...,gsk_ToJis6...,gsk_LmbQBr...; all publicly exposed since Dec 2025 — revoke in the Groq console and close the GitHub secret scanning alerts as "Revoked" in the Security tab.
[0.5.0] - 2026-05-11
Added
- Distance Intelligence Embedding Cache Optimization by @KaifAhmad1
- Implemented per-session graph revision-based embedding cache to avoid re-scanning all nodes on every request
- Added
get_cached_embeddings()method to GraphSession with thread-safe caching and automatic invalidation - Updated distance matrix and semantic neighborhood endpoints to use cached embeddings for significant performance improvement
- Added graph revision tracking using hash-based identifiers for cache invalidation
- Implemented force refresh capability and automatic cache invalidation on graph modifications (add_nodes/add_edges)
- Resolved TODO in
graph.pyfor embedding caching optimization
- Parquet File Ingestion Support (#548) by @Luffy2208
- Added ParquetIngestor class with PyArrow backend
- Single file and partitioned directory ingestion
- Schema and metadata extraction capabilities
- Selective column reading with memory efficiency
- Hive-style partition discovery support
- Unified dispatch integration
- Optional dependency management (ingest-parquet extra)
- Comprehensive test coverage (32/32 tests passing)
Ontology Hub (part of #517)
-
Alignments tab (PR #524, @KaifAhmad1 @ZohaibHassan16) — cross-ontology alignment authoring UI:
- Create/edit/delete alignments with source URI, target URI, relation selector (owl:equivalentClass, all five skos:*Match variants), confidence slider, provenance, and reviewer fields.
- Pairwise alignment matrix: scrollable table for all loaded ontology pairs; clicking a badge pre-fills the form.
- Alignment suggestions via
POST /api/ontology/suggest-alignments— blended score (0.4×label + 0.6×TF-IDF char-ngram cosine); one-click accept. - Ephemeral-storage banner; all handlers wrapped in
useCallback.
-
Health Dashboard (PR #524) — per-ontology quality scoring across 5 dimensions:
- Completeness, Consistency, SHACL (stub), Alignment, Documentation.
- Total score computed as mean of scoreable dimensions only (SHACL excluded when unavailable).
- Issue list with severity badges (error/warning/info), entity URI chip, "Fix in Editor" deep-link.
- Downloadable JSON health report;
GET /api/ontology/healthwith_MAX_ANALYSIS_NODES = 5 000OOM cap.
-
SHACL Studio (PR #524) — interactive SHACL shape authoring:
- Shape generation via
POST /api/ontology/shacl/generate(permissive/standard/strict tiers). - Shape library panel with per-shape Turtle extraction; "View all" restores full document.
- Monaco editor with custom Monarch tokenizer for Turtle syntax.
- Validation stub via
POST /api/ontology/shacl/validate; rejects empty/invalid Turtle with HTTP 422.
- Shape generation via
-
Visual Ontology Editor (PR #519, @KaifAhmad1) — @xyflow/react canvas for authoring classes/properties/individuals without hand-writing OWL/Turtle:
- Context menus on nodes (rename, add super/subclass, restrictions, SKOS metadata, deprecation, delete with impact count) and edges (toggle functional/symmetric/transitive/inverse-functional, add inverse).
- All edits debounced and staged as pending diffs via
PATCH /api/ontology/draft; nothing commits until proposal publish.
-
Versions & Proposals tab (PR #519) — version timeline, proposal review (approve/reject/publish), SHACL pre-validation, side-by-side diff via
VersionManager.diff_ontologies(). -
Ontology Registry (PR #518, @KaifAhmad1) — full CRUD with status/format badges, per-ontology stats, live search, filter pills (All/OWL/SKOS/Internal/External), action feedback auto-hide.
-
Ontology Loader (PR #518) — three-mode modal: URL import (fetch preview + load), file upload (.ttl/.rdf/.owl/.nt/.jsonld/.n3), create new (scratch/from-data/from-text).
-
Entity Search panel (PR #518) — debounced 320 ms search across all loaded ontologies; type filter pills; result detail panel with super/subclasses, domain/range, instance count.
-
SKOS Vocabulary Manager (PR #518) — hierarchical concept browser with recursive
ConceptTreeNode, client-sidefilterConcepts(), full SKOS annotation detail (definition, scopeNote, broader/narrower/related/exactMatch). -
16 backend endpoints under
/api/ontology— registry, preview, load, create, search, entity, skos/schemes, skos/concept, draft, proposals CRUD, versions, alignments, health, shacl/generate, shacl/shapes, shacl/validate (PRs #518, #519, #524). -
Explorer landing page redesign (PR #516, @ZohaibHassan16) — hero section, animated SVG graph preview, live
/api/graph/statsmetrics, workspace launcher;Space Grotesk/IBM Plex Sansfonts;prefers-reduced-motionsupport. -
Distance Intelligence (PR #502, @KaifAhmad1):
ContextGraph.get_neighbors(include_distance_metadata)— addsdistance_band,confidence_decay,path_to_anchorper result.AgentContext.retrieve()/find_precedents()blend graph proximity with semantic score (combined_score = (1−w)×semantic + w×proximity).- 5 new API endpoints:
POST /api/graph/distance-matrix(N×N, upper-triangle mirrored),GET /api/graph/node/{id}/semantic-neighborhood,GET /api/decisions/causal-distance,GET /api/temporal/distance-history,POST /api/export/distance-enriched(CSV/JSONL, capped at 200 nodes). - Explorer UI: Ego Mode (BFS depth-of-field fading, depth slider 1–8), Structural overlay, Semantic overlay, Heatmap (green→red by hop); Path inspector with distance band chip, metric cards, bottleneck node highlight.
- 57 new tests in
tests/context/test_distance_intelligence.py.
-
Graph Explorer visual refresh (PR #503, @ZohaibHassan16) — structured
ui.*design-token namespace; per-shape biomolecule/condition/compound config; decomposed toolbar memos; typed sub-components (SearchCommandBar,ToolbarCluster, etc.); deterministic LOD edge classification viaGraphFullEdgeClass. -
Graph Workspace declutter (PR #483, @ZohaibHassan16) — calmer default presentation for dense graphs, display-edge aggregation with raw-edge bundle retention, grouped community view, neighborhood collapse/expand.
-
Bidirectional path finding (closes #469, @KaifAhmad1) —
directed=falsequery param on BFS and Dijkstra; undirected view built viagraph.to_undirected()for traversal only; empty-path 404 guard;PathResponse.directedfield. -
Node distance semantics in path responses (closes #472) —
PathResponsegainshop_countanddistance_band("direct"/"near"/"mid-range"/"distant");classify_path_distance()insemantica/utils/helpers.py;KGVisualizer.visualize_network(highlight_path)with band-scaled edge rendering. -
Native
KnowledgeGraphtype support inKGVisualizer(closes #471) — formalKnowledgeGraphdataclass (entities,relationships,metadata);_normalize_graph()duck-types input; raises clearProcessingErroron unknown types. 21 tests added. -
Indexed search for large graphs (PR #481, @ZohaibHassan16) — purpose-built inverted index with exact/token/prefix lookup tiers; LRU cache (128 slots); O(log n) mutation sync via
bisect.insort; warm-query time 24 ms → 0.004 ms on 118 k-node graph. -
Provenance traversal multi-hop fix (PR #480, @Sameer6305) — undirected ego-graph expansion so upstream ancestors at depth ≥ 2 are no longer silently excluded;
ProvenanceEdge.directionfield (upstream/downstream/lateral); grouped markdown report under## Upstream/Downstream/Lateralsections. -
TripletStore ontology namespace (PR #447, @KaifAhmad1) —
_resolve_iri()appliesbase_uribeforeurn:fallback; W3C prefix expansion table (owl/xsd/rdf/rdfs/skos) expands to canonical IRIs regardless ofbase_uri. -
Blazegraph literal serialization (PR #448, @KaifAhmad1) —
_format_object_for_sparql()selects IRI/typed-literal/language-tagged-literal/plain-literal token;_resolve_datatype_iri()with prefix expansion; RFC 5646 language-tag validation;_escape_literal()for string escaping. -
DeepSeek provider via OpenAI SDK (PR #482, @liling) —
_init_clientrewritten usingopenai.OpenAI(base_url=self.base_url)instead of defunctdeepseekpackage;verbose_modeassignment fix;pyproject.tomlupdated toopenai>=1.0.0. -
DuplicateDetectorresult limiting and ranking (issue #534, by @KaifAhmad1):max_results— hard global cap on returned candidates; applied after sorting.Nonemeans no limit.top_k_per_entity— keep at most k candidates per entity (by the sort field) so no single entity floods the output.Nonemeans no per-entity limit.min_similarity— extra similarity floor on top ofsimilarity_threshold; candidates below it are dropped before ranking.Nonemeans no extra floor.sort_by— ranking field before limits are applied; accepts"confidence"(default) or"similarity_score". Invalid values raiseValueErrorat construction time.- All four options are applied by the new
_apply_result_limitshelper and are respected by bothdetect_duplicates()andincremental_detect(). - 15 new tests in
TestResultLimitingcovering each option in isolation and in combination. - Follow-up Qodo review fixes (by @KaifAhmad1):
top_k_per_entitynow uses OR semantics — a candidate is kept if either entity is still under quota, preventing high-quality pairs from being silently dropped when a popular counterpart saturates its limit.max_resultsandtop_k_per_entitynow validated at construction time; negative or non-integer values raiseValueError.min_similaritynow validated in[0.0, 1.0]at construction; out-of-range values raiseValueError.- Added
_normalize_entity_idhelper (always returnsstr) used consistently in both_apply_result_limitsand_build_duplicate_groups, eliminatingintvsstrID key mismatches. - Updated
detect_duplicatesandincremental_detectdocstrings to reflect the configurablesort_byfield.
Fixed
-
Fix:
ConflictDetector.detect_conflicts()raisesAttributeErrorwhen called withmethod=orproperty_name=kwargs (issue #533, PR conflicts, by @KaifAhmad1):detect_conflictswas defined twice inconflict_detector.py; Python silently overwrote the first (dispatcher) definition with the second (comprehensive), which accepted nomethodorproperty_nameparameters — causingAttributeErrororTypeErrorfor any caller using those kwargs.- Removed the first (dead) definition and merged its dispatcher logic into the surviving method. New signature:
detect_conflicts(entities, method="all", property_name=None, entity_type=None, **kwargs). - Supported
methodvalues:"all"(default, comprehensive),"value","property","type","relationship","temporal","logical","entity". Unknown values raiseValueError. - Fixed
method="relationship"silently defaultingrelationshipsto the entities list, which caused entity dicts to be iterated as relationship dicts producing silent wrong results (None_None_Nonekeys). Now defaults to[]with dict normalization. - Removed unreachable dead code (
for field_name in fields_to_checkloop aftertry/except raise) indetect_entity_conflicts. - Follow-up Qodo review fix — hardened
method="relationship"normalization: whenrelationshipskwarg is a dict whose"relationships"value is itself a non-list (or the key is absent), the value is now always wrapped in a list before being passed todetect_relationship_conflicts, guaranteeingList[Dict]input in all cases.
-
Fix:
semantica[all]installation fails on Windows due tofaiss-gpudependency (issue #532, PR #utlis, by @KaifAhmad1):[all]bundled the[gpu]extra (faiss-gpu>=1.7.0,cupy>=10.0.0), which has no Windows builds, causingpip install "semantica[all]"to fail withNo matching distribution found for faiss-gpu>=1.7.0.- Removed
gpufrom both[all]lines inpyproject.toml—[all]now installs only cross-platform dependencies. Users on Linux who need GPU acceleration can installsemantica[gpu]explicitly.
-
Fix: Progress tracker crashes with
UnicodeEncodeErroron Windows cp1252 consoles (issue #531, PR #utlis, by @KaifAhmad1):ConsoleProgressDisplay.update()had 5 directsys.stdout.write()calls that bypassed the existing_safe_write()guard, causingUnicodeEncodeErrorwhen emoji characters (🧠,📊) were written to cp1252-encoded consoles during any progress-tracked operation.- All 5 calls replaced with
self._safe_write(), which catchesUnicodeEncodeErrorand re-encodes output witherrors="replace"so progress output never crashes the process. - Added
TestProgressTrackerEncodingregression class (3 tests) covering_safe_writesafety, pipeline header write, and auto emoji-disable on cp1252 stdout.
-
Fix: Break circular import in
semantic_extract; address Qodo review bug (issue #528, PR #536, by @ZohaibHassan16, review fixes by @KaifAhmad1):- Root cause —
ner_extractor.pyimportedget_entity_methodfrommethods.py, whilemethods.pyimportedEntityfromner_extractor.py, creating a circular import that raisedImportError: cannot import name 'Entity' from partially initialized moduleon any import ofsemantica.semantic_extract. semantica/semantic_extract/types.py(new) — sharedEntity,Relation, andTripletdataclasses extracted into a dedicated module that neither side of the old cycle imports, so bothner_extractor,relation_extractor,triplet_extractor, andmethodscan import from it freely.semantica/semantic_extract/__init__.py— lazy-loads package-level exports so core extractor imports do not pull in optional modules (e.g. the YAML-backed semantic network extractor); addedTripleExtractoras a compatibility alias forTripletExtractor; legacy re-exports from the individual extractor modules preserved for backward compatibility.semantica/semantic_extract/methods.py— updated to import shared types fromtypes.py; extractor-specific imports moved to function scope where needed to prevent re-introducing the cycle.- Added regression tests (
tests/semantic_extract/test_imports.py) covering import order independence (methods-before-extractors and extractors-before-methods), legacy type import compatibility,TripleExtractoralias, and that core imports do not requireyaml. - Review fix (Qodo — Py3.8 test import crash):
test_imports.pyannotated_run_pythonas-> subprocess.CompletedProcess[str], which is not subscriptable at runtime on Python 3.8 (generic subscript on built-in types requires 3.9+). Addedfrom __future__ import annotations(PEP 563) so all annotations are lazy strings never evaluated at import time, restoring compatibility with the declaredrequires-python = ">=3.8"without any behaviour change on 3.9+.
- Root cause —
-
Fix: Lazy-load optional ingest backends; address Qodo review bugs (issue #527, PR #535, by @ZohaibHassan16, review fixes by @KaifAhmad1):
semantica/ingest/__init__.py— core exports (FileIngestor,ingest_file, config, registry) remain eagerly imported; all optional backends (WebIngestor,FeedIngestor,RepoIngestor,EmailIngestor,StreamIngestor,DBIngestor,MCPIngestor,OntologyIngestor,SnowflakeIngestor) are now deferred behind a module-level__getattr__, sofrom semantica.ingest import FileIngestorno longer fails when GitPython or BeautifulSoup4 are absent.semantica/ingest/methods.py— backend imports relocated into their respective ingestion functions (ingest_web,ingest_feed,ingest_repository,ingest_email) with helper_missing_optional_dependency()/_is_missing_dependency()for consistent, actionable error messages.- Review fix (Bug 1 — overbroad missing-dep detection): replaced
except ImportErrorwithexcept ModuleNotFoundErrorin all four function-level import guards and in__getattr__.ImportErrorcatches failures thrown by code inside a successfully found module, masking real bugs with a misleading "package not installed" message;ModuleNotFoundError(its subclass) is specific to absent modules. Simplified_is_missing_dependencyto rely solely onexc.namenow thatModuleNotFoundErroralways sets it. - Review fix (Bug 2 — expected errors logged as failures): added
except ConfigurationError: raisebefore the blanketexcept Exceptionhandlers iningest_web,ingest_feed,ingest_repository, andingest_email. Missing optional dependencies are expected user-configuration issues and must not produce error-level log entries. - Review fix (Bug 3 — test blocker not setting
exc.name):OptionalDependencyBlocker.find_specnow setserr.name = root_nameon the manually constructedModuleNotFoundError, matching what Python's import machinery does, so_is_missing_dependencycorrectly identifies the missing package in tests. - Added regression tests (
tests/ingest/test_optional_imports.py) that block thegitandbs4modules via a custom meta path finder and assert core imports succeed and backends raiseConfigurationErrorwith an actionable message.
-
Fix: Ontology Hub post-review bug fixes and security hardening (follow-up to #518, closes security advisory #23, by @KaifAhmad1):
- Broken registry filters —
fetchRegistrywas sending toolbar filter values (owl,skos,internal,external) to the backend as thestatusquery param, which only acceptspublished|draft|external, causing those filters to return empty lists. Removed the spuriousstatusparam; all format/kind filtering is now applied client-side viafilteredEntries, which already had the correct logic. - Toggle/refresh URI corruption —
toggle_ontologyandrefresh_ontologyapplied.removesuffix("/toggle")/.removesuffix("/refresh")to the captured path parameter, which would silently corrupt any ontology URI that legitimately ends with those strings. Starlette's route regex (/{uri:path}/toggle) already strips the literal suffix via backtracking, so theremovesuffixcalls were removed and the rawontology_uriparameter is used directly. - SSRF in URL fetch —
_fetch_url_sync()accepted arbitrary user-supplied URLs and calledrequests.get()with no validation, enabling server-side request forgery against internal services. Added_validate_fetch_url()which rejects non-http/httpsschemes and resolves the hostname viasocket.getaddrinfo, blocking loopback, private, link-local, reserved, and multicast addresses. - File upload format misdetected — the file picker accepted
.xmland.jsonbutfmtMaphad no entries for those extensions, causing them to default toturtle. Addedxml: "xml"andjson: "json-ld"mappings. Changed the unknown-extension fallback from|| "turtle"to?? ""(empty string), and omit theformatkey from the request body when empty so the backend_detect_format()runs instead of receiving a forced incorrect value. Also added.n3to the accepted extension list and dropzone hint. - Inconsistent XML hardening —
_parse_rdf_sync()calledrdflib.Graph().parse()directly, bypassing thedefusedxml-based XXE protection already present insemantica/explorer/utils/rdf_parser.py. Now routes through_safe_parse_rdf()from that module, applying consistent protection for all RDF/XML parse paths. - Search scans whole graph (
GET /api/ontology/search) — the endpoint fetched up to 999 999 nodes and performed a linear Python substring scan on every request. Replaced withsession.search(q, limit * 6)which uses theGraphSearchIndex; results are then post-filtered by_SEARCHABLE_TYPESandentity_typebefore being returned up to the requested limit. - ReDoS in format detector (security advisory #23, CodeQL
py/polynomial-redos, CWE-1333/730/400) —_detect_format()usedre.match(r"_:\w+|<[^>]+>\s+<[^>]+>", ...)to detect N-Triples content. The<[^>]+>\s+<[^>]+>alternative was flagged as a polynomial regular expression on uncontrolled data. The URI-subject branch was already unreachable (strings starting with<return"xml"two lines above), so the entire regex was replaced with two O(1) string operations:stripped.startswith("_:")and" <" in stripped.import reremoved as now unused.
- Broken registry filters —
-
OWLExporter Turtle syntax (closes #478) — invalid multi-block output fixed via
_ttl_block(); data properties no longer silently dropped;_escape_ttl_str()applied to all label/comment/version sites. 43 tests added. -
OWLGenerator schema compatibility (Issue #446) — label-first IRI fallback, list-typed datatype ranges, per-call namespace consistency,
subClassOf/subclassOfparity. -
TripletStore IRI regressions (PR #447 follow-up) — non-string IDs coerced to
str(); W3C prefix expansion now correct regardless ofbase_uri. -
KGVisualizeracceptsKnowledgeGraphobjects (closes #458) —_normalize_graph()duck-types input; raises clearProcessingErroron unknown types. 21 tests added. -
Semantic Distance UI slash-safe routes (PR #515, @ZohaibHassan16) — query-param routes
/api/graph/semantic-neighborhood?node_id=and/api/graph/path?source=&target=bypass FastAPI's%2Fpre-decode; legacy path-segment routes kept as deprecated aliases. -
Explorer Distance Intelligence rendering (PR #513, @ZohaibHassan16) — distance state flows through Sigma reducer/theme pipeline instead of mutating raw graph attributes;
restoreNodeColors()race eliminated by merging ego/heatmapuseEffecthooks. -
Distance Intelligence code review regressions (PR #502 follow-up, @KaifAhmad1) —
top_kparam name fix;include_distance_metadatagated behindFalsedefault;weakest_linkkey standardized; temporal sampling usestimedeltanottimetuple; O(E×L) decay replaced with O(E) index;AgentContext._apply_proximity_metadatastoresgraph_node_idseparately; sweep animationsweepGenerationcounter fix; HTTP 413 for >200 node subsets; upper-triangle distance matrix. -
Knowledge Explorer blockers (PR #420, @ZohaibHassan16):
Dockerfile: renamedDockerFile→Dockerfile; fixedCMDmodule path; addedapp = create_app()at module level.- CORS: default origins narrowed from
"*"tolocalhost:5173only. get_ws_manager()now raises HTTP 503 instead of unhandledAttributeError.- SPARQL: read-only enforcement —
INSERT/DELETE/UPDATE/LOAD/DROPrejected. - Vocabulary: 10 MB upload cap; JSON-LD format auto-detection for
.jsonld/.json-ld/.json. - Annotation
O(1)lookup viaGraphSession.get_annotation(id). - Self-loop guard in
batchMergeEdgesprevents Graphology crash. - Static build artifacts removed from git;
semantica/static/added to.gitignore.
-
Ontology Hub post-review hardening (PR #518 follow-up, @KaifAhmad1):
- Registry filter:
statusparam removed fromfetchRegistry; filtering applied client-side. - Toggle/refresh URI: removed
.removesuffix()calls that corrupted URIs ending with those strings. - Format detector:
_detect_format()ReDoS eliminated —re.matchreplaced with two O(1) string ops. - Broken
fmtMapentries: addedxml/jsonmappings; unknown-extension fallback changed from|| "turtle"to?? "". - XML hardening:
_parse_rdf_sync()now routes through_safe_parse_rdf()for consistent defusedxml XXE protection. - Search: replaced O(999 999) linear scan with
GraphSearchIndex-backedsession.search().
- Registry filter:
Security
- 12 vulnerability fixes (PR security-enhancement, @KaifAhmad1):
- [CRITICAL — CWE-95] Eval injection in
media_parser.py: replacedeval(ffprobe_output)withfractions.Fraction. - [CRITICAL — CWE-502] Pickle deserialization in
agent_memory.py: replaced with JSON; legacy.pklfiles detected and refused with migration message. - [HIGH — CWE-89] SQL injection in
snowflake_ingestor.py:LIMIT/OFFSETparameterized;ORDER BYregex-validated;WHEREclauses containing semicolons rejected. - [HIGH — CWE-611] XXE in
rdf_parser.py:defusedxml.defuse_stdlib()before all RDF/XML parsing. - [HIGH — CWE-346/200] Missing security headers in
server.py:CORSMiddleware,X-Content-Type-Options,X-Frame-Options, HSTS, generic 500 handler. - [HIGH — CWE-346/400] Overpermissive CORS in
explorer/app.py: methods/headers narrowed; 64 KB WebSocket frame cap. - [MEDIUM — CWE-20] Algorithm param unconstrained in
graph.py: enum-validatedbfs|dijkstraonly. - [MEDIUM — CWE-434] RDF upload without extension check in
vocabulary.py:.ttl/.rdf/.owl/.xml/.jsonldallowlist enforced. - [MEDIUM — CWE-1336] Prompt injection in
llm_extraction.py: user-supplied content wrapped injson.dumps(). - [MEDIUM — CWE-95] Dynamic
__import__()inpipeline_validator.py: replaced with proper module-level import. - [MEDIUM — CWE-1333] ReDoS in
enrich.py: whitespace-normalize then split on literal" AND ". - [LOW — CWE-22] Path traversal in
server.pySPA route:Path.resolve().relative_to()guard; 400 on escape. - [LOW — CWE-400] Unbounded SPARQL in
sparql.py: 5 000-row cap, 30 sasyncio.wait_fortimeout,Semaphore(4)concurrency cap;SparqlResponse.truncatedfield added. - [LOW — CWE-434] Import upload in
export_import.py: 50 MB cap;{.json,.csv}allowlist. - CodeQL
paths-ignoreforcookbook/**/*.htmlto suppress false-positive JS alerts #15–18.
- [CRITICAL — CWE-95] Eval injection in
- SSRF in Ontology Hub (PR #518 follow-up):
_validate_fetch_url()rejects non-http/https schemes and resolves hostname viasocket.getaddrinfo, blocking loopback/private/link-local/multicast addresses.
[0.4.0] - 2026-04-08
Added
Temporal Intelligence (@KaifAhmad1, PRs #396–#402)
- Core Temporal Data Model (PR #396) —
semantica.kg.temporal_modelwith shared parsing/normalization/serialization helpers;TemporalBoundandBiTemporalFactexported fromsemantica.kg; valid-time and transaction-time filtering;TemporalValidationErroron invalid inputs; history-preserving revisions inTemporalVersionManager.apply_revision()with supersession semantics. - Point-in-Time Query Engine (PR #397) —
TemporalGraphQuery.reconstruct_at_time(graph, at_time)builds consistent point-in-time subgraphs without mutating source;TemporalConsistencyReportdetects inverted intervals, relationships outside entity lifetimes, overlapping same-type relationships, and temporal gaps; sequence/cycle pattern detection; calendar-aligned evolution bucketing viatemporal_granularity; causal ordering controls onfind_temporal_paths()(strict/overlap/loose). - Deterministic Temporal Reasoning Engine (PR #398) —
semantica.kg.temporal_reasoning; full Allen interval algebra viaIntervalRelation(all 13 relations);TemporalReasoningEnginewith interval merging, gap analysis, coverage calculation, timelines, retroactive coverage; zero LLM calls; circular import risk betweensemantica.reasoningandsemantica.kgeliminated. - Temporal Awareness in ContextGraph (PR #399) —
Decisiondataclass gainsvalid_from/valid_until; superseded decisions remain in graph (immutable history);find_precedents_by_scenario(include_superseded, as_of);ContextGraph.state_at(timestamp)serializable snapshot;CausalChainAnalyzer.trace_at_time(event_id, at_time);AgentContext.checkpoint(label),diff_checkpoints(),flush_checkpoint(). - Temporal Metadata Extraction from Text (PR #400):
extract_relations_llm(extract_temporal_bounds=True)— eachRelationgainsvalid_from,valid_until,temporal_confidence(0.0–1.0),temporal_source_text; defaultFalseis 100% backward-compatible.- Calibrated confidence anchors: 1.00 = full ISO date → 0.00 = no temporal signal.
TemporalNormalizer(zero LLM calls, pure regex + dateutil):normalize(value)→ UTC datetime tuple orNone;normalize_phrase(phrase)→ metadata dict orNone; 13-domain default phrase map;TemporalAmbiguityWarningfor ambiguous DD/MM/YYYY inputs (never silently guesses locale).
- Temporal Provenance & OWL-Time Export (PR #401):
ProvenanceTracker.track_entity()auto-stampsrecorded_aton every new record.query_recorded_between(start, end),revision_history(fact_id),export_audit_log(fact_ids, format)(JSON/CSV).RDFExporter.export_to_rdf(include_temporal=True, time_axis="valid|transaction|both")— emits OWL-Time triples for all temporally-annotated relationships.create_snapshot()stamps"format_version": "1.0";validate_snapshot()andmigrate_snapshot()for stable snapshot lifecycle.
- Temporal GraphRAG Integration (PR #402) —
TemporalGraphRetrieverfilters retrieved context to a point in time;ContextRetriever.query_with_reasoning(at_time, header_template)prepends structured temporal header;TemporalQueryRewriterextracts temporal intent (before/after/at/during/between) from natural language; regex-only by default, optional LLM-assisted mode.
Ontology (@KaifAhmad1 @ZohaibHassan16)
- SHACL Shape Generation & Validation (PR #318) —
SHACLGeneratorderives SHACL node/property shapes from any ontology dict; three quality tiers (basic/standard/strict); Turtle/JSON-LD/N-Triples output; iterative multi-level inheritance propagation, cycle-safe;OntologyEngine.to_shacl(),export_shacl(),validate_graph(explain=True);SHACLValidationReportwith plain-English explanations for all 7 constraint types.pip install semantica[shacl]. - SKOS Vocabulary Module (PR #319) —
TripletStore.add_skos_concept()/get_skos_concepts(scheme_uri);OntologyEngine.list_vocabularies(),list_concepts(),search_concepts();NamespaceManager.get_skos_uri()/build_concept_scheme_uri(); SPARQL injection hardened. - Ontology Alignment API (PR #361) —
OntologyEngine.create_alignment(),get_alignments(),list_alignments(); OWL/SKOS standard predicates (owl:equivalentClass, all fiveskos:*Match);ReuseManager.suggest_alignments();QueryEngine.expand_entity_uri(use_alignments=True)with SPARQLVALUESclause injection; SPARQL injection hardened. - Ontology Diff & Migration (PR #367) —
VersionManager.diff_ontologies()covering classes/properties/individuals/axioms;ChangeLogAnalyzer.analyze()classifying CRITICAL/HIGH/MEDIUM/INFO impact;ImpactReport,generate_change_report();OntologyEngine.compare_versions()end-to-end orchestrator with optional validation and graph-instance checks.
Knowledge Explorer API (@ZohaibHassan16 @KaifAhmad1)
- Full FastAPI backend (PR #384) —
semantica.explorerpackage with graph, analytics, decisions, temporal, enrichment, export/import, annotations routes; 12 export formats; WebSocket progress for import; 99 integration tests.pip install semantica[explorer]; CLI:semantica-explorer --graph my_graph.json. - Thread safety (PR #385) —
ContextGraphandGraphSessionprotected withthreading.RLock; 8 analytics components lazily initialized under lock. - In-memory fallbacks (PR #386) — All 7
DecisionQueryand 4DecisionRecordermethods haveContextGraphfallback paths for in-memory usage without a graph DB. - Snapshot schema compatibility (PR #393) — accepts both
nodes/edgesandentities/relationshipssnapshot schemas transparently; metadata counts always accurate. - Audit trail & rollback protection (PR #394) — mutation-level audit tracking, named version tags,
restore_snapshot()requires explicit confirmation,get_node_history(),diff()Git-like alias. - SKOS Vocabulary REST API (PR #426) —
GET /api/vocabulary/schemes,GET /api/vocabulary/hierarchy?scheme=<uri>with cycle detection,POST /api/vocabulary/import(.ttl/.rdf/.owl; HTTP 422 on invalid). - O(N) → O(limit) Pagination (PR #431) —
find_nodes/find_edgesuseitertools.isliceon generators; ghost-node fix (acceptssource_id/target_idandsource/targetkey names); deterministic page boundaries viasorted();stats()applies same validity filters as pagination. - Named graph support (PR #432, @Sameer6305) —
enable_named_graphsflag forwarded correctly throughTripletStore.execute_query(); duplicateFROM/FROM NAMEDclauses prevented; graph URIs percent-encoded in DROP statements.
Integrations
- Agno Agentic Framework (Issue #249, @KaifAhmad1) — 5 components, all degrading gracefully when
agnois not installed:AgnoContextStore— graph-backed agent memory implementingagno.memory.db.base.MemoryDb.AgnoKnowledgeGraph— multi-hop GraphRAG knowledge base implementingagno.knowledge.base.AgentKnowledge.AgnoDecisionKit— 6 decision-intelligence tools (record_decision, find_precedents, trace_causal_chain, analyze_impact, check_policy, get_decision_summary).AgnoKGToolkit— 7 KG pipeline tools (extract_entities, extract_relations, add_to_graph, query_graph, find_related, infer_facts, export_subgraph).AgnoSharedContext— team coordinator with single sharedContextGraph;bind_agent(role)returns role-scoped view; thread-safe viaRLock.- 110 integration tests; 3 cookbook notebooks.
pip install semantica[agno].
- Novita AI Provider (PR #374, @Alex-wuhu) — OpenAI-compatible; default model
deepseek/deepseek-v3.2;NOVITA_API_KEY;create_provider("novita").
Reasoning
- Native Datalog Reasoning Engine (PR #371, @ZohaibHassan16) — pure-Python bottom-up semi-naive fixpoint with guaranteed termination; recursive Horn clause rules (e.g.
ancestor(X,Y) :- parent(X,Z), ancestor(Z,Y).); O(1) delta-index lookup;load_from_graph(ContextGraph);query("pred(?X, ?Y)")with optionalbindings=;DatalogReasoner,DatalogFact,DatalogRuleexported fromsemantica.reasoning.
Fixed
- Pattern Matcher restored (PR #387, @ZohaibHassan16) — dead code silently overwrote
_match_patternregex (pre-bound variable embedding, repeated-variable backreferences) withre.escape, breaking transitivity/symmetry/self-join rules; removed.re.errornow surfaced instead of swallowed. - OllamaProvider base_url ignored (PR #408, @AlexeyMyslin) —
ollama.Client(host=self.base_url)instead of raw module assignment; remote Ollama servers now reachable. - spaCy runtime fallback —
NERExtractornow catches runtime initialization failures, not just missing-model errors. - CentralityCalculator crash —
_build_adjacency()handles both ContextGraph dataclass edges (source_id/target_id) and plain dicts. find_pathalways used BFS (PR #384) — algorithm query param now correctly dispatched todijkstra_shortest_pathorbfs_shortest_path.- Event loop blocked in
/api/enrich/links(PR #385) —score_linkscoring loop wrapped inasyncio.to_thread. - Temp file leak in
export_graph(PR #384) —try/finallycleanup for all error paths. ChangeCategoryenum typo (PR #367) —"potenitally_breaking"→"potentially_breaking".- DecisionQuery/DecisionRecorder fallbacks (PR #386) —
type()guard instead ofisinstance()for Mock safety; flat property storage in_store_decision_node; spuriousproperties={}kwarg removed; tz-aware/naive datetime mismatch resolved;find_edges()hoisted out of BFS loop (O(nodes×edges) → O(1) per call). - Snapshot schema (PR #393) — silent restore failures when
nodes/edgesschema didn't match legacyentities/relationshipsexpectations. - Context explainability (@KaifAhmad1) — decision nodes now store full
scenario/reasoningtext; causal/precedent reconstruction returns enrichedDecisionobjects;PolicyEngine.get_affected_decisions()consistent across Cypher and fallback branches.
Security
- CWE-312/359/532 — Removed
api_keydebugprintblocks fromrelation_extractor.pyandtriplet_extractor.py. - CWE-20 — URL sanitization:
"url" in urlsreplaced withany(url == "url" for url in urls), eliminating substring match. - CI overpermissions —
permissions: contents: readadded tobenchmark.ymlandsecurity.yml. - SHACL path traversal (PR #318) — replaced
len < 500 and "\n" not in sheuristic withos.path.exists(). - SHACL inheritance mutation (PR #318) —
_propagate_inheritanceusesdataclasses.replace()instead of appending parentPropertyShapeobjects by reference. - SPARQL injection (PR #361) —
search_concepts,list_alignments,build_values_clausefully hardened.
[0.3.0] - 2026-03-10
Added
- Context Graph Feature Completeness (@KaifAhmad1):
ContextNode/ContextEdgegainvalid_from/valid_untilwithis_active(at_time) -> bool.ContextGraph.find_active_nodes(node_type, at_time)— temporal node filtering.get_neighbors(min_weight)— confidence-filtered BFS (default 0.0 passes all edges).link_graph()/navigate_to()/resolve_links(registry)— cross-graph navigation with full save/load round-trip.graph_idUUID field persisted to JSON.
Fixed
is_active()tz-aware/naive datetime normalization.valid_from/valid_untilserialization inadd_nodes(),add_edges(),to_dict(),from_dict().- Cross-graph link phantom-node prevention in
link_graph(). pipeline_builder.add_step()return type annotation.test_hybrid_search_performancetiming computation; threshold raised to < 5.0 s.- ProvenanceTracker added to
semantica/kg/__init__.pyexports. - Duplicate relation creation in
_parse_relation_result— orphaned legacy block removed. extraction_methodparameter added; typed path now correctly sets"llm_typed".- Cross-test cache pollution in
test_retry_logic.py—_result_cache.clear()added tosetUp(). - 14 tests in
tests/context/test_cross_graph_navigation.py; 85 real-world tests intests/test_030_realworld_comprehensive.py.
[0.3.0-beta] - 2026-03-07
Added
- Multi-Founder LLM Extraction (PR #354, @KaifAhmad1):
_parse_relation_result: unmatched subjects/objects produce a syntheticUNKNOWNentity instead of being silently dropped._match_patternrewritten: splits on?varplaceholders, pre-bound variable resolution, repeated-variable backreferences.
- TTL Export Aliases (PR #355, @KaifAhmad1) —
format="ttl"/"nt"/"xml"/"rdf"/"json-ld"resolve correctly before format validation; 8 tests intests/export/test_rdf_exporter.py. - Incremental/Delta Processing (PR #349, @ZohaibHassan16) — native delta computation between graph snapshots via SPARQL, delta-aware pipeline execution (
delta_mode), snapshot retention withprune_versions(), significant performance improvements for near real-time pipelines. - Deduplication v2:
- Candidate Generation v2 (PR #338, @ZohaibHassan16) — multi-key blocking, phonetic (Soundex) blocking, deterministic candidate budgeting; 63.6% faster (0.259 s → 0.094 s for 100 entities).
- Two-Stage Scoring Prefilter (PR #339, @ZohaibHassan16) — type mismatch, length ratio, token overlap gates; 18–25% faster batch processing.
- Semantic Relationship Deduplication v2 (PR #340, @ZohaibHassan16) — predicate synonym mapping (
works_for→employed_by), O(1) hash matching, weighted scoring (60% predicate + 40% object); 6.98x speedup (~83 ms vs ~579 ms). - Migration Guide (PR #344, @ZohaibHassan16) — comprehensive MIGRATION_V2.md; critical infinite recursion bug in
dedup_triplets()fixed.
- ArangoDB AQL Export (PR #342, @tibisabau) — AQL INSERT generation, configurable collections, batch processing (default 1 000),
.aqlauto-detection, 17 tests. - Apache Parquet Export (PR #343, @tibisabau) — columnar storage, configurable compression (snappy/gzip/brotli/zstd/lz4/none), explicit Arrow schemas,
.parquetauto-detection, 25 tests.
Fixed
- Test Suite Fixes (@KaifAhmad1):
- Context: entity extraction gated on
use_hybrid_search=True;_extract_entities_from_queryusesword[0].isupper(); addedexpand_contextBFS method;hybrid_retrievalandmulti_hop_context_assemblycorrected; vector result fallback tometadata["content"]. - KG:
calculate_pagerankaliases;community_detector._to_networkxno longer silently loses edges;_build_adjacencyhandles both"edges"and"relationships"keys; 9 tracking methods added toAlgorithmTrackerWithProvenance. - Pipeline: retry loop honours
max_retries;FailureHandler.handle_failure()added;add_stepreturn type fixed;validatealias added; error message standardized. - Tests: emoji replaced with ASCII for Windows cp1252 compatibility.
- Context: entity extraction gated on
NameError: missingTypeimport inutils/helpers.py.
[0.3.0-alpha] - 2026-02-19
Added
- Decision Tracking System — complete lifecycle management (record → analyze → query → precedent → influence) with audit trails and provenance tracking.
- Advanced KG Algorithms — Node2Vec embeddings, centrality analysis, community detection for decision insights.
- Enhanced Context Module — unified
AgentContextwith granular feature flags for decision tracking, KG algorithms, and vector store features. - Vector Store Features — hybrid search combining semantic, structural, and category similarity.
- Policy Management — versioning, compliance checking, and exception handling.
- Context Engineering Enhancement (PR #307, @KaifAhmad1) — full decision tracking, hybrid search,
PolicyExceptionmodel,GraphStorevalidation, explainable AI features, 9 critical bug fixes, 100% test coverage (9/9). - PgVector Store Support (PR #303, @Sameer6305 @KaifAhmad1) — HNSW/IVFFlat indexing, JSONB metadata filtering, psycopg3/psycopg2 fallback, SQL injection protection via
psycopg_sql.SQL(), 36+ tests. - Apache AGE Backend (PR #311, @Sameer6305) —
AgeStorewithGraphStoreAPI compatibility, SQL injection protection. - Improved Vector Store for Decision Tracking (PR #293, @KaifAhmad1) —
DecisionEmbeddingPipeline,HybridSimilarityCalculator(0.7 semantic + 0.3 structural),DecisionContext,ContextRetrieverwith multi-hop reasoning; 34+ tests. - Improved Graph Algorithms (PR #292, @KaifAhmad1) — 30+ algorithms across 7 categories (Node2Vec, Dijkstra, A*, PageRank, Louvain, Leiden, etc.), unified provenance tracking with
GraphBuilderWithProvenance/AlgorithmTrackerWithProvenance. - ResourceScheduler Deadlock Fix (PRs #299 #301, @d4ndr4d3 @KaifAhmad1) —
threading.Lock→threading.RLock; allocation validation; leak prevention on failure; 6 regression tests. - Dependabot & Security Automation — bi-weekly security updates, automated Bandit/Safety/Semgrep scans, security-critical package grouping.
Fixed
- Context Graphs decision tracking bugs (PR #315, @KaifAhmad1): empty/
Nonedecision ID,Nonemetadata, causal chain depth logic, nonexistent node handling,to_dict/from_dictround-trip. PolicyEnginelatest version selection;AgentContextfallback robustness and secure logging.- Import issues in test suite (ProvenanceTracker location); causal analyzer
max_depthbounds.
[0.2.7] - 2026-02-09
Added
- Snowflake Connector (PR #276, @Sameer6305) — multi-auth (password/OAuth/key-pair/SSO), table and query ingestion, SQL injection prevention, progress tracking, 24 tests.
pip install semantica[db-snowflake]. - Apache Arrow Export (PR #273, @Sameer6305) — explicit Arrow schemas, entity/relationship export, Pandas/DuckDB compatible, 20 tests.
- Benchmark Suite (PR #289, @ZohaibHassan16 @KaifAhmad1) — 137+ benchmarks across all 10 modules, Z-score statistical regression detection, GitHub Actions workflow. CLI:
python benchmarks/benchmark_runner.py.
[0.2.6] - 2026-02-03
Added
- W3C PROV-O Provenance Tracking (Issues #254 #246, @KaifAhmad1):
- Comprehensive provenance across all 17 Semantica modules; InMemory/SQLite backends; SHA-256 integrity.
- FDA 21 CFR Part 11, SOX, HIPAA, TNFD compliance infrastructure.
- 237 tests; opt-in (
provenance=Falseby default).
- Enhanced Change Management (Issues #248 #243, @KaifAhmad1):
TemporalVersionManagerandOntologyVersionManagerwith SQLite/in-memory backends; SHA-256 checksums; detailed diffs.- 104 tests; 17.6 ms for 10 k entities; 510+ ops/sec concurrent.
- CSV Ingestion Enhancements (PR #244, @saloni0318) — auto-detect encoding (chardet) and delimiter (csv.Sniffer); tolerant decoding; optional chunked reading.
- Ingest Unit Tests (Issues #239 #232, @Mohammed2372) — file, web, and feed ingestors; 998 lines of tests; 80–86% coverage.
- TextNormalizer comprehensive unit tests (PR #242, @ZohaibHassan16).
Fixed
- Temperature Compatibility (Issues #256 #252, @F0rt1s @IGES-Institut) —
temperature=Nonenow omits parameter so APIs use model defaults;_add_if_sethelper applied to all 5 providers; 10 tests. - JenaStore Empty Graph (Issues #257 #258, @ZohaibHassan16) —
if self.graph is None:replaces implicit falsy check in 5 methods.
[0.2.5] - 2026-01-27
Added
- Pinecone Vector Store (closes #219 #220) — serverless and pod-based indexes, namespace support, metadata filtering, unified
VectorStoreintegration. - Configurable LLM Retry Logic —
max_retriesparameter (default 3) inNERExtractor,RelationExtractor,TripletExtractor, and allextract_*_llmmethods. - Bring Your Own Model (BYOM) — custom HuggingFace models in all extractors; custom tokenizer support; runtime
model=overrides config defaults. - Enhanced NER — configurable aggregation strategies (simple/first/average/max); IOB/BILOU parsing for raw model outputs; confidence scoring.
- Relation Extraction — entity marker technique (
<subj>/<obj>tags) for sequence classification models; structured output parsing. - Triplet Extraction — Seq2Seq model support (REBEL) for direct structured triplet generation from text.
Fixed
- LLM extraction: strict
max_retriesenforcement prevents infinite retry loops. - Model parameter precedence: runtime arguments now correctly override config defaults in HuggingFace extractors.
- Circular imports in test suites.
[0.2.4] - 2026-01-22
Added
- Ontology Ingestion Module —
OntologyIngestorfor Turtle/RDF-XML/JSON-LD/N3 files;ingest_ontology()convenience function; recursive directory scanning;OntologyDatadataclass; integrated intoingest(source_type="ontology").
[0.2.3] - 2026-01-20
Added
- Amazon Neptune dev environment — CloudFormation template;
cfn-lintin pre-commit. - Vector Store high-performance ingestion —
VectorStore.add_documents()with batching and parallel processing (max_workers=6);VectorStore.embed_batch()helper. - LLM relation extraction tests (mocked and Groq integration).
Changed
- Simplified relation extraction parameter interface; improved error handling and verbose logging.
- Standardized
VectorStoreconcurrency defaults; implicitmax_workers=6in examples.
Fixed
- LLM Relation Extraction Parsing — normalized typed responses to consistent dict format before parsing; structured JSON fallback; extra kwargs removed from internals.
- Pipeline Circular Import (Issues #192 #193) — lazy-loaded
PipelineValidatorinsidePipelineBuilder.__init__;TYPE_CHECKINGguard. - JupyterLab Progress (Issue #181) —
SEMANTICA_DISABLE_JUPYTER_PROGRESSenv var suppresses rich progress tables.
[0.2.2] - 2026-01-15
Added
- Parallel Extraction Engine —
concurrent.futures.ThreadPoolExecutoracross all extractors (NERExtractor,RelationExtractor,TripletExtractor,EventDetector,SemanticNetworkExtractor);max_workersparameter; thread-safeProgressTracker. - Semantic extract regression suite; real-use-case benchmark script.
Changed
- Gemini SDK Migration —
google-genaiSDK withgoogle.generativeaifallback. - Pinned
opentelemetry-api/-sdkto 1.37.0; updatedprotobuf/grpcioconstraints. - Entity filtering applied only to LLM prompt construction, not non-LLM flows.
- Raised global
optimization.max_workersdefault to 8.
Security
- Credential sanitization — hardcoded API keys removed from 8 notebooks;
ExtractionCacheexcludesapi_key/token/passwordfrom cache keys; cache key hashing upgraded MD5 → SHA-256.
Performance
- ~1.89× speedup via parallel extraction (Groq
llama-3.3-70b-versatile, standard datasets). - Optimized entity matching: exact/substring/word-boundary fast paths before embedding similarity.
[0.2.1] - 2026-01-12
Fixed
- LLM Output Stability (Bug #176) — correct
max_tokenspropagation; automatic chunk-halving and retry on context/output limit errors. - Removed hardcoded
max_lengthconstraints fromEntity,Relation,Triplet. - Orchestrator lazy property initialization and configuration normalization.
AssertionErrorin orchestrator tests (mock alignment).- Pinned
protobuf>=5.29.1,<7.0,grpcio>=1.71.2; addedGitPythonandchardettopyproject.toml.
Changed
- Increased default
max_text_lengthto 64 000 characters for all major providers. - Standardized Groq defaults:
llama-3.3-70b-versatile, 64 k context, nativemax_tokens/max_completion_tokens.
[0.2.0] - 2026-01-10
Added
- Amazon Neptune Support —
AmazonNeptuneStorevia Bolt/OpenCypher;NeptuneAuthTokenManagerwith AWS IAM SigV4 signing; retry/backoff.pip install semantica[graph-amazon-neptune]. - Docling Integration —
DoclingParserfor PDF/DOCX/PPTX/XLSX/HTML/image parsing; OCR support; Markdown/HTML/JSON export. - Robust Extraction Fallbacks — ML/LLM → Pattern → Last Resort chains across all extractors.
- Provenance & Tracking —
batch_indexanddocument_idmetadata on all extracted items. - Semantic Extract — auto-chunking for long text;
silent_failparameter; JSON parsing with 3-attempt exponential backoff. - End-to-end KG pipeline integration tests;
TextEmbeddermodel switching tests.
Changed
- Removed internal dedup logic from extractors (deferred to
semantica/conflicts). - Standardized batch processing across all extractors using unified
extract/analyze/resolvepattern. - Clarified weighted confidence scoring (50% Method Confidence + 50% Type Similarity).
Fixed
NameErrorinextraction_validator.py(missingUnionimport).- Extractors returning empty lists for valid input when primary methods fail.
- Model switching bug in
TextEmbedder(state not cleared on model switch). (Issue #160) TypeError: unhashable type: 'Entity'inGraphAnalyzer. (Issue #159)- Pinned
protobuf==4.25.3,grpcio==1.67.1. TripletExtractor.validate_tripletsshadowed by internal attribute.- Incorrect
TextSplitterimport path.
[0.1.1] - 2026-01-05
Added
- Exported
DoclingParserandDoclingMetadatafromsemantica.parse. - Windows-specific troubleshooting note for PyTorch DLL issues.
Fixed
DoclingParserimport/export across platforms (Windows, Linux, Google Colab).- Error messaging when optional
doclingdependency is missing. - Versioning inconsistencies across the framework.
[0.1.0] - 2025-12-31
Added
- Command-line interface (
semanticaCLI) with knowledge base building and info commands. - FastAPI-based REST API server for remote access.
- Background worker component for scalable task processing.
- Framework-level versioning configuration for PyPI distribution.
- Automated release workflow with Trusted Publishing support.
Changed
- Updated versioning across the framework to 0.1.0.
- Refined entry point configurations in
pyproject.toml. - Improved lazy module loading for core components.
[0.0.5] - 2025-11-26
Changed
- Configured Trusted Publishing for secure automated PyPI deployments.
[0.0.4] - 2025-11-26
Changed
- Fixed PyPI deployment issues from v0.0.3.
[0.0.3] - 2025-11-25
Added
- Comprehensive issue templates (Bug, Feature, Documentation, Support, Grant/Partnership).
- Updated pull request template with clear guidelines.
- Community support documentation (
SUPPORT.md). - Funding and sponsorship configuration (
FUNDING.yml). - 10+ domain-specific cookbook examples (Finance, Healthcare, Cybersecurity, etc.).
Changed
- Simplified CI/CD workflows — removed failing tests and strict linting.
- Combined release and PyPI publishing into single workflow.
- Simplified security scanning to weekly pip-audit only.
Removed
- Redundant scripts folder (8 shell/PowerShell scripts).
- Unnecessary automation workflows (label-issues, mark-answered).
- Excessive issue templates.
[0.0.2] - 2025-11-25
Changed
- Updated README with streamlined content and better examples.
- Added more notebooks to cookbook.
- Improved documentation structure.
[0.0.1] - 2024-01-XX
Added
- Core framework architecture.
- Universal data ingestion (multiple file formats).
- Semantic intelligence engine (NER, relation extraction, event detection).
- Knowledge graph construction with entity resolution.
- 6-stage ontology generation pipeline.
- GraphRAG engine for hybrid retrieval.
- Multi-agent system infrastructure.
- Production-ready quality assurance modules.
- Comprehensive documentation with MkDocs.
- Cookbook with interactive tutorials.
- Multiple vector store backends (Weaviate, Qdrant, FAISS).
- Multiple graph database backends (Neo4j, NetworkX, RDFLib).
- Temporal knowledge graph support.
- Conflict detection and resolution; deduplication and entity merging.
- Schema template enforcement; seed data management.
- Multi-format export (RDF, JSON-LD, CSV, GraphML).
- Visualization tools; pipeline orchestration.
- Streaming support (Kafka, RabbitMQ, Kinesis).
- Context engineering for AI agents; reasoning and inference engine.
Types of Changes
| Label | Meaning |
|---|---|
| Added | New features |
| Changed | Changes in existing functionality |
| Deprecated | Soon-to-be removed features |
| Removed | Removed features |
| Fixed | Bug fixes |
| Security | Vulnerability fixes |
| Performance | Performance improvements |
For detailed release notes, see GitHub Releases.