# UKO Provenance Tracking

**Package:** `cleveragents.application.services` (UKOIndexer, UKOGraphPersistence)
**Introduced:** v3.8.0 (issue #891)

This module guide covers the provenance tracking and temporal versioning
capabilities added to the UKO runtime in v3.8.0.  Provenance metadata
enables audit trails, point-in-time queries, and historical analysis of
the knowledge graph.

For the full UKO runtime API reference, see
[`docs/reference/uko_runtime.md`](../reference/uko_runtime.md).
For the temporal data model, see
[`docs/reference/temporal_data_model.md`](../reference/temporal_data_model.md).

---

## Purpose

Every typed triple produced by `UKOIndexer.index_graph()` now carries three
provenance fields:

| Field | OWL Property | Type | Description |
|-------|-------------|------|-------------|
| `sourceResource` | `uko:sourceResource` | `str` (ULID) | The CleverAgents resource that produced this triple |
| `validFrom` | `uko:validFrom` | `datetime` (UTC) | When this triple version became valid |
| `isCurrent` | `uko:isCurrent` | `bool` | Whether this is the current (non-superseded) version |

These fields are defined in UKO Layer 0 (see
[`docs/reference/uko.md`](../reference/uko.md#layer-0--universal-foundation-uko)).

---

## How Provenance Is Attached

When `UKOIndexer.index_graph()` runs, it calls `attach_provenance()` on
every triple before storing it in the graph backend:

```python
# Conceptual flow (simplified)
triples = analyzer.analyze(content, resource_uri)
implicit = inference_engine.infer(triples)
all_triples = triples + implicit

for triple in all_triples:
    provenanced = ProvenancedTriple(
        triple=triple,
        provenance=ProvenanceMetadata(
            source_resource=resource.id,
            source_path=resource.location,
            valid_from=datetime.now(UTC),
            is_current=True,
        ),
    )
    graph_backend.add_triple(project, provenanced)
```

---

## Revision Chain

A revision chain tracks the full history of ontology state across indexing
runs.  When a resource is re-indexed:

1. All existing triples for that resource are marked `isCurrent=False` and
   `validUntil` is set to the re-indexing timestamp.
2. New triples are created with `isCurrent=True` and a fresh `validFrom`.
3. A new revision entry is appended to the chain, linked to the previous
   revision.

This enables point-in-time queries: "what did the knowledge graph look like
at timestamp T?"

```
Revision 1 (2026-03-01)  →  Revision 2 (2026-03-15)  →  Revision 3 (2026-04-01)
   isCurrent=False            isCurrent=False              isCurrent=True
   validUntil=2026-03-15      validUntil=2026-04-01        validUntil=None
```

---

## Querying Provenance

### Current Triples Only

```python
from cleveragents.application.services.uko_query_interface import UKOQueryInterface

query = UKOQueryInterface(graph_backend=backend, project="local/my-app")

# Classify a resource using only current triples
result = query.classify_resource("01HXYZ...")
# result.layer, result.primary_type, result.all_types, result.implicit_relations
```

### Historical Triples (Point-in-Time)

```python
from datetime import datetime, timezone
from cleveragents.application.services.uko_persistence import UKOGraphPersistence

persistence = UKOGraphPersistence(
    graph_backend=backend,
    project="local/my-app",
    persist_path=Path("~/.config/cleveragents/uko-graph.json").expanduser(),
)

# Query triples valid at a specific point in time
historical = persistence.query_at_time(
    timestamp=datetime(2026, 3, 15, tzinfo=timezone.utc)
)
```

### Triples by Source Resource

```python
# Get all triples (current and historical) from a specific resource
triples = graph_backend.query(
    project="local/my-app",
    sparql="""
    SELECT ?s ?p ?o WHERE {
        ?s ?p ?o .
        ?s uko:sourceResource "01HXYZ..." .
    }
    """
)
```

---

## Persistence Format

`UKOGraphPersistence` serialises the full graph (including provenance) to
JSON.  The format includes all triples with their provenance metadata:

```json
{
    "version": "1",
    "project": "local/my-app",
    "revision": 3,
    "triples": [
        {
            "subject": "uko-py:class/AuthService",
            "predicate": "rdf:type",
            "object": "uko-oo:Class",
            "provenance": {
                "source_resource": "01HXYZ...",
                "source_path": "src/auth/service.py",
                "source_range": "15:1-87:0",
                "valid_from": "2026-04-01T10:00:00Z",
                "valid_until": null,
                "is_current": true
            }
        }
    ]
}
```

---

## BDD Coverage

The provenance tracking feature is covered by 47 BDD scenarios in
`features/uko_provenance.feature`:

- Typed triple provenance fields (`sourceResource`, `validFrom`, `isCurrent`)
- Temporal queries (point-in-time, current-only, historical)
- Revision chain creation and traversal
- Graph persistence with provenance (save/restore round-trip)
- Re-indexing marks previous triples as historical

---

## Gotchas

- **Naive datetimes are rejected.** All `validFrom` and `validUntil` values
  must be timezone-aware UTC datetimes. Passing a naive `datetime` raises
  `ValueError`.
- **Re-indexing is not atomic.** If `index_graph()` fails partway through,
  some triples may be marked historical while their replacements were not
  yet written. The indexer logs a warning in this case; re-running
  `reindex_resource()` will repair the state.
- **In-memory backend has no persistence.** The `InMemoryGraphIndexBackend`
  used in tests does not persist across process restarts. Use
  `UKOGraphPersistence` with a file backend in production.

---

## Related Documentation

- [UKO Runtime Services](../reference/uko_runtime.md) — full API reference
- [UKO Ontology](../reference/uko.md) — four-layer ontology and provenance property definitions
- [UKO Indexer](../reference/uko_indexer.md) — `ProvenanceMetadata` and `ProvenancedTriple` types
- [Temporal Data Model](../reference/temporal_data_model.md) — `TemporalNode`, `RevisionChain`, tier storage
- [ACMS](../reference/acms.md) — how provenance feeds into context assembly
