zqel CLI Reference¶
Normative for all command-line behaviour at the compiler version in this repository.
Contents¶
- zqel — compile and check
- zqel run — compile and execute a PROGRAM
- zqel guided — GS-next candidate request
- zqel extern — Verified Library toolchain
- Exit status
- Output format
- Profiles and their effect on compilation
- Targets and their effect on output
- Failure modes
1. zqel — compile and check¶
Synopsis¶
zqel <file.zqel> [--target st|rust|check] [--profile default|pilot|float64]
[--project <file.toml>] [--strict]
Arguments¶
file¶
Path to a .zqel source file. The file must contain either a normal program
(one or more FUNCTION/EXTERN FUNCTION definitions followed by one
FUNCTION_BLOCK) or a single EXTERN_REAL_CONTRACT declaration (§3.5 of the
Language Reference).
--target st|rust|check¶
Select the output mode. Default: check.
| Value | Effect |
|---|---|
check |
Verify the programme and print a structured summary. No code is emitted. |
st |
Emit IEC 61131-3 Structured Text to stdout. |
rust |
Emit a Rust struct + impl step() to stdout. |
When --target check, the compiler additionally runs representation analysis and
overflow analysis (numerical assurance chain, if applicable). These analyses are skipped when
emitting code directly with --target st or --target rust.
--profile default|pilot|float64¶
Select the assurance profile. Default: default.
| Value | Meaning |
|---|---|
default |
Development baseline. All features available; experimental features accepted. |
pilot |
Verified core (G1-closed at documented commit). Rejects experimental features; requires both ST and Rust backends; requires proved EXTERN warrants. |
float64 |
Enables global MATHREAL/LREAL → IEEE 754 binary64 representation. Same restrictions Same restrictions as default; adds Float64 lowering at codegen. |
See Language Reference §17 for the full gate table.
When --project is also given, [representation] real = "float64" in the TOML
sets the profile to float64 unless --profile pilot is explicitly given on the
command line (CLI profile takes precedence for pilot; TOML takes precedence for
float64).
--project <file.toml>¶
Load a project configuration file (see Project Configuration Reference). Enables the full numerical assurance chain:
MATHREAL/LREALmachine representation (from[representation])- Sampling models and discretisation bounds (
[sampling.*]) - Physical input error bounds (
[physical_errors]) - Algebraic rewrite policy (
[rewrite]) - External mathematical contracts and implementation warrants
(
[external_contracts],[external_implementations.*])
Without --project, numerical assurance analyses are not performed and MATHREAL/LREAL programmes
emit only under --profile float64.
When the source file contains an EXTERN_REAL_CONTRACT declaration, --project
loads the implementation warrants declared in the TOML and prints them alongside
the contract digest.
--strict¶
Treat warnings as errors. Compilation fails (exit code 1) if any warning is
generated. Without --strict, warnings are printed to stderr and compilation
continues.
Examples¶
# Verify and print assurance summary
zqel examples/motor.zqel
# Emit Structured Text
zqel examples/motor.zqel --target st
# Emit Rust
zqel examples/motor.zqel --target rust
# Full numerical assurance analysis via project file
zqel examples/t2/lowpass.zqel --project examples/t2/lowpass.toml
# Check a mathematical contract with implementation warrants
zqel examples/t2/external_pid.zqel --project examples/t2/external_pid.toml
# Verified-core profile
zqel examples/motor.zqel --profile pilot
# REAL programme — Float64 representation
zqel examples/analog/pid.zqel --profile float64 --target rust
# Treat warnings as errors in CI
zqel examples/pressure.zqel --strict
2. zqel run — Compile and execute a PROGRAM¶
zqel run compiles a PROGRAM to native Rust, links it against
zqel-runtime-host (or the platform adapter), and starts the scan loop.
The process image is shared via two memory-mapped files (mmap), one for
inputs and one for outputs. An external process (plant simulator, HMI,
supervisory tool) reads and writes those files to exchange data with the
running program.
Synopsis¶
zqel run <file.zqel> [--period-ns NS] [--scans N]
[--input PATH] [--output PATH]
[--manifest PATH]
Arguments¶
file¶
Path to a .zqel source file containing exactly one PROGRAM block.
If the file contains a FUNCTION_BLOCK instead, zqel run rejects it with an
error. Sub-blocks referenced by the PROGRAM (FUNCTION_BLOCK, SSFC) are
resolved from the same directory as file.
--period-ns NS¶
Scan period in nanoseconds. Default: 10000000 (10 ms).
The runtime executes one scan cycle every NS nanoseconds. If a scan takes
longer than the period, the next scan starts immediately (no skipped cycles,
no accumulated jitter).
--scans N¶
Number of scan cycles to run. 0 (the default) runs until the process
receives SIGINT (Ctrl-C).
--input PATH¶
Path for the input process image file. Default: /tmp/<name>-input.img.
If the file does not exist, zqel run creates it with a zeroed payload and a
valid header (magic, ABI version, schema hash, payload size). If it already
exists, its schema hash is validated on each scan; a mismatch causes the
runner to exit.
--output PATH¶
Path for the output process image file. Default: /tmp/<name>-output.img.
Created (or overwritten) by the runner on startup; the runner holds an exclusive write lock via a seqlock counter in the header. Readers must perform a seqlock-safe read (check the sequence counter before and after copying the payload).
--manifest PATH¶
Path to write the process image schema JSON manifest. Default:
<dir>/<name>.io.json in the same directory as --input.
The manifest is written (or overwritten) on every zqel run invocation,
before the runner starts. It is always up to date with the binary image files.
See Process Image Reference for the schema format.
What zqel run does¶
- Compiles the source with
FLOAT64_REPR(MATHREAL → IEEE 754 binary64 on the wire). - Generates a Rust runner binary (content-addressed; cached in
~/.cache/zqel/rust/). - Writes the manifest JSON.
- Creates the input image if it does not yet exist.
- Launches the runner subprocess.
The runner writes output on every scan and reads input at the start of each
scan. Both images use a seqlock header (seq field) to allow concurrent
reads without a mutex.
Examples¶
# Run the Pasteurizer PROGRAM with a 100 ms scan period
zqel run examples/pasteurizer/Pasteurizer.zqel --period-ns 100000000
# Run for exactly 1000 scans, then exit
zqel run examples/pasteurizer/Pasteurizer.zqel --scans 1000
# Custom image paths
zqel run my.zqel --input /dev/shm/my-in.img --output /dev/shm/my-out.img
# Print manifest to a named file
zqel run my.zqel --manifest /tmp/my.io.json
Supported I/O types¶
| zqel type | Wire encoding | Size |
|---|---|---|
BOOL |
BOOL_U8 (0 or 1) |
1 B |
INT |
INT16_LE |
2 B |
DINT |
INT32_LE |
4 B |
LINT |
INT64_LE |
8 B |
UINT |
UINT16_LE |
2 B |
UDINT |
UINT32_LE |
4 B |
ULINT |
UINT64_LE |
8 B |
BYTE |
UINT8_LE |
1 B |
WORD |
UINT16_LE |
2 B |
DWORD |
UINT32_LE |
4 B |
LWORD |
UINT64_LE |
8 B |
LREAL |
IEEE754_BINARY64_LE |
8 B |
MATHREAL |
IEEE754_BINARY64_LE |
8 B |
Array types and WSTRING are not yet supported in zqel run.
Fields are packed densely in declaration order: VAR_INPUT first, then VAR_OUTPUT, each starting at byte 0 within its own image file.
Runtime-owned values¶
zqel run does not expose elapsed scan time as a process image field.
Time-dependent constructs (T_elapsed, WITHIN, timer presets) are driven
by the runtime clock internally. An external process cannot inject a fake
scan period through the process image.
3. zqel guided — GS-next candidate request¶
zqel guided starts a Rust/Kani or Structured-Text/matiec/CBMC proof-carrying
candidate workflow. It accepts either a combinational block whose outputs are
constrained by RULE, or the narrow GS-v2-S stateful slice whose zqel register
rules define an exact one-step transition. Creating a request never accepts a
candidate or weakens a zqel proof.
Synopsis¶
zqel guided <file.zqel> --out <directory> [--target rust|st] [--agent codex|claude] [--model <model>]
[--cache-dir <directory>]
[--verify-candidate <candidate.json>]
[--proof kani|creusot|cbmc]
[--kani-unwind <n>]
[--cbmc-unwind <n>]
[--accept]
The command writes compiler-owned inputs for the candidate boundary:
request.json: port andPARAMETERtypes, ASSUMEs, caller and PARAMETER REQUIREs, state, invariants, transitions, RULEs, synthesis targets, source digest, and a machine-readableproof_scope;PROMPT.md: the restricted prompt for a candidate generator.
For the default --target rust, when its smaller optional renderer can lower the
same declaration, the command also writes candidate.creusot.rs, a Rust/Creusot skeleton with compiler-owned
requires and ensures. Otherwise it writes CREUSOT_UNAVAILABLE.md with the
specific unsupported construct; creusot_status.json binds that availability to
the source digest. The exact Kani/CBMC request remains usable.
--agent codex calls codex exec in read-only sandbox mode. --agent claude
calls Claude Code in non-interactive JSON-schema mode with built-in and MCP tools
and project customisation disabled. Both adapters normalise to the
same locally validated candidate.json schema; a provider never changes the proof
contract or acceptance checks. --codex remains a compatibility alias for
--agent codex; --model is passed through to the selected CLI. Every proposal is untrusted: the command does not install it,
emit PLC code, or claim a proof. A later proof gate must verify both the
compiler-owned contract and the candidate bytes.
After --accept, GS-next stores the candidate and its hash-bound receipt in
.zqel/guided-cache/ by default — or .lola/guided-cache/ while a project
still has the older directory (M9, #123). The key is the normalized declaration/proof
contract (target, ABI, ports, assumptions, parameter requirements, RULEs, and
synthesis targets), not the raw source digest; harmless formatting or comment
changes therefore reuse the accepted candidate. A later --agent codex or
--agent claude invocation for the same declaration writes that cached proposal
to candidate.json without starting an LLM. --cache-dir selects a different
local cache. Cache reuse never skips a requested --verify-candidate replay.
--target st writes candidate.st and candidate.cbmc.c. The former is the one
compiler-owned IEC function-block shell, the latter is the CBMC harness over the C
that matiec generated from that exact ST artifact. ST candidate verification
requires --proof cbmc, MATIEC_DIR (containing iec2c and lib/ieclib.txt),
and cbmc on PATH; a successful --accept writes candidate_accepted.st.
--verify-candidate injects the proposal's candidate_body into the one
compiler-generated hole. For Rust it emits a standalone symbolic Rust harness
and proves the zqel contract with Kani/CBMC (--proof kani), without adding any
Cargo dependency to this project. --proof creusot replays the generated Creusot
source in a temporary crate when a compatible local Creusot installation exists.
Both paths reject obvious escape hatches (unsafe, FFI, macros,
filesystem/process access, and panic/TODO placeholders). A successful replay is
still only proof evidence; candidate installation and target code emission remain
a separate acceptance gate. Passing --accept after a successful replay writes
the executable candidate_accepted.rs and acceptance.json; the latter binds the
zqel source, candidate body, proof backend, proof harness, and the original RULEs
by SHA-256.
Kani checks loop-unwinding assertions; --kani-unwind controls the default bound
(32). --cbmc-unwind applies the same fail-closed unwinding check to loops in an
ST candidate (matiec→C→CBMC). Raise either only for a candidate with a known larger static loop bound; an
unwinding failure is not a proof.
The current exact Kani ABI covers combinational BOOL, all fixed-width IEC
integer/bit-string types, TIME comparisons, statically sized/static-indexed arrays,
IEC wrapping arithmetic, conversions, conditionals, and bounded aggregates. It normalises CONSTs
and bounded zqel FUNCTIONs and quantifies PARAMETER values under their REQUIREs.
MATHREAL/LREAL and WSTRING representation boundaries, PRE/timer state,
composition/wiring, EXTERN calls, and large SORT artifacts are not approximated;
the proof_scope records those exclusions and rejects a source until its
dedicated transition or representation ABI exists.
The separate zqel-gs-*-stateful-v1 ABI covers one-step BOOL and fixed-width
integer register state with explicit priority and HOLD. Its proof harnesses
separate the unconditional initial relation from the inductive transition
relation; invariants in this first slice therefore read retained state only.
Timer state, arrays, derived outputs, composition, MATHREAL/LREAL, WSTRING, and
EXTERN remain fail-closed boundaries.
Example¶
zqel guided examples/pumpbank.gs --out build/pumpbank-proof --agent codex
zqel guided examples/pumpbank.gs --target st --out build/pumpbank-st-proof \
--verify-candidate build/pumpbank-st-proof/candidate.json --proof cbmc --accept
4. zqel extern — Verified Library toolchain¶
Sub-commands for authoring, verifying, and registering external function artifacts.
Synopsis¶
zqel extern <command> [options]
2.1 zqel extern check¶
zqel extern check
Validates the Verified Library (zqel/verified/manifest.json):
- All artifact entries have a
lola_contractfield. - All target entries have a
filefield pointing to an existing source. - SHA-256 digests match the stored source files.
- All clause entries have a valid
status(provedoraudited). - All
recipe_idreferences resolve to entries inwitness_recipes._REGISTRY.
Output. Prints a count of artifacts and targets, then lists issues (errors) and
pending scaffolds (non-errors from zqel extern init) separately. Advisory lines
note when a reverification tool is unavailable in PATH.
Exit status. 0 if no issues; 1 if any issue was found.
2.2 zqel extern verify¶
zqel extern verify <artifact-id> --target rust|st
Runs the verification recipe for the named artifact on the named backend target.
Does not write to the manifest (read-only). Use zqel extern register to seal the
result.
Arguments:
| Argument | Description |
|---|---|
artifact-id |
Artifact key in manifest.json (e.g. sat_add_i16). |
--target rust\|st |
Backend target. Must match a target entry in the manifest. |
Output. Prints artifact id, target, clause, recipe, tool, truncated sha256, then
the verification result (proved ✓ or failed ✗). On success, prints a suggested
zqel extern register command.
Effect. On success, writes a temporary .verify_<id>_<target>.json cache file
in zqel/verified/. This cache is reused (if fresh — within 10 minutes) by a
subsequent zqel extern register to avoid re-running the recipe.
Exit status. 0 on proved; 1 on failure or error.
2.3 zqel extern register¶
zqel extern register <artifact-id> --target rust|st [--audit <reason>]
Seals a verification warrant into the manifest. The only write path to the registry.
Arguments:
| Argument | Description |
|---|---|
artifact-id |
Artifact key in manifest.json. |
--target rust\|st |
Backend target. |
--audit <reason> |
Bypass recipe; record a manual audit with the given reason string. Sets clause status to audited. |
Behaviour without --audit.
- Looks for a fresh verify result (from a recent
zqel extern verifyrun). - If not found, re-runs the recipe automatically.
- Checks that
lola_contracthas not changed since the verify result was produced. - Checks that the artifact source file has not changed since the verify result.
- Writes
sha256and clausestatus/witnessinto the manifest atomically. - Deletes the verify cache file.
Behaviour with --audit <reason>.
Records the current sha256 and sets clause status to audited with the given
reason and a timestamp. No recipe is executed.
Exit status. 0 on success; 1 on error.
2.4 zqel extern init¶
zqel extern init <contract-name> --target rust|st [--by <artifact-id>] [--ensures <clause>]
Scaffolds a new artifact entry. Creates:
- A source stub in
zqel/verified/<artifact-id>.<ext>. - A manifest entry in
zqel/verified/manifest.json. - A recipe stub function in
zqel/witness_recipes.py.
Existing files are not overwritten.
Arguments:
| Argument | Description |
|---|---|
contract-name |
Human function name (e.g. abs_nonneg). Used as the stub function name. |
--target rust\|st |
Backend target for the scaffold. |
--by <artifact-id> |
Artifact key to use in the manifest. Default: <contract-name>_i16. |
--ensures <clause> |
Pre-fill the lola_contract ENSURE clause in the manifest. Default: placeholder ENSURE .... |
Output. Lists created and already-existing files. Prints next-step instructions (implement stub → verify → register → check).
Exit status. Always 0.
3. Exit Status¶
| Code | Meaning |
|---|---|
0 |
Success. For zqel <file>.zqel --target check, verification passed and all assurance gates cleared. For zqel --target st/rust, code was emitted successfully. |
1 |
Failure. Compilation error (type error, verification failure, ambiguity, profile gate), OS error (file not found), or parse error. Error details printed to stderr. |
4. Output Format (--target check)¶
When --target check (the default), zqel prints a structured summary to stdout.
Always printed¶
Compilation succeeded: <BlockName>
G2: <verdict>
Claims: <n>
G2 is the gate verdict from the contract assurance check: passed, not-required,
or unrated. Claims is the count of step-phase proof obligations.
Printed for a programme with a Representation Boundary¶
$ python -m zqel examples/analog/integrator_bounded.zqel --profile float64
Compilation succeeded: IntegratorBounded
4 output properties verified
Overflow safety: decided by the float64 range gate, not here
conditional on the deployment holding the cycle time to 1s — REQUIRED, not enforced (#168)
MATHREAL/LREAL with Float64 policy: the fixed-point gate does not apply. …
There used to be a block here showing certified ranges lowered to fixed-point
widths ([-12.345, 12.345] -> Q7.8). That analysis was removed with #161; the
representation is declared by a profile now, not inferred, and the finite-range
decision belongs to the float64 range gate.
Printed when overflow analysis runs¶
REAL overflow-safety (DT <= 1s): safe [proved]
Or, if findings exist:
REAL overflow-safety (DT <= 1s): findings
<finding description>
Printed when Engineering Error Report is available (--project)¶
── Engineering Error Report (<var>) ─────────────────────────────
<formatted report>
Printed when EXTERN evidence is available¶
EXTERN <artifact-id>/<target>[,<target>] [<worst-warrant>]
or (legacy EXTERN array path):
EXTERN array '<output>': realised by <file> [<status>], discharging `<contract>`
witness: <witness>
Printed when REQUIRE premises exist¶
Caller action: discharge or monitor <id1>, <id2>, ...
EXTERN_REAL_CONTRACT check output (--project with contract file)¶
EXTERN_REAL_CONTRACT <Name>
digest: <16-hex-chars>
inputs: <var1>, <var2>
outputs: <var1>
states: <var1> (only printed if states exist)
implementations (<n>):
<backend>: [<status>] <impl-id> ε_out={<var>≤<val>} ε_st={<var>≤<val>}
5. Profiles and Their Effect on Compilation¶
See Language Reference §17 for the full gate table. Quick reference:
| Feature | default |
pilot |
float64 |
|---|---|---|---|
| WSTRING | experimental | rejected | experimental |
| Bitstring arithmetic | experimental | rejected | experimental |
| TIME inputs/outputs | experimental | rejected | experimental |
| Variable timer presets | experimental | rejected | experimental |
| MATHREAL overflow | advisory | hard error | advisory |
| MATHREAL backends | requires float64 |
requires float64 + overflow-safe |
enabled |
| Required targets | — | st + rust | — |
| EXTERN warrant floor | — | proved | — |
| Fresh EXTERN evidence | — | required | — |
6. Targets and Their Effect on Output¶
| Target | stdout | stderr | EXTERN behaviour |
|---|---|---|---|
check |
Assurance summary | Warnings | Entailment check + warrant lookup |
st |
IEC 61131-3 ST source | Warnings | ST artifact embedded in output |
rust |
Rust struct + impl | Warnings | Rust artifact embedded in output |
When --target st or --target rust, the programme must already pass check
semantics (the compiler verifies before emitting). An EXTERN with no artifact for
the chosen backend raises NotImplementedError and compilation fails.
7. Failure Modes¶
| Error | Exit | Cause |
|---|---|---|
| Parse error | 1 | Syntax error in the source file |
| Sema error | 1 | Type error, duplicate declaration, undefined reference, malformed expression |
invariant-init |
1 | Initial state violates an INVARIANT |
invariant-step |
1 | A cycle can violate an INVARIANT (counterexample printed) |
| Ambiguity | 1 | Two equal-priority rules can fire simultaneously with different results |
| Division obligation | 1 | Cannot prove divisor ≠ 0 |
| Profile gate | 1 | Source uses a feature rejected by the active profile |
| Backend EXTERN missing | 1 | EXTERN FUNCTION with BY key not found, or no artifact for the chosen backend |
| File not found | 1 | Source file or --project TOML does not exist |
| Warning (strict) | 1 | Any warning when --strict is active |
Errors are printed to stderr. stdout receives code only on success with --target st
or --target rust.