diff --git a/CHANGELOG.md b/CHANGELOG.md index 78138cc4..3d9e94ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,6 +43,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- **`ProvenanceManager` tracking methods silently swallowed failures without logging and returned fabricated entries** (#783) + - `track_relationship()`, `track_chunk()`, and `track_property_source()` now return `Optional[ProvenanceEntry]` (`None` on storage failure, consistent with #782's `track_entity` fix) instead of a fabricated populated object + - `_save_entry()` now always logs on any storage failure, including previously-silent per-item batch failures + - `track_entities_batch()` and `track_chunks_batch()`'s rare block-level transaction failures are now logged too + - `source_tracker.py`'s `track_sources_batch()` no longer counts failed tracking calls in its stats + - **`ProvenanceManager.track_entity` persisted partial history and returned fabricated entries on storage failure** (#782, #816) by @Sameer6305 and @KaifAhmad1 - `track_entity()`'s two-step write (history archive + primary update) is now atomic — if either write fails, the whole operation rolls back via the existing #807 `transaction()` mechanism, instead of silently persisting a partial state - `track_entity()`'s return type is now `Optional[ProvenanceEntry]`: on failure it returns a safe deep copy of the pre-failure existing entry (if one existed) or `None` (if this was a brand-new, never-successfully-tracked entity) — never a fabricated object claiming values that were never actually persisted diff --git a/docs/reference/provenance.md b/docs/reference/provenance.md index 5c1cf3bb..6a7e4ced 100644 --- a/docs/reference/provenance.md +++ b/docs/reference/provenance.md @@ -222,9 +222,9 @@ cleared = manager.clear() | Method | Returns | Description | | :------ | :------- | :----------- | | `track_entity(entity_id, source, metadata, **kwargs)` | `Optional[ProvenanceEntry]` | Record entity provenance atomically; returns `ProvenanceEntry` on success, or `None`/existing entry on storage failure | -| `track_relationship(relationship_id, source, metadata, **kwargs)` | `ProvenanceEntry` | Record relationship provenance | -| `track_chunk(chunk_id, source_document, ...)` | `ProvenanceEntry` | Record chunk provenance with char offsets | -| `track_property_source(entity_id, property_name, value, source)` | `ProvenanceEntry` | Record property-level source attribution | +| `track_relationship(relationship_id, source, metadata, **kwargs)` | `Optional[ProvenanceEntry]` | Record relationship provenance; returns `ProvenanceEntry` on success, or `None` on storage failure | +| `track_chunk(chunk_id, source_document, ...)` | `Optional[ProvenanceEntry]` | Record chunk provenance with char offsets; returns `ProvenanceEntry` on success, or `None` on storage failure | +| `track_property_source(entity_id, property_name, value, source)` | `Optional[ProvenanceEntry]` | Record property-level source attribution; returns `ProvenanceEntry` on success, or `None` on storage failure | | `track_entities_batch(entities, source)` | `int` | Batch-track entities; returns success count | | `track_chunks_batch(chunks, source_document)` | `int` | Batch-track chunks; returns success count | | `get_lineage(entity_id)` | `Dict[str, Any]` | Full lineage as aggregated dict | @@ -236,7 +236,7 @@ cleared = manager.clear() ## ProvenanceEntry Fields -`ProvenanceEntry` is the core dataclass. Every tracking method returns one: +`ProvenanceEntry` is the core dataclass. Every tracking method returns one on success (or `None` on storage failure): ```python from semantica.provenance import ProvenanceEntry