What is Alloy?
Alloy is a lightweight formal modeling language created by Daniel Jackson at MIT. Unlike programming languages, you don’t execute Alloy specifications—you analyze them. The Alloy Analyzer searches for instances that satisfy your constraints or violate your assertions. Organizations use Alloy for critical systems:- Microsoft Research has published work applying Alloy to access control policies
- AT&T used Alloy to model telephone switching networks
- Aerospace applications include verifying spacecraft software properties
The Mental Model
Before diving into syntax, let’s build intuition. Atoms: An atom is an indivisible entity a user, a file, a server. It’s the smallest discrete entity that can exist. Atoms have no internal structure in Alloy, they’re just distinct identities. Think of them as rows in a database with only an ID column. Signatures: A signature defines a type of atom.sig File {} says “there are things called Files.” When Alloy analyzes your model, it generates concrete atoms: File0, File1, File2. Signatures are like tables in a database.
Relations: Relations connect atoms. sig File { parent: one Folder } says every File has exactly one parent Folder. Relations are like foreign keys. Alloy represents all data as relations even attributes are just relations to value atoms.
Facts: Facts are constraints that must always hold. fact { no f: Folder | f.parent = f } says no folder can be its own parent. Facts eliminate impossible configurations from Alloy’s search.
Instances: An instance is a concrete assignment of atoms and relations that satisfies all facts. When you run the Analyzer, it searches for valid instances. If your model is overconstrained, no instances exist.
Counterexamples. When you assert something should always be true, Alloy searches for violations. A counterexample is a specific instance that breaks your assertion proof that your model has a flaw.
Example 1: File System Basics
Let’s write a complete specification. This one models something familiar, files and folders, but introduces all the core concepts.module FileSystem - Names this specification. Modules help organize larger models.
sig File { parent: one Folder } - Defines the File signature. Every File has exactly one parent Folder. The keyword one means “exactly one.”
sig Folder { parent: lone Folder } - Defines Folders. Each Folder has at most one parent. The keyword lone means “zero or one” this allows root folders with no parent.
fact NoCircularFolders - A constraint: no folder can be an ancestor of itself. The ^ operator means transitive closuref.^parent is the parent, grandparent, great-grandparent, and so on. The in operator tests membership.
run {} for 3 File, 2 Folder - Tells the Analyzer: find an instance with up to 3 Files and 2 Folders (could be fewer). The empty braces {} mean no additional constraints beyond the facts.
Paste this into the Alloy Analyzer or MacAlloy and click Execute. You’ll see a visualization showing concrete atoms like File0, File1, Folder0 connected by arrows representing the parent relation. This isn’t abstract, it’s a specific, valid configuration that satisfies all your constraints.
Example 2: The Permission Bug
The file system works, but it’s not useful yet. Let’s add users and permissions, and find a bug.canAccess: set Folder - Each User can access zero or more Folders. The keyword set means “any number.”
pred canRead[u: User, f: File] - A predicate is a reusable formula with parameters. This one says user u can read file f if u can access f’s parent folder.
run canRead - Find an instance where the predicate is true (some user can read some file).
This works. But there’s a subtle problem. What happens when we move a file?
Finding the Bug
In a real system, files can move between folders. Let’s model that and check if permissions stay correct. To model change over time, we need Alloy’s temporal features. This is more advanced than what we’ve covered so far—don’t worry if it takes a second reading. The key insight is worth the complexity: we can ask Alloy to find bugs that only appear when things change.This section uses Alloy 6’s temporal features (
var, ', after, always). If you’re using Alloy 4 or 5, these features aren’t available. Download Alloy 6 to run this example.var parent: one Folder - The var keyword marks a mutable relation. File parents can change over time. (This uses Alloy 6’s temporal features.)
f.parent' = newParent - The prime (') means “the value in the next state.” Like TLA+, Alloy can model state transitions.
all other: File - f | other.parent' = other.parent - This is a frame condition: it explicitly says all other files keep their current parents. Without this, Alloy could change any file’s parent arbitrarily in the next state.
assert MovePreservesAccess - An assertion is something you claim should always be true. The check command searches for violations.
after canRead[u, f] - The temporal operator after means “in the next state.”
Run this specification. Alloy finds a counterexample:
- User0 can access Folder0
- File0’s parent is Folder0 (so User0 can read it)
- File0 moves to Folder1
- User0 cannot access Folder1
- User0 can no longer read File0
This is Alloy’s killer feature: you write down what should be true, and Alloy shows you exactly how it can be false. The counterexample isn’t abstract, it’s a specific scenario you can trace through.
Fixing the Bug
The fix depends on your requirements. Maybe you want to check destination permissions before allowing moves:When
check finds no counterexample, it means no violation exists within the bounds you specified (here, up to 3 files, 2 folders, 2 users). Alloy doesn’t prove properties hold for all possible sizes—just that no small counterexample exists. In practice, most bugs manifest in small instances, making bounded verification surprisingly effective.Example 3: Network Reachability
Let’s see Alloy applied to a different domain, network topology, and introduce a few more concepts.abstract sig Device {} - An abstract signature has no atoms of its own; only its extensions do. Device exists only to group Computer, Router, and Server.
sig Computer extends Device - Computer is a subtype of Device. Inheritance works as you’d expect.
*linkedTo - The * is reflexive transitive closure: the router itself, plus everything reachable through linkedTo.
fun reachableRouters[c: Computer]: set Router - A function returns a value. Here it returns all routers reachable from computer c.
{ s: Server | s.hostedOn in reachableRouters[c] } - A set comprehension: all servers whose hosting router is in the reachable set.
Run the model.
The check should pass, with symmetric links and full router connectivity, every computer can reach every server through some path. If you did get a counterexample, it would mean some computer can’t reach some server. Common causes: connectedTo is empty (computer isn’t plugged in), or the model has a bug. Try removing the Symmetric fact and see what happens.
This pattern, model the system, assert properties, check for violations is how I use Alloy for real designs, from Active Directory trust relationships to API permission models.
Core Vocabulary
Let’s solidify the concepts you’ve learned:| Term | Meaning |
|---|---|
| Atom | An indivisible entity (File0, User1, etc.) |
| Signature | A type definition: what kinds of atoms exist |
| Relation | A connection between atoms (fields in signatures) |
| Fact | A constraint that always holds |
| Predicate | A reusable, parameterized formula |
| Function | A parameterized expression that returns a value |
| Assertion | A property you claim should always be true |
| Instance | A concrete assignment of atoms and relations |
| Counterexample | An instance that violates an assertion |
one— Exactly one (required, singular)lone— Zero or one (optional, singular)some— One or more (required)set— Zero or more (optional, plural).— Relational join (navigation)^— Transitive closure*— Reflexive transitive closure~— Transpose (inverse)in— Membership/subset test'— Next-state value (temporal)
Alloy vs. TLA+
You might wonder when to use Alloy versus TLA+. They’re complementary:| Aspect | Alloy | TLA+ |
|---|---|---|
| Focus | Structural relationships | State transitions over time |
| Verification | SAT-based bounded search | Exhaustive model checking |
| Strength | Finding counterexamples, visualization | Temporal properties, liveness |
| Best for | ”Can this bad state exist?" | "Does this sequence happen correctly?” |
Where to Go Next
You’ve learned the core concepts: signatures, relations, facts, predicates, assertions, and how Alloy finds counterexamples. You’ve seen three examples, from simple to moderately complex. Next steps:- Alloy Syntax Guide - Comprehensive coverage of operators, quantifiers, temporal features, and everything you need for production specifications.
- MacAlloy - If you’re on macOS, this is the native tool I wrote for running Alloy specifications. Clean interface, fast feedback.
- Software Abstractions - Daniel Jackson’s book, the definitive guide to Alloy and lightweight formal methods.
- Alloy Documentation - Official tutorials and reference material.