alloy
Write, review, and validate formal specifications of system designs, state machines, protocols, and algorithms using the Alloy 6 modeling language
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Write, review, and validate formal specifications of system designs, state machines, protocols, and algorithms using the Alloy 6 modeling language
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Write, review, and validate formal specifications of system designs, state machines, protocols, and algorithms using the Alloy 6 modeling language (more details)
Write JML (Java Modeling Language) specifications for Java programs and verify them using OpenJML. Covers the full workflow: writing specs, running static verification (ESC) and runtime assertion checking (RAC), interpreting verification results, and debugging failures. (more details)
Write JML (Java Modeling Language) specifications for Java programs and verify them using OpenJML. Covers the full workflow: writing specs, running static verification (ESC) and runtime assertion checking (RAC), interpreting verification results, and debugging failures.
Write and verify formal TLA+ and PlusCal specifications for system designs, state machines, algorithms, and concurrent protocols. Covers invariants, temporal properties, model checking, nondeterminism, fairness, and optimization. (more details)
Formally specify and verify system designs, state machines, algorithms, and concurrent protocols using TLA+ and PlusCal. Covers invariants, temporal properties, model checking with TLC, fairness, and reporting results responsibly.
| name | alloy |
| description | Write, review, and validate formal specifications of system designs, state machines, protocols, and algorithms using the Alloy 6 modeling language |
| license | MIT |
| compatibility | opencode |
| metadata | {"domain":"formal-methods","language":"alloy"} |
Load this skill when the user wants to:
.als specification.md file that contains alloy code blocksAlloy is a formal modeling language whose core principle is everything is a relation. The Alloy Analyzer performs bounded model checking by translating specifications to SAT problems and searching for instances or counterexamples within a finite scope. Alloy 6 adds native support for behavioral modeling with mutable state and linear temporal logic (LTL).
Reference: Practical Alloy and alloytools.org.
var).var, action predicates, init, stutter, temporal assertions.extends for disjoint partitions. Avoid integers unless arithmetic is truly required.run example {} first. Check satisfiability before adding many facts. Re-run after every major strengthening to avoid vacuity.fun/pred definitions for repeated concepts. Use ^ and * for reachability.pred init, one predicate per action (guard + effect + full frame conditions), pred stutter, and a transitions fact with always (... or stutter).assert { always P }. Liveness as assert { fairness implies eventually P }. Factor into inductive invariant when useful.run. Negative examples with unsat run. Bounded checks with explicit scopes, step counts, and expect annotations.When reviewing .als files, check for issues by severity:
Critical (likely bugs):
var relations change freely)always P where after always P was intended (contradicts current state)Structural issues:
run {} to verify non-trivial instances existcheck commands (default 3 atoms / 10 steps may hide bugs)util/ordering forces exact scope on the ordered signatureone sig consuming parent scope budgetSubtle issues:
in vacuously true when LHS is empty -- use some X & Y insteadalways (evaluates only at initial state)one, set, etc. explicitly)This section covers aspects of Alloy that are frequently misused. For basic syntax (signatures, fields, set/relational operators, quantifiers, boolean connectives), rely on standard Alloy 6 knowledge.
Always state multiplicity explicitly, even when one is the default:
| Keyword | Meaning |
|---|---|
one | Exactly one target per source atom |
lone | Zero or one target |
some | One or more targets |
set | Zero or more targets (unrestricted) |
disj1. In quantifiers -- bind distinct variables:
all disj x, y : Key | no x.lock & y.lock
2. On field declarations -- injectivity constraint:
sig Key { lock : disj some Lock }
// No two Keys share a Lock. If lock is var, holds in every state.
3. Between field names -- per-atom disjointness:
sig Key { disj lock, lock2 : one Lock }
// For each Key atom, lock and lock2 map to different Lock atoms.
4. As a predicate -- n-way disjointness test:
disj[x.lock, y.lock, z.lock]
Future-time:
| Operator | Meaning |
|---|---|
after F | F holds in the next state |
always F | F holds in current and all future states |
eventually F | F holds in current or some future state |
F until G | G becomes true eventually, F holds until then |
F releases G | G holds until and including when F becomes true, or forever |
Past-time:
| Operator | Meaning |
|---|---|
before F | F held in the previous state (false at state 0) |
once F | F held in some past state (including current) |
historically F | F held in all past states (including current) |
F since G | G was true at some past point, F has held since |
F triggered G | G has held since F was last true, or G has always held |
Prime operator: expr' evaluates expr in the next state. '' means two states ahead.
Sequence operator: P ; Q means P and after Q. Lowest precedence of all operators.
Key rules:
historically P at state 0 is just P.always (... once ...)).before is always false at state 0.| Syntax | Meaning |
|---|---|
for N | Default scope of N atoms per top-level signature |
for N but M Sig | Override scope for specific signature |
for N but exactly M Sig | Exact scope (not upper bound) |
for N but K steps | Bound trace to K steps (default: 10) |
for N but 1.. steps | Unbounded temporal model checking |
expect annotations document expected outcomes:
check bad_property for 6 expect 1 // expect counterexample found
check good_property for 6 expect 0 // expect no counterexample
Pitfall: check my_assertion {} checks the empty constraint (always true), NOT the assertion. Omit the braces: check my_assertion.
Reachability via transitive closure:
fun descendants [o : Object] : set Object { o.^(entries.object) }
fun allReachable : set Object { Root.*(entries.object) }
Override pattern (updating a relation for one key):
entries' = entries ++ (d -> newEntries)
// Replaces d's entries, keeps all other directories' entries unchanged
Total ordering without util/ordering (avoids exact scope limitation):
sig Node { next : lone Node }
one sig first, last in Node {}
fact ordering {
no next.first
no last.next
Node - first in first.^next
}
The canonical structure for any behavioral Alloy model:
// --- State ---
var sig uploaded in File {}
sig File { var shared : set Token }
// --- Initial state ---
pred init {
no uploaded
all f : File | no f.shared
}
// --- Events (each has guard + effect + frame conditions) ---
pred upload [f : File] {
// Guard
f not in uploaded
// Effect
uploaded' = uploaded + f
// Frame conditions (EVERY other mutable relation)
shared' = shared
}
// --- Stuttering (mandatory) ---
pred stutter {
uploaded' = uploaded
shared' = shared
}
// --- Transition system ---
fact transitions {
init and always (
(some f : File | upload[f])
or (some f : File | delete[f])
or stutter
)
}
Frame condition discipline: Every event predicate must explicitly state relation' = relation for every mutable relation it does not modify. Omitting a frame condition leaves the relation unconstrained -- the most common source of bugs.
Stuttering is mandatory. It ensures composability, extends finite behaviors to infinite traces, and prevents deadlocked states from making liveness properties vacuously true.
Ring topology:
sig Node { succ : one Node }
fact ring { all n : Node | Node in n.^succ }
Messages as tuples (recommended when message structure is simple):
abstract sig Type {}
one sig Candidate, Elect extends Type {}
sig Node {
succ : one Node,
var inbox : Type -> Node // each tuple IS a message
}
No message atoms needed. No scope issues. Order-of-magnitude faster analysis.
Messages as signatures (when messages have complex structure -- requires generator axioms):
abstract sig Message { payload : one Node }
sig CandidateMsg, ElectedMsg extends Message {}
fact generator {
all n : Node | some m : CandidateMsg | m.payload = n
}
fact unique {
all disj m1, m2 : CandidateMsg | m1.payload != m2.payload
}
Derived state via temporal functions (eliminates frame conditions for computed properties):
fun Elected : set Node {
{ n : Node | once (before (some (Elect -> n) & n.inbox)
and no (Elect -> n) & n.inbox) }
}
Safety (something bad never happens):
assert shared_are_accessible {
always (shared.Token in uploaded - trashed)
}
check shared_are_accessible for 5
Liveness (something good eventually happens -- requires fairness):
pred fairness {
all n : Node |
(eventually always enabled[n]) implies (always eventually acts[n])
}
assert eventually_elected {
fairness implies eventually (some Elected)
}
check eventually_elected for 4 but 20 steps
Inductive invariant (dramatically faster than unbounded temporal check):
pred inv { shared.Token in uploaded - trashed }
// Refactor init and transitions as predicates for induction
pred next {
(some f : File | upload[f]) or
(some f : File | delete[f]) or
stutter
}
assert initiation { init implies inv }
assert preservation { (inv and next) implies after inv }
check initiation for 10 but 1 steps
check preservation for 10 but 2 steps
Makes events visible in the Analyzer's trace visualizer. No performance penalty -- derived functions are only computed during visualization unless referenced in formulas.
enum Event { UploadEv, DeleteEv, StutterEv }
fun upload_happens : Event -> File {
{ e : UploadEv, f : File | upload[f] }
}
fun stutter_happens : set Event {
{ e : StutterEv | stutter }
}
fun events : set Event {
stutter_happens + (upload_happens + delete_happens).File
}
// Simplify the transitions fact:
fact transitions { init and always some events }
// Check mutual exclusion of events:
check at_most_one { always lone events } for 3
Use the ; (sequence) operator to describe specific execution traces:
run scenario {
some f : File, t : Token {
upload[f] ; share[f, t] ; download[t] ; delete[f] ; always stutter
}
} for 1 File, 1 Token
Always constrain the tail of the trace (e.g., always stutter) to prevent unexpected continuations.
Unmaintained mutable relations change arbitrarily between states.
// WRONG: shared can change freely during upload
pred upload [f : File] {
f not in uploaded
uploaded' = uploaded + f
}
// RIGHT: explicitly preserve all other mutable relations
pred upload [f : File] {
f not in uploaded
uploaded' = uploaded + f
trashed' = trashed
shared' = shared
}
Without stuttering, deadlocked states have no valid infinite continuations, making the specification vacuously true for properties about those states.
// WRONG: deadlock if no event is enabled
fact transitions {
init and always (some f : File | upload[f] or delete[f])
}
// RIGHT: always include stutter
fact transitions {
init and always (
(some f : File | upload[f] or delete[f])
or stutter
)
}
always P when you mean after always PInside an event predicate, always P includes the current state, which may contradict the event's own effect.
// WRONG: says download[t] is false NOW (contradicts itself)
pred download [t : Token] {
...
always not download[t]
}
// RIGHT: prohibition starts in the next state
pred download [t : Token] {
...
after always not download[t]
}
Liveness properties are trivially satisfied by infinite stuttering unless fairness excludes it.
// WRONG: satisfied by never doing anything
assert progress { eventually some uploaded }
// RIGHT: add fairness as a premise
pred fairness { always eventually (some f : File | upload[f]) or no (File - uploaded) }
assert progress { fairness implies eventually some uploaded }
Too many constraints can make the specification unsatisfiable, causing all check commands to trivially pass.
// Always validate satisfiability after modifying facts:
run sanity_check {} for 5
// If this finds no instance, the facts are contradictory.
The default scope of 3 atoms and 10 steps may hide counterexamples.
check no_partitions for 3 // no counterexample (false confidence)
check no_partitions for 5 // counterexample found!
// For liveness, increase step bound:
check liveness_prop for 4 but 30 steps
alwaysTop-level formulas are evaluated at state 0 only.
// WRONG: Elected is likely empty at state 0
all n : Elected | some n.inbox
// RIGHT: place inside always so it re-evaluates each state
always (all n : Elected | some n.inbox)
A model of a cloud file sharing app demonstrating mutable state, temporal logic, frame conditions, safety, liveness, event depiction, and scenarios.
module filesharing
// --- Signatures ---
sig File {
var shared : set Token
}
sig Token {}
var sig uploaded in File {}
var sig trashed in uploaded {}
// --- Initial state ---
pred init {
no uploaded
no trashed
no shared
}
// --- Events ---
pred upload [f : File] {
f not in uploaded
uploaded' = uploaded + f
trashed' = trashed
shared' = shared
}
pred delete [f : File] {
f in uploaded - trashed
trashed' = trashed + f
uploaded' = uploaded
shared' = shared
}
pred restore [f : File] {
f in trashed
trashed' = trashed - f
uploaded' = uploaded
shared' = shared
}
pred share [f : File, t : Token] {
f in uploaded - trashed
historically t not in File.shared
shared' = shared + f -> t
uploaded' = uploaded
trashed' = trashed
}
pred download [t : Token] {
some shared.t & (uploaded - trashed)
uploaded' = uploaded
trashed' = trashed
shared' = shared
}
pred empty {
some trashed
uploaded' = uploaded - trashed
trashed' = trashed - trashed
shared' = shared - trashed -> Token
}
pred stutter {
uploaded' = uploaded
trashed' = trashed
shared' = shared
}
// --- Transition system ---
fact transitions {
init and always (
(some f : File | upload[f] or delete[f] or restore[f])
or (some f : File, t : Token | share[f, t])
or (some t : Token | download[t])
or empty
or stutter
)
}
// --- Safety ---
assert shared_are_accessible {
always (shared.Token in uploaded - trashed)
}
assert trashed_are_uploaded {
always (trashed in uploaded)
}
// --- Liveness ---
pred fairness_on_empty {
(eventually always some trashed) implies (always eventually empty)
}
assert trash_eventually_emptied {
fairness_on_empty implies always eventually no trashed
}
// --- Undo property ---
assert restore_undoes_delete {
all f : File | always (
delete[f] and after restore[f] implies
uploaded'' = uploaded and trashed'' = trashed and shared'' = shared
)
}
// --- Event depiction ---
enum Event { UploadEv, DeleteEv, RestoreEv, ShareEv, DownloadEv, EmptyEv, StutterEv }
fun upload_happens : Event -> File {
{ e : UploadEv, f : File | upload[f] }
}
fun delete_happens : Event -> File {
{ e : DeleteEv, f : File | delete[f] }
}
fun restore_happens : Event -> File {
{ e : RestoreEv, f : File | restore[f] }
}
fun share_happens : Event -> File -> Token {
{ e : ShareEv, f : File, t : Token | share[f, t] }
}
fun download_happens : Event -> Token {
{ e : DownloadEv, t : Token | download[t] }
}
fun empty_happens : set Event {
{ e : EmptyEv | empty }
}
fun stutter_happens : set Event {
{ e : StutterEv | stutter }
}
fun events : set Event {
empty_happens + stutter_happens
+ (upload_happens + delete_happens + restore_happens).File
+ download_happens.Token
+ (share_happens.Token).File
}
// --- Commands ---
run show {} for 3 but 10 steps
run scenario_share_then_delete {
some f : File, t : Token {
upload[f] ; share[f, t] ; delete[f] ; download[t] ; always stutter
}
} for 2 but 8 steps expect 0
check shared_are_accessible for 5 but 15 steps expect 0
check trashed_are_uploaded for 5 but 15 steps expect 0
check restore_undoes_delete for 4 but 10 steps expect 0
check trash_eventually_emptied for 3 but 20 steps
Check in this order:
alloy command on PATHorg.alloytools.alloy.dist.jar in the project or common locationsalloy*.jar file in the project directory# CLI:
alloy exec <file.als>
# CLI with markdown file:
alloy exec <file.md>
# JAR:
java -jar <path-to-alloy.jar> <file.als>
# JAR with markdown file:
java -jar <path-to-alloy.jar> <file.md>
| Output | Meaning |
|---|---|
| "Instance found" | run found a satisfying instance |
| "No instance found" | run: spec may be over-constrained |
| "Counterexample found" | check: assertion is violated |
| "No counterexample found" | check: assertion holds within scope |
Report results as bounded analysis: say no counterexample was found within the checked scope, not that the system is proved correct.
If the Analyzer is not available, produce the .als file and inform the user to open it in the Alloy Analyzer GUI or install the CLI tool.
.als, one module per filemodule declaration should match the filename// (line), -- (line), /* ... */ (block)models/ or alloy/ directory// --- Signatures ---, // --- Events ---, etc..md), the file must start with a YAML header (frontmatter): three dashes on the first line, followed by a title field in YAML format, followed by three more dashes.---
title: [ModelTitle]
---
Before delivering a model, verify:
run command?stutter present if the model is behavioral?