From panther-ivy-plugin
Use when editing .ivy files, creating test specifications, or adding RFC bracket-tag annotations. Covers Ivy syntax, declaration types, module system, test spec patterns, and annotated specification writing.
How this skill is triggered — by the user, by Claude, or both
Slash command
/panther-ivy-plugin:ivy-writing-guideThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
This skill combines the Ivy language reference, test specification patterns, and RFC bracket-tag annotation conventions. Use it whenever editing or creating `.ivy` files.
This skill combines the Ivy language reference, test specification patterns, and RFC bracket-tag annotation conventions. Use it whenever editing or creating .ivy files.
Every Ivy file begins with a language version pragma:
#lang ivy1.7
This must be the first line. Version 1.7 is the current standard used in PANTHER protocol models.
type packet_id # Uninterpreted type
type message_type = {request, response, error} # Enumerated type
type cid # Abstract identifier
type stream_kind = {unidir, bidir} # Protocol-specific enum
interpret bit -> bv[1] # Bitvector interpretation
Built-in types: bool, nat (natural numbers), int (integers), bv[N] (bitvectors).
relation sent(P: packet_id, N: node_id)
relation connected(N1: node_id, N2: node_id)
relation conn_seen(C:cid)
Relations are boolean-valued and represent protocol model state.
function packet_dest(P: packet_id) : node_id
function last_pkt_num(C:cid, L:quic_packet_type) : pkt_num
individual my_id : node_id
individual the_cid : cid
Actions model state transitions with preconditions, effects, and postconditions:
action send(src: node_id, dst: node_id, p: packet_id) = {
require connected(src, dst);
require ~sent(p, dst);
sent(p, dst) := true;
ensure sent(p, dst)
}
require: precondition that must holdensure: postcondition that must hold after:=: deterministic assignmentassume: introduces an assumption (use sparingly, weakens the model)Properties that must hold in every reachable state:
invariant sent(P, N) -> connected(source(P), N)
invariant ack_pending(P) -> sent(P, dest(P))
Invariants are checked inductively: must hold initially and be preserved by every action.
axiom connected(X, Y) -> connected(Y, X) # Assumed true (not checked)
conjecture forall P. sent(P, dest(P)) -> ack_pending(P) # Checked but not inductive
object frame = {
type id
relation valid(F: id)
action create : id
action destroy(f: id)
}
thisInside an object, type this declares the object itself as a parameterized type:
object counter = {
type this
individual val(X: this) : nat
action increment(c: this) = { val(c) := val(c) + 1 }
}
object protocol = {
object client = {
action connect(srv: server.endpoint)
}
object server = {
type endpoint
action accept(c: client)
}
}
module ordered_set(elem) = {
type this
relation contains(S: this, E: elem)
action add(s: this, e: elem) returns (s2: this)
}
instance packet_set : ordered_set(packet_id)
instance node_set : ordered_set(node_id)
Isolates define verification boundaries separating specification from implementation:
isolate protocol_spec = {
object client = { ... }
object server = { ... }
specification { invariant ... }
}
include collections
include order
include my_protocol_types
Includes search the Ivy standard library and the current directory. No .ivy extension in the directive.
Every test specification follows this pattern:
#lang ivy1.7
# 1. Includes
include order
include {prot}_infer
include file
include ivy_{prot}_shim_{role}
include ivy_{prot}_{role}_behavior
# 2. Initialization
after init {
sock := net.open(endpoint_id.{role}, {role}.ep);
{role}.set_tls_id(0);
var extns := tls_extensions.empty;
extns := extns.append(make_transport_parameters);
call tls_api.upper.create(0, false, extns);
}
# 3. Exported actions (test mirror generates these)
export frame.ack.handle
export frame.stream.handle
export frame.crypto.handle
export packet_event
# 4. End-state verification
export action _finalize = {
require is_no_error;
require conn_total_data(the_cid) > 0;
}
Order matters. Critical includes:
ivy_{prot}_shim_{role}) -- bridges formal model to implementationivy_{prot}_{role}_behavior) -- encodes RFC requirementsafter init)Opens network sockets, sets TLS identifiers, creates transport parameter extensions, initializes TLS/security layer.
export declarations tell the test mirror which actions to generate randomly. Z3/SMT ensures generated actions satisfy all before clause constraints.
Called when the test completes. Performs heuristic end-state checks:
export action _finalize = {
require is_no_error;
require conn_total_data(the_cid) > 0;
}
{prot}_server_test_*.ivy): Ivy plays client, tests server IUT{prot}_client_test_*.ivy): Ivy plays server, tests client IUT{prot}_mim_test_*.ivy): Ivy plays man-in-the-middleBase test files define common structure. Variant files extend them:
#lang ivy1.7
include {prot}_server_test
# Weight attributes to bias generation
attribute frame.crypto.handle.weight = "5"
attribute frame.path_response.handle.weight = "5"
# Additional exports
export frame.new_connection_id.handle
# Variant-specific _finalize checks
after _finalize {
require migration_completed;
}
Higher weights make an action more likely to be chosen:
attribute frame.stream.handle.weight = "10" # Strongly prefer streams
attribute frame.rst_stream.handle.weight = "0.02" # Rarely generate resets
*_stream.ivy -- Basic stream data transfer*_connection_close.ivy -- Connection termination*_retry.ivy -- Retry mechanism testing*_migration.ivy -- Connection migration*_0rtt.ivy -- Zero-RTT early data*_timeout.ivy -- Timeout handling#lang ivy1.7 headerafter init block with socket/TLS setupexport declarations for mirror-generated actions_finalize with end-state checksEvery require, ensure, assume, or assert statement should include a bracket tag comment:
require conn_state = open; # [rfc9000:4.1]
require pkt.size <= max_packet_size; # [rfc9000:14.1, rfc9000:8.1]
ensure stream_data_delivered; # [rfc9000:2.2]
| Component | Format | Example |
|---|---|---|
| RFC number | rfc + number (no space) | rfc9000 |
| Section | colon + section number | :4.1 |
| Sub-section | dot-separated | :4.1.2 |
| Full tag | rfc{N}:{S} | rfc9000:4.1 |
*_requirements.yaml manifestivy_coverage (mode="stats") MCP toolivy_diagnostics MCP toolCreate {rfc}_requirements.yaml files for full traceability:
rfc: "RFC9000"
requirements:
rfc9000:4.1:
text: "A sender MUST NOT send data on a stream beyond the current limit"
section: "4.1"
level: MUST
layer: stream
testable: true
Forgetting after init blocks: Relations and functions start with arbitrary values unless explicitly initialized.
Ungrounded variables in invariants: invariant sent(P, N) means "for all P and N, sent(P,N) is true" -- probably not what you intended.
Overly strong invariants: Too strong will fail on initial state. Start weak, strengthen as needed.
Missing require clauses: Without preconditions, actions can be called in any state.
Circular includes: Ivy does not support circular include dependencies.
Using assume instead of require: assume weakens the model by introducing unverified assumptions.
Missing _finalize: Without _finalize, end-state properties are never checked.
Wrong role in test file: Server test = Ivy plays client. Period.
snake_case for actions/relations/functions. PascalCase for module names.specification and implementation blocks.after init: Explicitly initialize all mutable state.IMPORTANT: Always use ivy-tools MCP tools for Ivy verification operations. Never run ivy_check, ivyc, ivy_show, or ivy_to_cpp directly via Bash.
Used by: All specification writing workflows
Related skills:
Related agents:
npx claudepluginhub elniak/panther-ivy-plugin --plugin panther-ivy-pluginFetches IETF RFCs as plain text, stores in spec/ directory, and generates OCamldoc citations for OCaml modules, functions, and types. Use for implementing standards and protocol compliance docs.
Guides creation, editing, and verification of skills for AI coding agents using test-driven development with subagent scenarios. Use when authoring or debugging skills.