Status: Accepted (2026-07-25); implemented in
corium-db::bootstrap with the
model in docs/design/data-model.md and the views
in docs/design/time-model.md. Completes the time
model of ADR-0005.
ADR-0005 shipped the full time model — as-of, since, history, the log
API, tx-reports — but transaction time was only half present. Every datom
carried its tx, and t ordered transactions, yet the wall-clock instant
lived exclusively as a field on the log record (TxRecord::tx_instant) and a
high-water mark on the database root. That left three gaps:
[?tx :db/txInstant ?inst] matched nothing. A query could
reach a datom's transaction but not the time it was committed, so "what did
this look like last Tuesday" was unanswerable in Datalog, and audit questions
needed the log API and a join done by hand in application code.docs/design/time-model.md promised
as-of(instant) and since(instant); nothing implemented them, because
there was nothing indexed to resolve an instant against."datomic.tx" tempid had no
analogue, so the who/why of a change had nowhere to live next to the when.Corium is being evaluated for bitemporal extensions, where valid time is compared against transaction time. Comparing against an axis the query language cannot see is not possible, so this is a prerequisite for that work as well as being worth having on its own.
Make transaction time an ordinary datom.
:db/txInstant in every
database (:db.part/db sequence 50, as in Datomic; sequences below the
first user-installable id are reserved for the engine). It is an instant,
cardinality one, AVET-indexed, with history.max(now, last + 1).:db/txInstant against the
transaction entity dates a commit explicitly (how a backfilled import keeps
original timestamps); an instant that does not advance the clock is
rejected, because monotonicity is what makes instant resolution meaningful."datomic.tx" (and :db/current-tx at the EDN
boundary) names the transaction entity, allocating nothing and never
upserting through a unique-identity attribute.as-of/since accept a wall-clock instant on
every surface: Rust, cljrs, the wire (DbViewSpec), the console, and the
SQL shell. An instant resolves to the last transaction committed at or
before it; an instant older than the database resolves to basis 0.d/datoms returns them, and SQL's wide
projection gains a db table over transactions. This is Datomic's behavior
and the honest reading of "everything is datoms", but it does change counts
that tests and dashboards may have treated as user data.t ↔ instant correspondence is carried on the database value, so deriving a
view never truncates the clock it resolves against. Its memory cost is two
ordered-map entries per transaction, against a value that already holds every
datom.:db/txInstant
datoms; a peer bootstrapped from one resolves instants only within the
replayed tail until the next publication.:db/txInstant, and instant-named views
there resolve to basis 0. t-named views are unaffected.Can you improve this documentation?Edit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |