# Temporal Data Model (Revision-Aware RDF)

The Temporal Data Model enables revision tracking of UKO knowledge graph nodes
across three storage tiers (hot/warm/cold). When code changes, existing nodes
are **not deleted** -- they are marked historical and a new revision node is
created with a back-link to the predecessor.

Based on `docs/specification.md` lines 41940--42499.

## Architecture

```
Code Change ──► Analyzer ──► TemporalService.create_revision()
                                    │
                         ┌──────────┴───────────┐
                         │  TemporalBackend     │
                         │  ┌─────────────────┐ │
                         │  │ Old node:       │ │
                         │  │  isCurrent=false│ │
                         │  │  validUntil=now │ │
                         │  ├─────────────────┤ │
                         │  │ New node:       │ │
                         │  │  isCurrent=true │ │
                         │  │  isRevisionOf=  │ │
                         │  │    old_uri      │ │
                         │  └─────────────────┘ │
                         └──────────────────────┘
```

## Domain Models

All models are frozen Pydantic `BaseModel` instances (immutable value objects).

### TemporalMetadata

Temporal fields carried by every UKO InformationUnit. Maps to the OWL
temporal properties defined in UKO Layer 0.

| Field | Type | Default | Spec Property |
|-------|------|---------|---------------|
| `valid_from` | `datetime` (UTC) | **required** | `uko:validFrom` |
| `valid_until` | `datetime \| None` | `None` | `uko:validUntil` |
| `is_current` | `bool` | `True` | `uko:isCurrent` |
| `is_revision_of` | `str \| None` | `None` | `uko:isRevisionOf` |

### TemporalNode

A UKO InformationUnit with full temporal metadata and provenance.

| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `node_uri` | `str` | **required** | Unique UKO URI (e.g., `uko-py:class/Auth_v2`) |
| `source_resource` | `str` | **required** | ULID of originating resource |
| `source_path` | `str` | **required** | File path within the resource |
| `source_range` | `str \| None` | `None` | Source range (e.g., `"15:1-87:0"`) |
| `temporal` | `TemporalMetadata` | **required** | Embedded temporal metadata |

### RevisionChain

Ordered chain of node versions for a single UKO concept.

| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `current_uri` | `str` | **required** | URI of the current head node |
| `predecessors` | `tuple[str, ...]` | `()` | Predecessor URIs (oldest-first) |

Properties: `depth` (total versions), `all_uris` (predecessors + current).

### TierRetentionConfig

Retention policy for warm and cold tiers.

| Field | Type | Default | Config Key |
|-------|------|---------|------------|
| `warm_retention_hours` | `int` (≥1) | `24` | `context.tiers.warm.retention-hours` |
| `cold_retention_days` | `int` (≥1) | `90` | `context.tiers.cold.retention-days` |

### TierQueryResult

Result from a tier-aware temporal query.

| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `nodes` | `tuple[TemporalNode, ...]` | `()` | Matching nodes |
| `tier` | `ContextTier` | **required** | HOT / WARM / COLD |
| `temporal_scope` | `TemporalScope` | **required** | CURRENT / RECENT / ALL |

## TemporalBackend Protocol

```python
class TemporalBackend(Protocol):
    def store_node(self, node) -> None: ...
    def create_revision(self, current_uri, new_node, timestamp) -> TemporalNode: ...
    def get_current(self, node_uri_base) -> TemporalNode | None: ...
    def get_history(self, node_uri_base, temporal_scope) -> tuple[TemporalNode, ...]: ...
    def get_revision_chain(self, node_uri) -> RevisionChain: ...
    def query_by_tier(self, tier, temporal_scope, retention) -> TierQueryResult: ...
    def mark_historical(self, node_uri, timestamp) -> TemporalNode: ...
```

## Three Storage Tiers

| Tier | Content | Temporal Filter | Retention |
|------|---------|-----------------|-----------|
| **Hot** | Current UKO graph | `isCurrent = true` only | Until removed |
| **Warm** | Recent context | Current + recently-expired | `warm_retention_hours` (24h) |
| **Cold** | All historical | All temporal versions | `cold_retention_days` (90d) |

## Revision Chain Mechanics

1. Old node: `is_current` → `False`, `valid_until` → timestamp
2. New node: `is_current` → `True`, `valid_from` → timestamp
3. New node: `is_revision_of` → old node URI
4. Old node's `valid_from` is preserved unchanged

## v1 Limitations

- `InMemoryTemporalBackend` stores all nodes in a `dict`; no persistence.
- No SPARQL query helpers yet (deferred to graph backend integration).
- `temporal-archaeology` strategy stub exists but does not yet use the
  temporal backend (wiring deferred to strategy integration milestone).
