---
type: Axis
title: temporality (history/time axis)
description: The temporality architecture axis — the history/time ladder on a persisted entity (none → created_at → +updated_at → +deleted_at → append-log → bitemporal). Authored in RFC #75 5A.1 as the worked exemplar exercising the §6 entry schema end-to-end, then carried into 5A.2 as the lead DEEP axis (temporality + schema-authority are the richest scars).
tags: [axis, architecture, temporality, history, time, rfc-75, deep]
timestamp: 2026-06-18T00:00:00Z
---

# Temporality (history/time axis)

> **DEEP axis (5A.1 exemplar → 5A.2 lead).** Authored in 5A.1 to exercise the §6
> entry schema end-to-end, and carried into 5A.2 as the lead deep axis. The full
> six-rung ladder + the skill's own climb are below; it is the schema template
> the other fifteen axes mirror.

## Concept

Temporality is the **history/time** axis on a persisted **entity** (RFC #75
§3.1): a ladder from no stamping at all, through creation time, mutation time,
soft deletion, an append-only change log, up to full bitemporal modelling
(valid-time × transaction-time). Its **commitment surface is recorded-history**
(§4): the past you never stamped is **unrecoverable** — you cannot backfill a
`created_at` you never wrote. Its **scope is per-entity-with-default** (§5):
applied by the standing policy "stamp every persisted row," with the
write-once-at-genesis maintenance discipline the skill got exactly wrong in
#604/#698 (a regenerator clobbered genesis timestamps).

## Why it is an axis (discriminator test — §2)

1. **Ordered tiers** — each rung strictly adds temporal capability.
2. **Cross-cutting bundle** — a rung ripples through schema columns, the
   write-path stamping discipline, the regenerator (must not clobber), and tests.
3. **Asymmetric migration cost** — adding `created_at` to a live table is cheap
   for *new* rows but the history before the column existed is gone forever; a
   change-log retrofit cannot reconstruct un-logged mutations.
4. **Requirement-keyed selection** — falsifiable probes (below) pick the rung.

## Tiers (the ladder)

| Tier | Adds | Commitment surface | Forecloses next-cost |
|---|---|---|---|
| `none` | nothing | — | trivial → add a column |
| `created_at` | genesis stamp | recorded-history | low; backfill is a lie for old rows |
| `updated_at` | last-mutation stamp | recorded-history | low; loses intermediate mutations |
| `deleted_at` | soft delete | persistent-data-schema | medium; every read must filter |
| `append-log` | full change log | recorded-history | high; rewrites every write path |
| `bitemporal` | valid-time × txn-time | persistent-data-schema | very high; dual-axis queries everywhere |

## Schema view (the §6 entry — validated by `axis_catalog`)

```yaml
id: temporality
kind: architecture-axis
scope: per-entity-with-default
default_tier: created_at_updated_at
maintenance_discipline: >-
  created_at is write-once at genesis; updated_at is rewritten on every
  mutation; a projection/regenerator must NEVER clobber genesis (the
  #604/#698 scar). Per-entity deviations live in the entity record's
  axis-materialization facet, not in the standing policy.
selection:
  probes:
    - "Does any requirement ask 'when was this created'? → created_at."
    - "Does any requirement ask 'when did this last change'? → updated_at."
    - "Must deletions be recoverable / auditable? → deleted_at (soft delete)."
    - "Must every intermediate state be reconstructable? → append-log."
    - "Must we query 'what did we believe at time T about state at time S'? → bitemporal."
composes_with:
  - "provenance = principal × temporality (created_by/updated_by)"
  - "per-tenant retention = tenancy × temporality"
  - "projection-vs-canonical drift = schema-authority × temporality"
tiers:
  - tier: none
    commitment_delta:
      surface: persistent-data-schema
      delta: "no temporal columns; the entity has no recorded time at all."
    admin_tool: "none"
    enforcement: []
    tests: []
    forecloses: "any later 'when' question about rows written at this tier."
    migration_cost_to_next: "trivial — add a nullable created_at column."
  - tier: created_at
    commitment_delta:
      surface: recorded-history
      delta: "a write-once genesis timestamp stamped at row creation."
    admin_tool: "backfill tool stamps a best-effort value for legacy rows (a known lie)."
    enforcement:
      - "write path stamps created_at at INSERT; never on UPDATE."
      - "regenerator/projection preserves genesis (#604/#698 invariant)."
    tests:
      - "created_at present + immutable across a mutation."
    forecloses: "true genesis for rows that predate the column."
    migration_cost_to_next: "low — add updated_at; stamp on mutation."
  - tier: updated_at
    commitment_delta:
      surface: recorded-history
      delta: "a last-mutation timestamp rewritten on every UPDATE."
    admin_tool: "none beyond created_at's."
    enforcement:
      - "write path rewrites updated_at on every mutation."
    tests:
      - "updated_at advances on mutation; created_at does not."
    forecloses: "the sequence of intermediate mutations (only the latest is kept)."
    migration_cost_to_next: "medium — add deleted_at + filter every read."
  - tier: deleted_at
    commitment_delta:
      surface: persistent-data-schema
      delta: "a nullable soft-delete timestamp; rows are hidden, not removed."
    admin_tool: "restore tool clears deleted_at; purge tool hard-deletes."
    enforcement:
      - "every read filters deleted_at IS NULL unless explicitly including tombstones."
    tests:
      - "soft-deleted row hidden from default reads; restorable."
    forecloses: "naive 'row is gone' assumptions; every query gains a predicate."
    migration_cost_to_next: "high — rewrite every write path into an append log."
  - tier: append-log
    commitment_delta:
      surface: recorded-history
      delta: "an append-only change log; current state is a fold over events."
    admin_tool: "log viewer + replay/rebuild tool."
    enforcement:
      - "no in-place mutation; every change is an appended event."
    tests:
      - "current state == fold(events); replay is deterministic."
    forecloses: "destructive UPDATE; storage grows monotonically."
    migration_cost_to_next: "very high — add a second time axis (bitemporal)."
  - tier: bitemporal
    commitment_delta:
      surface: persistent-data-schema
      delta: "valid-time × transaction-time; every fact carries both axes."
    admin_tool: "as-of query console (state at S, as believed at T)."
    enforcement:
      - "every write records both valid-time and transaction-time."
    tests:
      - "as-of query reproduces a past belief about a past state."
    forecloses: "single-timeline reasoning; all queries become two-dimensional."
    migration_cost_to_next: "n/a — top of the ladder."
applies_when: []
examples:
  - "The skill's own climb: created_at bugs (#604/#698, 3.15.0 stamp audit), the history log (#270), the bitemporal shadow store (3.14)."
```

> `applies_when: []` here is a deliberate placeholder for the EXEMPLAR — the
> empty marker means "no probed token applies" only in the project-token sense;
> temporality is in fact universally relevant wherever entities are persisted.
> 5A.2's deep temporality entry will either drop the marker (no opinion → always
> applies) or key it to a meaningful project-requirement token space.

## Selection procedure (§8)

Run the bidirectional check over the ladder: a de-escalation pass (YAGNI —
"can I drop a rung without violating a requirement?") and an escalation pass
("does any requirement force a higher rung?"), driven by the `selection.probes`.
Per-entity scope means this runs **per entity**, never over value objects
(§3.1). The chosen rung is recorded in the entity record with confidence +
rationale; `code_design_drift_gap` later flags a row that grew temporal columns
the record doesn't declare (5A.5).

## Composition (§5)

Temporality is half of several **products** (derived, not authored here):
`created_by`/`updated_by` = **principal × temporality** (provenance); per-tenant
retention = **tenancy × temporality**; the skill's recurring projection-vs-
canonical drift = **schema-authority × temporality**. The interaction table that
derives these is authored in 5A.3.
