skills/verification/tlaplus-model-reduction/SKILL.md
Reduces a TLA+ model so TLC can actually check it — shrinks constants, adds state constraints, abstracts data, or applies symmetry — when the state space is too large to enumerate. Use when TLC runs out of memory, when checking takes hours, or when a spec works at N=2 and you need confidence at larger scale.
npx skillsauth add santosomar/general-secure-coding-agent-skills tlaplus-model-reductionInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
TLC is explicit-state: it literally visits every reachable state. State count is roughly product of each variable's domain size, raised to how many variables you have, times fanout. When that product hits billions, you're done — unless you shrink something.
Run TLC with -coverage 1. Look at:
| Symptom | Likely cause | Fix | | ---------------------------------------- | --------------------------------- | ---------------------------------- | | Runs forever, queue keeps growing | Unbounded domain (Nat, Seq) | State constraint | | Huge distinct-states count, slow | Too many processes/nodes | Shrink CONSTANTS | | generated/distinct ratio very high | Symmetric processes | SYMMETRY set | | One variable has huge domain | Over-precise data modeling | Abstract the data |
The .cfg constants control scale. Proc = {p1, p2, p3, p4, p5} → try {p1, p2, p3}. Most bugs show at N=2 or N=3.
Small model hypothesis: if a protocol bug exists, it usually exists at small scale. Paxos bugs manifest with 3 nodes. Consensus needs 3 for a meaningful majority; 5 rarely finds new bugs.
Any variable ranging over Nat or unbounded sequences makes the state space infinite. Add a CONSTRAINT in .cfg:
CONSTRAINT StateConstraint
StateConstraint ==
/\ counter <= 10
/\ Len(queue) <= 5
/\ clock <= 20
TLC stops exploring successors of any state violating the constraint. This is not a proof of safety — it's a search within a bounded region. But if the property holds up to the bound and the bound is well above any threshold in the spec, that's strong evidence.
If processes are interchangeable (any permutation of {p1, p2, p3} gives the same behavior), declare it:
SYMMETRY Perms
Perms == Permutations(Proc)
TLC now treats (p1 leader, p2 follower, p3 follower) and (p2 leader, p1 follower, p3 follower) as the same state. For N symmetric processes, this divides state count by roughly N!.
Warning: Symmetry is unsound with liveness checking. TLC will warn you. Safety only.
If a message payload doesn't affect the protocol, don't model it. Replace value \in Int with value \in {v1, v2} — two distinct symbolic values are enough to distinguish "same" from "different."
| Over-modeled | Abstracted |
| ----------------------------------------- | ------------------------------------------------------- |
| msg.body \in STRING | Drop .body — protocol doesn't inspect it |
| timestamp \in Nat | timestamp \in 0..3 with a constraint, or just ordering (t1 < t2) |
| balance \in Int | balance \in {Neg, Zero, Pos} if only sign matters |
A VIEW tells TLC which part of the state to hash for "already seen" checks:
VIEW StateView
StateView == <<pc, lock_holder>> \* ignore tmp, counter for dedup purposes
States that differ only in un-viewed variables are treated as equal. Dangerous: you can miss bugs in the ignored variables. Use only when you're sure those variables don't affect the property.
Spec: Leader election among N nodes. Original .cfg:
CONSTANTS Nodes = {n1, n2, n3, n4, n5}
MaxRound = 100
TLC: 47M distinct states after 2 hours, still growing. Queue at 3M.
Diagnosis: round \in Nat is unbounded → infinite. Nodes are symmetric. 5 nodes is overkill.
Reduced .cfg:
CONSTANTS Nodes = {n1, n2, n3}
MaxRound = 4
CONSTRAINT StateConstraint
SYMMETRY NodePerms
StateConstraint == round <= MaxRound
NodePerms == Permutations(Nodes)
TLC: 18K distinct states, finishes in 12 seconds. Invariant holds.
Is this enough? 3 nodes is the minimum for a meaningful majority. MaxRound = 4 — the protocol's timeout threshold is 2, so 4 rounds is two full timeout cycles. Any round-based bug would fire by then. Strong evidence, not proof.
If you've shrunk to N=2 with tight constraints and it's still too big, the spec has too much internal state. Options:
Init ⟹ Inv and Inv ∧ Next ⟹ Inv' with TLAPS. Harder to write, scales infinitely.-simulate) — random walks through the state space. Doesn't prove anything but finds bugs.if counter >= 10 then crash and you constrain counter <= 5, you've hidden the bug.## Diagnosis
Blowup source: <unbounded var | constant size | symmetry | data precision>
Evidence: <states/sec, queue growth, coverage numbers>
## Reductions applied
1. <technique> — <before → after>
...
## New .cfg
<config block>
## Soundness caveats
<what this reduction can miss — and why you believe it won't, for this property>
## Result
<states explored, time, invariant status>
development
Extracts human-readable pseudocode from a verified formal artifact (Dafny, Lean, TLA+) while preserving the verified properties as annotations, so the proof-carrying logic can be reimplemented in a production language. Use when porting verified code to an unverified target, when documenting what a formal spec actually does, or when handing a verified algorithm to an implementer.
development
Translates natural-language or pseudocode descriptions of concurrent and distributed systems into TLA+ specifications ready for the TLC model checker. Identifies state variables, actions, type invariants, safety properties, and liveness properties from the description. Use when formalizing a protocol, when the user describes a distributed algorithm to verify, when designing a consensus or locking scheme, or when starting formal verification of a concurrent system.
development
TLA+-specific instance of model-guided repair — reads a TLC error trace, identifies the enabling condition that should have been false, strengthens the corresponding action, and maps the fix to source code. Use when TLC reports an invariant violation or deadlock and you have the code-to-TLA+ mapping from extraction.
testing
Translates specifications into temporal logic formulas (LTL, CTL, or TLA) by matching the specification's shape to the right logic and operators. Use when formalizing requirements for any model checker, when choosing between LTL and CTL for a property, or when the user has a temporal claim and doesn't know which operators express it.