Validation-First Development Skill using Quint for formal specifications - comprehensive skill handling both design (planning) and execution (verification) through formal state machine specifications.
Inherits all available tools
Additional assets for this skill
This skill inherits all available tools. When active, it can use any tool Claude has access to.
This skill enables formal specification-driven development using Quint (formal specification language for distributed systems). It guides you through defining state machines, writing invariants, verifying properties, and generating implementation stubs.
This skill handles both the DESIGN phase (planning specifications) and the EXECUTION phase (creating and verifying artifacts).
Static Assertions (compile-time) > Test/Debug Contracts > Runtime Contracts
Before using Quint for state machine modeling, ensure compile-time verifiable properties are encoded in the type system:
| Language | Tool | Command |
|---|---|---|
| Rust | static_assertions crate | cargo check |
| TypeScript | satisfies, as const | tsc --strict |
| Python | assert_type, Final | pyright --strict |
Quint complements static verification by modeling state machines and temporal properties that cannot be expressed in types alone.
PLAN -> CREATE -> VERIFY -> GENERATE
| | | |
v v v v
Design Write Check Create
Specs .qnt Props Stubs
[<start>Start] -> [Validate Preconditions]
[Validate Preconditions] -> [Quint Available?]
[Quint Available?] no -> [Install Quint]
[Quint Available?] yes -> [Detect Specs]
[Install Quint] -> [Detect Specs]
[Detect Specs] -> [Specs Exist?]
[Specs Exist?] no -> [Create New Spec]
[Specs Exist?] yes -> [Analyze Existing]
[Create New Spec] -> [Define State Machine]
[Analyze Existing] -> [Define State Machine]
[Define State Machine] -> [Write Invariants]
[Write Invariants] -> [Write Actions]
[Write Actions] -> [Verify Properties]
[Verify Properties] -> [Verification OK?]
[Verification OK?] no -> [Fix Violations]
[Fix Violations] -> [Verify Properties]
[Verification OK?] yes -> [Run Tests]
[Run Tests] -> [Tests OK?]
[Tests OK?] no -> [Refine Tests]
[Refine Tests] -> [Run Tests]
[Tests OK?] yes -> [Generate Stubs]
[Generate Stubs] -> [<end>Complete]
You are a specification-first development specialist designing formal specifications with Quint BEFORE code changes.
CRITICAL: This is a DESIGN planning task. You design specification artifacts that will be created during the run phase.
Understand Requirements
Artifact Detection (Conditional)
fd -e qnt $ARGUMENTS
command -v quint
Design Specification Architecture
Prepare Run Phase
.outline/specs/*.qntquint verify, quint testUse sequential-thinking for:
- Modeling state transitions
- Planning action sequences
- Invariant decomposition
Use actor-critic-thinking for:
- Evaluating state machine completeness
- Challenging invariant strength
- Alternative modeling approaches
Use shannon-thinking for:
- State space analysis
- Temporal property coverage
- Verification bounds
// Target: .outline/specs/{module}.qnt
module {ModuleName} {
//---------------------------------------------------------
// From requirement: {requirement text}
//---------------------------------------------------------
// State Variables
var state1: StateType1
var state2: StateType2
// Invariant: {description from requirement}
val invariant_1: bool = {
// Property to verify
state1.property && state2.property
}
// Action: {description from requirement}
action action_1 = {
// State transition logic
state1' = transform(state1)
}
// Temporal property
temporal property_1 = always(invariant_1)
}
Requirements Analysis
Specification Architecture
Target Artifacts
.outline/specs/*.qnt file listVerification Commands
quint typecheck for syntaxquint verify --main={Module} for propertiesquint test for examples# Create .outline/specs directory
mkdir -p .outline/specs
Create Quint modules from the plan design:
// .outline/specs/{Module}.qnt
// Generated from plan design
module {ModuleName} {
//---------------------------------------------------------
// Source Requirements: {traceability from plan}
//---------------------------------------------------------
// === State Variables ===
// From plan: {state design}
var state1: StateType1
var state2: StateType2
// === Initialization ===
action init = {
state1' = initialValue1,
state2' = initialValue2
}
// === Invariants ===
// Traces to: {requirement reference}
val invariant_name: bool = {
// Property from plan design
state1.property && state2.property
}
// === Actions ===
// Traces to: {requirement reference}
action action_name = {
// State transition from plan
state1' = transform(state1)
}
// === Temporal Properties ===
temporal always_invariant = always(invariant_name)
}
# Check Quint availability
command -v quint >/dev/null || exit 11
# Verify artifacts exist
fd -e qnt .outline/specs >/dev/null || exit 12
# Typecheck all specs
quint typecheck .outline/specs/*.qnt || exit 12
# Verify main module
quint verify --main=$MODULE .outline/specs/*.qnt || exit 13
# Run specification tests
quint test .outline/specs/*.qnt || exit 14
# Multi-seed verification for thoroughness
for seed in 12345 67890 11111 22222 33333; do
echo "Verifying with seed $seed..."
quint verify --main=$MODULE --seed=$seed --verbose \
--max-steps=100 .outline/specs/*.qnt || exit 13
done
# Comprehensive testing with coverage
quint test --verbose --seed=$RANDOM --coverage \
--timeout=60 .outline/specs/*.qnt || exit 14
After verification, generate language-specific implementation stubs:
// Generated from Quint invariant: {invariant_name}
fn assert_invariant(state: &State) {
assert!(
state.property1 && state.property2,
"Invariant: {invariant_description}"
);
}
// Generated from Quint action: {action_name}
fn action_name(state: &mut State) {
// Pre: {precondition from spec}
state.field = transform(state.field);
// Post: {postcondition from spec}
}
// Generated from Quint invariant: {invariant_name}
function assertInvariant(state: State): void {
invariant(
state.property1 && state.property2,
"Invariant: {invariant_description}"
);
}
// Generated from Quint action: {action_name}
function actionName(state: State): State {
// Pre: {precondition from spec}
return { ...state, field: transform(state.field) };
// Post: {postcondition from spec}
}
# Generated from Quint invariant: {invariant_name}
def assert_invariant(state: State) -> None:
assert state.property1 and state.property2, \
"Invariant: {invariant_description}"
# Generated from Quint action: {action_name}
def action_name(state: State) -> State:
# Pre: {precondition from spec}
return state._replace(field=transform(state.field))
# Post: {postcondition from spec}
quint verify --main=Module spec.qnt
quint test spec.qnt
quint typecheck spec.qnt
quint verify --main=Module --seed=$RANDOM --verbose spec.qnt
quint test --verbose --coverage spec.qnt
quint verify --main=Module --seed=$RANDOM --verbose --max-steps=100 --invariant=prop spec.qnt
quint test --verbose --seed=$RANDOM --coverage --timeout=60 spec.qnt
| Code | Meaning | Remediation |
|---|---|---|
| 0 | Success | Continue to implementation |
| 11 | Quint not installed | npm install -g @informalsystems/quint |
| 12 | Invalid specification | Check syntax, run quint typecheck |
| 13 | Specification violation | Review invariants, fix state transitions |
| 14 | Property verification failed | Strengthen properties or fix spec |
| 15 | Mapping incomplete | Complete stub generation |
# Generate property tests with proptest
# Map Quint invariants to runtime assertions
# Use derive(Debug, PartialEq) for state
# Generate property tests with hypothesis
# Map invariants to assert statements
# Use dataclasses for state representation
# Generate property tests with fast-check
# Map invariants to type guards
# Use strict types for state
# Generate property tests with gopter
# Map invariants to panic checks
# Use structs for state
# Generate property tests with jqwik
# Map invariants to assert statements
# Use records for immutable state
# Generate property tests with FsCheck
# Map invariants to Code Contracts
# Use records for state
# Generate property tests with rapidcheck
# Map invariants to assert macros
# Use const correctness for state
| Language | Property Test Library |
|---|---|
| Rust | proptest |
| Python | hypothesis |
| TypeScript | fast-check |
| Go | gopter |
| Java | jqwik |
| Symptom | Cause | Resolution |
|---|---|---|
| Exit 11 | Quint not installed | npm install -g @informalsystems/quint |
| Exit 12 | Syntax error in .qnt | Run quint typecheck, fix errors |
| Exit 13 | Invariant violated | Check state transitions, fix logic |
| Exit 14 | Property test failed | Review property, strengthen or fix |
| Exit 15 | Stub generation failed | Check module exports |
| Verification timeout | State space too large | Reduce max-steps or simplify spec |
| Non-determinism issues | Missing seed | Use explicit --seed |
# Increase max steps
quint verify --main=Module --max-steps=200 spec.qnt
# Use explicit nondet with seed
quint verify --seed=12345 spec.qnt
# Break into smaller invariants
# Verify each separately
# Regenerate stubs from updated spec
# Run property tests to catch mismatches
# Typecheck specification
quint typecheck spec.qnt
# Verify with verbose output
quint verify --main=Module --verbose spec.qnt
# Run tests with coverage
quint test --verbose --coverage spec.qnt
# Find invariant definitions
rg 'val.*:.*bool' .outline/specs/
# Find action definitions
rg 'action\s+\w+' .outline/specs/
# List temporal properties
rg 'temporal' .outline/specs/
module DistributedCounter {
type NodeId = str
type Message = { sender: NodeId, value: int }
var nodes: Set[NodeId]
var counters: NodeId -> int
var messages: Set[Message]
pure def init = {
nodes' = Set("node1", "node2", "node3") and
counters' = nodes'.mapBy(n => 0) and
messages' = Set()
}
action node_increment(node: NodeId): bool = {
node.in(nodes) and
counters' = counters.set(node, counters.get(node) + 1) and
messages' = messages.union(
nodes.exclude(Set(node)).map(n => {
sender: node,
value: counters.get(node) + 1
})
)
}
action node_receive(node: NodeId, msg: Message): bool = {
msg.in(messages) and
node.in(nodes) and
counters' = counters.set(node, max(counters.get(node), msg.value)) and
messages' = messages.exclude(Set(msg))
}
val all_counters_non_negative =
nodes.forall(n => counters.get(n) >= 0)
val messages_bounded =
messages.size() <= nodes.size() * 100
temporal eventually_consistent =
eventually(nodes.forall(n1 =>
nodes.forall(n2 =>
counters.get(n1) == counters.get(n2)
)
))
}
# Type check
quint typecheck distributed_counter.qnt
# Verify invariants
quint verify --main=DistributedCounter \
--invariant=all_counters_non_negative \
--invariant=messages_bounded \
--seed=$RANDOM \
--verbose \
distributed_counter.qnt
# Run tests
quint test --verbose --coverage distributed_counter.qnt
use std::collections::{HashMap, HashSet};
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct NodeId(String);
#[derive(Debug, Clone)]
pub struct Message {
sender: NodeId,
value: i32,
}
pub struct DistributedCounter {
nodes: HashSet<NodeId>,
counters: HashMap<NodeId, i32>,
messages: Vec<Message>,
}
impl DistributedCounter {
pub fn new(node_ids: Vec<String>) -> Self {
let nodes: HashSet<_> = node_ids.into_iter().map(NodeId).collect();
let counters: HashMap<_, _> = nodes.iter()
.map(|n| (n.clone(), 0))
.collect();
let instance = Self {
nodes,
counters,
messages: Vec::new(),
};
instance.assert_invariants();
instance
}
pub fn node_increment(&mut self, node: &NodeId) {
assert!(self.nodes.contains(node), "node not in system");
let current = *self.counters.get(node).unwrap();
self.counters.insert(node.clone(), current + 1);
for other_node in &self.nodes {
if other_node != node {
self.messages.push(Message {
sender: node.clone(),
value: current + 1,
});
}
}
self.assert_invariants();
}
pub fn node_receive(&mut self, node: &NodeId, msg: Message) {
let msg_idx = self.messages.iter()
.position(|m| m.sender == msg.sender && m.value == msg.value)
.expect("message not found");
self.messages.remove(msg_idx);
let current = *self.counters.get(node).unwrap();
self.counters.insert(node.clone(), current.max(msg.value));
self.assert_invariants();
}
fn assert_invariants(&self) {
// all_counters_non_negative
for value in self.counters.values() {
assert!(*value >= 0, "Invariant violated: counter negative");
}
// messages_bounded
let max_messages = self.nodes.len() * 100;
assert!(
self.messages.len() <= max_messages,
"Invariant violated: too many messages"
);
}
}
#[cfg(test)]
mod tests {
use super::*;
use proptest::prelude::*;
#[test]
fn test_init_satisfies_invariants() {
let counter = DistributedCounter::new(vec![
"node1".into(),
"node2".into(),
]);
// If we reach here, invariants hold
}
proptest! {
#[test]
fn test_operations_preserve_invariants(
node_count in 1usize..5,
op_count in 0usize..50
) {
let nodes: Vec<_> = (0..node_count)
.map(|i| format!("node{}", i))
.collect();
let mut counter = DistributedCounter::new(nodes.clone());
for i in 0..op_count {
let node_id = NodeId(nodes[i % nodes.len()].clone());
counter.node_increment(&node_id);
}
// If we reach here, invariants always held
}
}
}
| Scenario | Better Alternative |
|---|---|
| Simple CRUD operations | Test-driven development |
| Non-concurrent systems | Design-by-contract |
| Mathematical proofs | Proof-driven (Lean 4) |
| Type-level constraints | Type-driven (Idris 2) |
| Performance-critical code | Benchmark-driven |
| Rapid prototyping | Test-driven development |
# Example GitHub Actions
- name: Verify Specification
run: |
npm install -g @informalsystems/quint
quint verify --main=Module spec.qnt
To use this skill in Claude Code:
Use the validation-first skill to develop a distributed counter system
Or directly:
/validation-first DistributedCounter counter_spec.qnt
The agent will guide you through the entire specification-first development workflow.