2026-06-18T16:06:40Z by Showboat 0.6.1
Keep every nightly snapshot — deduplicated to just the churn — and any past state is yours.
The catalog changes every day: titles get corrected, records arrive, copies go missing. A lake that only holds today can’t answer the question an auditor, a cataloger, or a puzzled librarian eventually asks — what did the record say back then? Storing a full copy of a two-million-bib catalog every night would answer it, expensively, in a pile of near-identical snapshots.
The sierra-snapshot tenant does something leaner: it
keeps a bitemporal bronze that records only what
actually moved each night, every row stamped with the epoch it
opened and (once superseded) the epoch it closed. The full state of any
past night is then reconstructed on demand. This walkthrough runs the
real engine — the pure diff_epoch
classifier and the BronzeStore envelope (in-memory DuckDB)
— over a tiny synthetic catalog across three nights. Every command
reproduces; showboat verify re-runs each one and diffs the
output.
Each night’s snapshot is set-diffed against the prior open state. The
classifier is pure — maps of
pk -> row_hash in, an EpochDelta out, no
I/O and no epoch arithmetic. It sorts every key into new,
changed, deleted, or unchanged, and from
those derives the two actions the store applies: open a
fresh row (new ∪ changed) and close the
prior one (deleted ∪ changed). Unchanged rows are left
alone — that no-op is the whole dedup.
uv run python docs/demos/time-travel/_driver.py diff
The engine — classify one night against the last
================================================
Every night's snapshot is set-diffed against the prior open state. The diff is PURE
(pk -> row_hash maps in, an EpochDelta out — no I/O, no epoch math). The version tokens
below stand in for the content row_hash:
prior open (Mon): {'101': 'holes-v1', '102': 'wonder-v1', '103': 'hatchet-v1'}
current (Tue): {'101': 'holes-v1', '102': 'wonder-v2', '104': 'terabithia-v1'}
new=1 changed=1 deleted=1 unchanged=1
opened (insert a fresh row): ['102', '104']
closed (cap the prior row) : ['102', '103']
opened = new ∪ changed; closed = deleted ∪ changed; the UNCHANGED row (101) is a
no-op — that's the dedup that keeps storage ≈ base + churn, not a full copy per night.
Now feed three real nights through a BronzeStore. Each
returns its EpochDelta, and the envelope ends up holding a
base plus the churn — only the rows that actually moved
— instead of a full copy per night. That’s what makes “keep every
snapshot forever” affordable.
uv run python docs/demos/time-travel/_driver.py snapshots
Three nightly snapshots — stored as churn, not copies
=====================================================
epoch 1: new=3 changed=0 deleted=0 unchanged=0
epoch 2: new=1 changed=1 deleted=1 unchanged=1
epoch 3: new=0 changed=1 deleted=0 unchanged=2
envelope rows stored: 6 (a base of 3 + 3 churn events)
full nightly copies would be: 9
Unchanged rows are never rewritten — only what actually moved is recorded, each row
stamped with the epoch it opened and (once superseded) the epoch it closed.
Because every row carries the epoch it was open from and to,
reconstructing a past night is a single as_of(epoch) query:
return the rows whose window contains it. Here is the same catalog read
back as it stood on each of the three nights — the corrected title, the
withdrawn record, the new arrival, the copy that went missing, each
exactly where it was.
uv run python docs/demos/time-travel/_driver.py time_travel
Time-travel — what did the catalog hold each night?
===================================================
as_of(epoch 1) — Monday night:
bib 101 Holes Available
bib 102 Wonder Available
bib 103 Hatchet Available
as_of(epoch 2) — Tuesday night:
bib 101 Holes Available
bib 102 Wonder (10th anniversary ed.) Available
bib 104 Bridge to Terabithia Available
as_of(epoch 3) — Wednesday night:
bib 101 Holes Available
bib 102 Wonder (10th anniversary ed.) Available
bib 104 Bridge to Terabithia Missing
Read bib 102 down the nights: its title is corrected between Monday and Tuesday and
the old wording is still recoverable. Bib 103 (Hatchet) exists on Monday and is gone
by Tuesday; bib 104 arrives Tuesday and goes Missing on Wednesday. Every past state is
reconstructed exactly — not a guess, the rows that were actually open that night.
A timeline is only trustworthy if you can’t corrupt it. Re-running an epoch with the same data is a no-op (so a backfill is safe to retry), and epochs must move forward — feeding an out-of-order epoch is refused with an error, never silently misfiled into the past.
uv run python docs/demos/time-travel/_driver.py safety
The bitemporal safety rails — idempotent + monotonic
====================================================
Re-running an epoch with the same data is a no-op (re-ingest epoch 3):
delta -> new=0 changed=0 deleted=0 unchanged=3 (nothing rewritten)
And epochs must move forward — feeding an out-of-order epoch is refused, not silently
misfiled (try to ingest epoch 2 after the store is already at epoch 3):
ValueError: out-of-order epoch 2 for 'catalog': bronze is already at epoch 3; ingest epochs in ascending order
Idempotent re-runs + strictly non-decreasing epochs are what make the timeline
trustworthy: you can re-run a backfill safely, and you can never corrupt the past.
Every command above re-runs under showboat verify. The
on-thesis invariants — as_of reconstructs each night
exactly, a changed row keeps its past value recoverable, an out-of-order
epoch is refused, and re-running an epoch is a no-op — are pinned by the
demo’s own guard test, run against the real engine:
uv run pytest tests/demos/test_time_travel_demo.py -q 2>&1 | sed -E 's/ in [0-9.]+s//'...... [100%]
6 passed
Bitemporal bronze ships today for the snapshot tenant. The next moves are ideas, not commitments, posed as questions:
EpochDelta already names exactly what opened and closed.
Should that become a readable “what changed in the catalog overnight”
feed — for selectors, for collection analysis — rather than an internal
number?as_of view could be a citable,
frozen reference — “the catalog as of 2026-06-01” — for an audit or a
reproduced report.The discipline is the point. We ship the part we can stand behind — every night kept as churn, every past state reconstructed exactly, the timeline impossible to corrupt — and it all re-runs on demand.
← all walkthroughs · Rendered from 566da3f on 2026-06-18 · showboat verify: reproduces. A living artifact — the version ledger is git.