skills/verification/requirement-to-tlaplus-property-generator/SKILL.md
Translates natural-language requirements into TLA+ properties — invariants for safety, temporal formulas for liveness — checkable with TLC. Use when writing the PROPERTY and INVARIANT sections of a TLA+ spec, when formalizing acceptance criteria, or when the user has a requirement and a model but no property.
npx skillsauth add santosomar/general-secure-coding-agent-skills requirement-to-tlaplus-property-generatorInstall 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.
A requirement is a claim about behavior. A TLA+ property is the same claim, stated precisely enough to check. The translation is about finding the formal shape of an informal statement.
| The requirement says… | It is… | TLA+ form |
| ---------------------------------------- | ---------- | -------------------------------- |
| "never", "always", "must not", "at most" | Safety | Invariant: Inv == ... |
| "eventually", "will", "responds", "terminates" | Liveness | Temporal: Prop == <>... or []<>... |
| "if X then eventually Y" | Liveness (response) | [](X => <>Y) or X ~> Y |
| "X until Y" | Safety-ish | Encoded with history variables or LTL-style |
Safety = something bad never happens. Checkable at each state. Liveness = something good eventually happens. Needs fairness; checkable only over infinite traces.
Every property bottoms out in a predicate over state variables. Extract the nouns from the requirement; map them to VARIABLES.
| Requirement noun | Likely state variable |
| ----------------------------- | ---------------------------------- |
| "the lock" | lock_holder |
| "the queue" | queue (a sequence) |
| "process P" | pc[P] (its program counter) |
| "a request" | An element of msgs or pending |
| "the leader" | leader (or \E p : role[p] = "leader") |
| English | TLA+ |
| ----------------------------------------------- | --------------------------------------------------------- |
| "At most one process holds the lock" | Cardinality({p \in Proc : holding[p]}) <= 1 |
| "The queue never exceeds N" | Len(queue) <= N |
| "If a process is in the critical section, it holds the lock" | \A p : pc[p] = "cs" => lock_holder = p |
| "Every request is eventually acknowledged" | \A r \in Req : (r \in pending) ~> (r \in acked) |
| "The system never deadlocks" | []<><<Next>>_vars (always eventually a step) — or check in .cfg with CHECK_DEADLOCK |
| "X and Y are never true simultaneously" | ~(X /\ Y) |
| "Once elected, a leader stays leader" | [](leader = p => [](leader = p)) — or with action: [][leader' = leader \/ leader = NULL]_leader |
| "P always precedes Q" | Tricky — use a history variable: seen_P => Q won't fire before P |
Requirement: "A write request must be replicated to a majority of nodes before it is acknowledged to the client."
Decompose:
r \in writesreplicated[r] is a set of nodes, Cardinality(replicated[r]) >= ...> Cardinality(Nodes) \div 2r \in acked then the replication condition holds. This is an ordering constraint on when acked can be set.Property (safety — "never ack prematurely"):
Majority == Cardinality(Nodes) \div 2 + 1
SafeAck ==
\A r \in acked :
Cardinality(replicated[r]) >= Majority
Checkable as an invariant. If TLC finds a state where r \in acked but replicated[r] is a minority, the replication protocol is wrong.
Companion liveness — "every write does eventually get acked":
AckLiveness ==
\A r \in writes : (r \in pending) ~> (r \in acked)
Needs fairness on the replication and ack actions to be checkable.
"X before Y" is NOT X => <>Y. That says "if X, eventually Y" — nothing about order.
"X before Y" means: at the moment Y happens, X has already happened. In state terms: Y => (X has been true at some past point). TLA+ is forward-looking — past is awkward. Standard trick:
VARIABLE seen_X \* history variable — not in the real system
Init == ... /\ seen_X = FALSE
Next == ... /\ seen_X' = (seen_X \/ X) \* latches TRUE once X holds
Precedence == Y => seen_X
<>P is only checkable if Next has fairness — otherwise TLC says "yes, if I stutter forever P never happens, property violated." Add WF_vars(Action) for the action that achieves P.[]<>P when you mean <>P. []<>P = "infinitely often P" (keeps happening). <>P = "at least once." Different requirements.## Requirement (as given)
<verbatim>
## Classification
<safety | liveness | response | precedence>
## State mapping
<english noun> ↦ <TLA+ variable/expression>
## Property
<TLA+>
## Fairness needed (liveness only)
<WF/SF conditions — and why>
## Sanity check
<does the property trivially hold? is it vacuously true? — one sentence>
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.
testing
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.
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.