Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

The Temporal Model

Every fact in Quipu has two time dimensions:

  1. Transaction time — when the fact was recorded in the database
  2. Valid time — when the fact was true in the real world

This is called a bitemporal model, and it means you can always answer:

  • “What did we know at time T?” (transaction time)
  • “What was true at time T?” (valid time)
  • “What did we know at time T1 about what was true at T2?” (both)

Why Bitemporality Matters

Say you record on April 1 that koror has 4 CPU cores:

quipu knot - --db homelab.db --timestamp 2026-04-01 <<'EOF'
@prefix hw: <http://example.org/homelab/> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
hw:koror hw:cpuCores "4"^^xsd:integer .
EOF

On April 3 you upgrade to 8 cores and record the change:

quipu retract "http://example.org/homelab/koror" \
  --predicate "http://example.org/homelab/cpuCores" \
  --db homelab.db --timestamp 2026-04-03

quipu knot - --db homelab.db --timestamp 2026-04-03 <<'EOF'
@prefix hw: <http://example.org/homelab/> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
hw:koror hw:cpuCores "8"^^xsd:integer .
EOF

Now you can time-travel:

# What's the current state?
quipu read "SELECT ?cores WHERE {
  <http://example.org/homelab/koror> <http://example.org/homelab/cpuCores> ?cores
}" --db homelab.db
# → 8

# What was true on April 2?
quipu read "SELECT ?cores WHERE {
  <http://example.org/homelab/koror> <http://example.org/homelab/cpuCores> ?cores
}" --db homelab.db --valid-at 2026-04-02
# → 4

The EAVT Fact Log

Under the hood, facts are stored as immutable rows:

E (entity)A (attribute)V (value)T (tx)valid_fromvalid_toop
hw:kororhw:cpuCores412026-04-012026-04-03Assert
hw:kororhw:cpuCores422026-04-032026-04-03Retract
hw:kororhw:cpuCores832026-04-03nullAssert

Nothing is deleted. Retractions close the valid_to window on old facts and add a new retraction record. The full history is always available.

Transaction Time vs Valid Time

DimensionWhat it tracksSet byQueryable via
Transaction timeWhen the database learned about the factSystem (auto-incremented tx ID)--tx flag, as_of_tx parameter
Valid timeWhen the fact was true in realityYou (--timestamp flag)--valid-at flag, valid_at parameter

Transaction time is monotonic and system-controlled. Valid time is user-supplied and can refer to the past or future.

Querying Through Time

Current state (default)

SELECT ?host ?cores WHERE {
  ?host <http://example.org/homelab/cpuCores> ?cores .
}

Returns only currently-asserted facts (op=Assert, valid_to is null).

Valid-time travel

quipu read "SELECT ?host ?cores WHERE {
  ?host <http://example.org/homelab/cpuCores> ?cores
}" --db homelab.db --valid-at 2026-04-02

Returns facts that were valid at the specified point in time.

Transaction-time travel

quipu read "SELECT ?host ?cores WHERE {
  ?host <http://example.org/homelab/cpuCores> ?cores
}" --db homelab.db --tx 1

Returns only facts recorded up to transaction 1 — what the database knew at that point, regardless of valid-time windows.

REST API

curl -s localhost:3030/query -X POST \
  -H "Content-Type: application/json" \
  -d '{
    "query": "SELECT ?host ?cores WHERE { ?host <http://example.org/homelab/cpuCores> ?cores }",
    "valid_at": "2026-04-02"
  }'

Contradiction Detection

If two facts for the same entity+attribute have overlapping valid-time windows, Quipu flags a contradiction. This prevents conflicting states from silently coexisting:

#![allow(unused)]
fn main() {
let issues = store.detect_contradictions()?;
// Returns pairs of facts with overlapping intervals
}

Design Principles

  • Append-only: Facts are never mutated or deleted
  • Full audit trail: Every change is a transaction with metadata
  • Time-travel by default: Any query can add a temporal context
  • Contradiction-aware: Overlapping valid-time windows are surfaced