High-performance cache policies and supporting data structures.
How to add optional TLA+ specs for cache policies. TLA+ checks structural invariants on all reachable states for a finite instance; Rust proptest checks step-wise observables on long random traces. They are complementary.
| Tier | TLA+ fit |
|---|---|
| Exact | Good when state is finite and victim rules are deterministic |
| Mirror | Possible if the mirrored DS has bounded finite state |
| Bounded | Defer — adaptive victims and legal sets are harder to model finitely |
| Composed | Defer — TTL deadlines add continuous time |
Start with a human operational spec (template.md) before writing TLA+.
Human operational specs live under policies/<tier>/. TLA+ artifacts live under formal/<policy>/:
docs/testing/specs/
├── policies/exact/<policy>.md # human source of truth
├── formal/<policy>/
│ ├── <Policy>.tla # MODULE name must match filename (case-sensitive on Linux)
│ ├── <policy>.cfg # TLC constants, INVARIANT, CONSTRAINT
│ └── tlc.md # Runbook + alignment checklist
├── tla-guide.md # This file
└── formal/README.md # Formal specs index
Run TLC via the generic wrapper:
./scripts/run-tlc.sh fifo
# or aliases:
./scripts/run-fifo-tlc.sh
./scripts/run-tlc.sh lru
./scripts/run-lru-tlc.sh
TLC is manual only — not run in CI.
SemanticOK — policy truth (matches operational spec)ExplorationOK — finiteness bounds (MaxQueueLen, etc.) not in the human specUse a sentinel for optional values — e.g. NoVictim instead of NULL.
Match harness Op alphabet — document omitted ops (read-only hits, value-only updates).
Set CHECK_DEADLOCK FALSE when exploration constraints can stall the state graph.
Document alignment in formal/<policy>/tlc.md with a change log when spec or .tla changes.
MODULE name. On case-insensitive volumes Fifo.tla may appear as fifo.tla; do not create duplicates.FIFO is the reference TLA+ pilot. Human spec: fifo.md. Runbook: formal/fifo/tlc.md.
| Artifact | What it proves |
|---|---|
| fifo.md | Human-readable source of truth |
| Fifo.tla + TLC | Structural FIFO invariants on all reachable states for a tiny finite instance |
NaiveFifoModel + proptest |
Step-wise observables on long random traces (u8 keys, capacity 1..=16) |
| TLA+ | fifo.md | NaiveFifoModel |
|---|---|---|
cache |
store |
store: HashSet<K> |
queue |
insertion_order |
insertion_order: VecDeque<K> |
Capacity |
capacity |
capacity: usize |
OldestLive |
peek_victim |
oldest_key() / peek_victim_key() |
NoVictim |
none (empty cache) | None |
Stale entries: after RemoveKey, removed keys stay in queue but not in cache. OldestLive skips them when scanning front-to-back.
Capacity = 2
cache = {k2, k3} ← residents (set)
queue = ⟨k1, k2, k3⟩ ← insertion log (sequence)
^stale ^live ^live
OldestLive = k2 ← first queue element still in cache
| TLA+ action | Harness Op |
Enabled when | cache' |
queue' |
|---|---|---|---|---|
InsertNew(k) |
Insert(k) (new key) |
k ∉ cache, queue room / eviction succeeds |
add k; evict oldest if full |
PopThroughVictim + append k if full; else append |
RemoveKey(k) |
Remove(k) |
k ∈ cache |
remove k |
unchanged (stale remains) |
EvictOldest |
EvictOne |
cache nonempty, victim exists | remove oldest live | PopThroughVictim |
Intentionally omitted: Get, Peek, GetMut, Touch (no structural change); Insert(k) when k ∈ cache (value update only).
SemanticOK (policy truth — checked by TLC):
| Operator | Meaning |
|---|---|
LenBound |
\|cache\| ≤ Capacity |
CacheKeysInQueue |
every resident key appears in queue |
QueueConsistency |
every queue element ∈ Keys |
PeekVictimOK |
cache empty ↔ OldestLive = NoVictim |
VictimInCache |
when victim defined, it is in cache |
ExplorationOK (TLC finiteness only):
| Operator | Meaning |
|---|---|
QueueLengthBound |
Len(queue) ≤ MaxQueueLen |
Fifo.tlaOldestLive, PopThroughVictimSemanticOK, ExplorationOKInsertNew, RemoveKey, EvictOldest| File | Role |
|---|---|
| Fifo.tla | Machine-readable spec (module Fifo) |
| fifo.cfg | TLC constants and INVARIANT SemanticOK |
| formal/fifo/tlc.md | FIFO runbook and alignment log |
| fifo.md | Human operational spec |
| scripts/run-fifo-tlc.sh | One-command TLC runner |
Second TLA+ pilot. Human spec: lru.md. Runbook: formal/lru/tlc.md.
| Artifact | What it proves |
|---|---|
| lru.md | Human-readable source of truth |
| Lru.tla + TLC | Structural LRU invariants on all reachable states for a tiny finite instance |
NaiveLruModel + cross-model / proptest |
Step-wise observables on long random traces |
| TLA+ | lru.md | NaiveLruModel |
|---|---|---|
order |
order (MRU-first deque) |
access timestamps (equivalent oracle) |
LruKey |
peek_victim (back of deque) |
lru_victim() / peek_victim_key() |
Capacity |
capacity |
capacity: usize |
NoVictim |
none (empty cache) | None |
Unlike FIFO, the LRU deque has no stale entries — RemoveKey filters the key from order.
| TLA+ action | Harness Op |
Enabled when | Effect |
|---|---|---|---|
InsertNew(k) |
Insert(k) (new key) |
k ∉ Cache |
prepend k; evict LRU (back) if full |
PromoteKey(k) |
Insert(k) resident / Get / Touch hit |
k ∈ Cache |
move k to MRU (front) |
RemoveKey(k) |
Remove(k) |
k ∈ Cache |
remove k from deque |
EvictLru |
EvictOne |
deque nonempty | drop LRU (back) |
Intentionally omitted: Peek (no promotion); GetMut in LruCore adapter (no-op).
SemanticOK)| Operator | Meaning |
|---|---|
LenBound |
Len(order) ≤ Capacity |
NoDuplicates |
deque entries are unique |
OrderInKeys |
every element ∈ Keys |
PeekVictimOK |
empty ↔ LruKey = NoVictim |
VictimInCache |
victim defined ⇒ resident |
No ExplorationOK queue cap is needed — deque length is bounded by Capacity.
| File | Role |
|---|---|
| Lru.tla | Machine-readable spec (module Lru) |
| lru.cfg | TLC constants and INVARIANT SemanticOK |
| formal/lru/tlc.md | LRU runbook and alignment log |
| lru.md | Human operational spec |
| scripts/run-lru-tlc.sh | One-command TLC runner |