Skip to main content

Verum.toml — Manifest Reference

The canonical project manifest is Verum.toml. The CLI also reads the legacy verum.toml spelling, preferring the canonical file when both exist. Project scaffolding still writes the lowercase spelling. Keep one manifest per project.

This page describes the host CLI schema. The Verum-side core.cog.manifest API has a separate schema and is not the parser used by the host CLI.

Minimal example​

[cog]
name = "my-project"
version = "0.1.0"

[language]
profile = "application"

[cog] — package identity​

[cog]
name = "my-project" # required
version = "0.1.0" # required
authors = ["Alice <a@example.com>"]
description = "A short description"
license = "MIT OR Apache-2.0"
homepage = "https://example.com"
repository = "https://github.com/me/my-project"
keywords = ["web", "async"]
categories = ["network"]

The [cog] section is for metadata only. edition, profile, rust-min, readme, documentation, and verification are not recognised fields — the corresponding concepts live in the sections below.

[language] — language profile​

[language]
profile = "application" # application | systems | research
ProfileUnsafeDependent typesTarget audience
application (default)—opt-in~80 % of users — no unsafe, refinements + runtime checks
systems✓✓allocators, drivers, OS code
research✓✓ + required verificationproof-heavy code

[dependencies], [dev_dependencies], [build_dependencies]​

[dependencies]
serde = "1.4"
tokio = { version = "2.0", features = ["rt"] }
mylib = { path = "../mylib" }
data = { git = "https://github.com/me/data", rev = "abc123" }

[dev_dependencies]
criterion = "0.5"

[build_dependencies]
codegen = "0.2"

Note the underscore spelling: dev_dependencies, not dev-dependencies.

Detailed dependency fields: version, path, git, branch, tag, rev, features, optional, and default-features (alias default_features). Unknown fields are rejected, including ipfs, registry, package, and workspace.

For source publication, every entry in [dependencies] needs an explicit valid version requirement. Detailed entries preserve the feature list and both Boolean options. Source fields (path, git, branch, tag, rev) are refused even when a version is present; publication does not rewrite them. An explicit "*" is accepted, while a missing version is an error. See Cog packages.

Version specifiers follow SemVer:

  • "1.4" — ^1.4 (>= 1.4.0, < 2.0.0).
  • "=1.4.2" — exactly 1.4.2.
  • "~1.4.2" — >= 1.4.2, < 1.5.0.
  • ">=1.4, <2.0" — explicit range.

[features]​

[features]
default = ["std", "tls"]
std = []
tls = ["openssl"]
gpu = ["opencl"]

The CLI accepts verum build --features gpu and --all-features. Publication preserves feature declarations and dependency options. Consumer activation of optional dependencies, default features, and transitive feature unification is not yet fully integrated; declaring these fields does not establish that all dependency edges will be selected correctly.

[build] — basic build settings​

[build]
target = "native" # or a target triple
opt_level = 2 # 0-3
incremental = true
lto = false # bool here; use [lto] for strategies
codegen_units = 16
panic = "unwind" # unwind | abort

[profile.dev], [profile.release], [profile.test], [profile.bench]​

Per-profile overrides. Each profile carries:

[profile.release]
tier = "1" # "0" interpreter | "1" aot (aliases accepted)
verification = "certified" # see "verification levels" table below
opt_level = 3
debug = false
debug_assertions = false
overflow_checks = false
lto = true
incremental = false
codegen_units = 1
cbgr_checks = "optimized" # all | optimized | proven

tier accepts the strings "0", "interpreter", "interp" for interpreter mode and "1", "aot", "release", "native" for AOT.

Verification levels — fourteen-strategy ladder​

The verification field of every profile (and default_strategy of [verify] below) accepts one of fifteen lowercase strings: the fourteen verification strategies admitted by the grammar plus the tooling-side-only none sentinel. Strategies are ordered on a strictly monotone ν-ordinal ladder so promotions are always checked in one direction (lax → strict).

Valueν-ordinalStrategyWhen to pick
none—Unchecked (tooling-only sentinel — does not correspond to a @verify(...) attribute)Never auto-selected; only by explicit override.
runtime0Runtime assertions; no formal proofDefault for the application profile.
static1Dataflow + CBGRTrivially decidable refinements without SMT.
fast2Capability router with reduced timeout (≤ 100 ms / goal)UNKNOWN-tolerant; iterative development.
complexity_typed< ω (n)Bounded-arithmetic verification (V₀ / V₁ / S¹₂ / V_NP / V_PH / IΔ₀); polynomial-time; CI ≤ 30 sCrypto protocols, real-time, embedded.
formalωFull SMT verification (default strategy)Default dev for research profile.
proofω + 1User tactic block + kernel re-check (dominates SMT; admits induction)Theorems / foundational lemmas.
thoroughω · 2Portfolio race with extended timeout; mandatory decreases / invariant / frameCatches missing specs.
reliableω · 2 + 1thorough + cross-solver agreement: two adapters must both return UNSATUNKNOWN on disagreement.
certifiedω · 2 + 2reliable + certificate materialisation + multi-format exportDefault release for research profile; required for .verum-cert export (Coq / Lean / Dedukti / Metamath).
coherent_staticω · 2 + 3Weak operational coherence — α-cert + symbolic ε-claim; polynomial in |P|·|φ|; CI ≤ 60 sProduction fallback for coherence-required code.
coherent_runtimeω · 2 + 4Hybrid operational coherence — α-cert + runtime ε-monitor; trace-bounded; CI ≤ 5 minHybrid coherence; runtime monitoring acceptable.
coherentω · 2 + 5Strict operational coherence — α/ε bidirectional check; single-exponential; CI ≤ 30 minCritical-safety code requiring full operational coherence.
synthesize≤ ω · 3 + 1Inverse proof search (synthesis); holes filled by capability-router-dispatched SyGuS adapterCost is unbounded — opt-in only.
assume—No verification is performed — escape hatch off the ladderAudit-tracked via verum audit --assumptions; use only when the obligation is established outside the verification pipeline.

Direction — @verify(proof) on a function compiling under @verify(runtime) is always accepted (lax → strict, monotone in ν-ordinal). The reverse requires re-proof and is rejected by the level-inference pass.

See Gradual verification for the full ν-coordinate ordering, capability-router dispatch table, and cost / completeness trade-off per strategy.

[verify] — formal verification​

[verify]
# Base behaviour
default_strategy = "formal" # runtime | static | formal | fast |
# thorough | certified | synthesize
solver_timeout_ms = 10_000
enable_telemetry = true # write .verum/state/smt-stats.json
persist_stats = true
fail_on_divergence = true # certified + cross-verifier divergence = build fail

# Budget / profiling knobs — see `docs/verification/performance.md` and
# `docs/verification/cli-workflow.md` for the workflow.
total_budget = "120s" # fail the build past this wall-clock
slow_threshold = "5s" # flag functions slower than this
profile_slow_functions = true # enable profiler when --profile given
profile_threshold = "1s" # only profile functions past this

# On-disk cache
cache_dir = ".verum/verify-cache"
cache_max_size = "500MB"
cache_ttl = "30d" # evict entries older than this

# Shared CI cache — S3 or Redis, reads + writes proofs across runs.
distributed_cache = "s3://my-bucket/verify-cache"

# Per-module override: narrower scope than top-level [verify].
[verify.modules."crypto.signing"]
strategy = "certified"
solver_timeout_ms = 60_000

# Named profiles — selected via `verum verify --verify-profile <name>`.
# Inheritance: CLI flag > profile > [verify] > default.
[verify.profiles.release]
default_strategy = "certified"
solver_timeout_ms = 300_000
fail_on_divergence = true

[verify.profiles.ci]
default_strategy = "fast"
solver_timeout_ms = 3_000
total_budget = "60s"

Strategy-specific timeout multipliers: fast 0.3×, thorough 2×, certified 3×, synthesize 5×.

Precedence (highest → lowest):

  1. CLI flag (--solver, --timeout, --budget, --distributed-cache, …).
  2. Active [verify.profiles.<name>] if --verify-profile <name> is set.
  3. Per-module override in [verify.modules."module.path"] (for functions that live in that module and its descendants).
  4. Top-level [verify].
  5. Built-in default.

Human-readable durations: s, m, h, d suffixes (or a bare integer = seconds).

[verify.solver] — solver tuning​

Direct passthroughs to the backend configs (Z3Config, Cvc5Config) and the intermediate verifier configs — see Architecture → SMT integration → Configuration knobs for the full matrix and which solver-adapter parameter scope each setting reaches.

A misspelled key here is an ERROR, not a shrug

[verify.solver] and its sub-tables deserialize with deny_unknown_fields. Writing [verify.solver.sep-logic] (hyphen) or max_unfoldng_depth fails the manifest parse and names the offender, rather than being dropped.

Two consequences worth knowing:

  • A partial table is fine. Every field defaults, so [verify.solver.qe] simplify_level = 0 leaves the other six keys of that table — and the other nine tables — exactly as they were.
  • The values are read once, early. verum build and verum verify install this block before any verification work begins; the solver layer refuses a later install rather than let one component run on the manifest and another on the defaults.

[smt], [smt.fallback], [smt.portfolio] and [smt.validation] appear in verum_smt's own Rust doc-comments. They are NOT an alternative spelling for a project manifest: that loader (SmtConfig::from_toml_file) reads a standalone file and is called from one example programme. In a Verum.toml, [verify.solver.*] is the surface.

[verify.solver]
# Solver selection
backend = "auto" # auto | auto | portfolio | capability_router

# Z3 context tuning — `verum_smt::z3_backend::Z3Config`
[verify.solver.z3]
enable_proofs = true
minimize_cores = true
enable_interpolation = false
global_timeout_ms = 30_000 # context-level cap (Config::set_timeout_msec)
memory_limit_mb = 8_192 # process-wide (set_global_param)
enable_mbqi = true
enable_patterns = true
random_seed = 42 # reproducibility (smt.random_seed)
auto_tactics = true # auto_config Config param

# cvc5 adapter tuning — `verum_smt::cvc5_backend::Cvc5Config`
# (this used to repeat the `[verify.solver.smt-backend]` header above,
# which is a duplicate-key error: two backends with different fields
# cannot share one table)
[verify.solver.cvc5]
logic = "ALL" # ALL | QF_LIA | QF_LRA | QF_BV |
# QF_NIA | QF_NRA | QF_AX |
# QF_UFLIA | QF_AUFLIA
timeout_ms = 30_000 # tlimit-per
incremental = true
produce_models = true
produce_proofs = true
produce_unsat_cores = true
preprocessing = true # preprocess-only=false
quantifier_mode = "auto" # none | ematching | cegqi | mbqi | auto
random_seed = 42
verbosity = 0 # 0–5, saturates at 5

# Quantifier elimination
[verify.solver.qe]
timeout_ms = 5_000
simplify_level = 2 # 0=skip | 1=simplify | 2=+propagate-values | 3=+ctx-simplify
use_qe_lite = true
use_qe_sat = true
use_model_projection = true
use_skolemization = true

# Interpolation
[verify.solver.interpolation]
algorithm = "MBI" # McMillan | Pudlak | Dual | Symmetric | MBI | PingPong | Pogo
strength = "balanced" # weakest | balanced | strongest
simplify = true
timeout_ms = 5_000
quantifier_elimination = true # if false, project_onto_shared returns input verbatim
max_projection_vars = 100 # MBI bails when elimination set exceeds this

# Static verification (bounds elimination)
[verify.solver.static]
timeout_ms = 30_000 # global
constraint_timeout_ms = 100 # per-constraint
enable_proofs = true
enable_unsat_cores = true
minimize_cores = true
enable_caching = true
max_cache_size = 10_000
auto_tactics = true
memory_limit_mb = 4_096

# Bisimulation
[verify.solver.bisimulation]
max_depth = 100
timeout_ms = 30_000
generate_counterexamples = true
infinite_strategy = "bounded_unfolding" # coinduction | up_to | bounded_unfolding

# Separation logic
[verify.solver.sep_logic]
entailment_timeout_ms = 5_000
max_unfolding_depth = 10
enable_frame_inference = true # set false to skip residual computation (~30% encoder reduction)

# Unsat-core extraction
[verify.solver.unsat_core]
minimize = true
quick_extraction = false
max_iterations = 100
timeout_ms = 10_000
proof_based = false

# Optimizer (MaxSAT, Pareto)
[verify.solver.optimizer]
incremental = true # if false, push/pop are no-ops
max_solutions = 100 # Pareto-front cap; the field is
# `Maybe<usize>`, so a negative
# sentinel cannot parse — omit the
# key for the default, which is 100
# (`unwrap_or(100)`), NOT unbounded
timeout_ms = 30_000
enable_cores = true
method = "lexicographic" # lexicographic | pareto | independent | box

# Parallel portfolio
[verify.solver.parallel]
num_workers = 8
timeout_ms = 30_000
enable_sharing = true # broader gate over enable_lemma_exchange
enable_lemma_exchange = true # effective only when enable_sharing is also true
race_mode = true
lemma_exchange_interval_ms = 500
max_lemmas_per_exchange = 10
enable_cube_and_conquer = false
cubes_per_worker = 4

# Verification cache (cross-build SMT result reuse)
[verify.solver.cache]
max_size = 2_000 # entries
max_size_bytes = "500MB"
ttl = "30d"
statistics_driven = true # cache only expensive queries
min_decisions_to_cache = 1_000
min_conflicts_to_cache = 100
min_solve_time_ms = 100
Solver parameter-scope discipline

Each tunable in this section is wired to a specific scope inside the SMT layer (process-global, per-context, or per-query). The mapping is not interchangeable — assigning a parameter at the wrong scope can silently no-op. The verifier picks the correct scope per key, and the operator's manual at Verification → Solver tuning → Parameter-scope discipline documents every knob the manifest exposes.

Recap: how the manifest reaches the solver​

The full chain when you set a value in verum.toml:

Verum.toml [verify.solver.<sub>]
↓ (VerifyConfig.solver — verum_cli::config)
verum_smt::config::install(…) ← once, before any verification work
↓ (verum_smt::config::effective)
Subsystem constructors (QuantifierEliminator::new, StaticVerifier,
SeparationLogic::new, SepLogicEncoder::new)
↓ (consulted by the verifier method)
Backend config (verum_smt::z3_backend::Z3Config,
verum_smt::cvc5_backend::Cvc5Config)
↓ (per-call wiring on each fresh solver)
Adapter Params / SMT-LIB options

Default::default() is deliberately NOT rerouted through the installed configuration — only the new() constructors consult it. A test or a caller that asks for the literal defaults still gets them, whatever a manifest elsewhere says.

An earlier revision of this page claimed every field in the chain had been audited for "set but never read". That audit measured the arrows below the first one and not the first one itself, which was the arrow that did not exist.

[workspace]​

[workspace]
members = ["core", "api", "cli"]
exclude = ["vendor/*"]

[registry]​

[registry]
index = "https://registry.example.com"

Supply your registry's base URL without /api/v1; package commands append the API path. publish, search, and install read this setting from the active manifest. Set it explicitly because projectless and manifest-backed defaults differ. An invalid manifest is reported instead of selecting a fallback registry. See Cog packages for the publication workflow and its limits.

Language-feature sections​

Nine orthogonal sections each gate a subsystem. Every field defaults to a sensible value; include only the ones you want to change.

[types]​

[types]
dependent = true
refinement = true
cubical = true
higher_kinded = true
universe_polymorphism = false # opt-in; rare & costly
coinductive = true
quotient = true
instance_search = true
coherence_check_depth = 16

[runtime]​

[runtime]
cbgr_mode = "mixed" # managed | checked | unsafe | mixed
async_scheduler = "work_stealing" # single_threaded | multi_threaded | work_stealing
async_worker_threads = 0 # 0 = logical CPU count
futures = true
nurseries = true
task_stack_size = 0 # 0 = OS default
heap_policy = "adaptive" # aggressive | conservative | adaptive
panic = "unwind" # unwind | abort

[codegen]​

[codegen]
tier = "aot" # interpret | aot | check
mlir_gpu = false # MLIR path for @device(GPU) code
gpu_backend = "auto" # auto | metal | cuda | rocm | vulkan
monomorphization_cache = true
proof_erasure = true
debug_info = "line" # none | line | full
tail_call_optimization = true
vectorize = true
inline_depth = 3

[meta]​

[meta]
compile_time_functions = true
quote_syntax = true
macro_recursion_limit = 128
reflection = true
derive = true
max_stage_level = 2 # 0=runtime, 1=meta fn, 2+=multi-stage

[protocols]​

[protocols]
coherence = "strict" # strict | lenient | unchecked
resolution_strategy = "most_specific" # most_specific | first_declared | error
blanket_impls = true
higher_kinded_protocols = true
associated_types = true
generic_associated_types = true

[context]​

[context]
enabled = true
unresolved_policy = "error" # error | warn | allow
negative_constraints = true
propagation_depth = 32

[safety]​

[safety]
unsafe_allowed = true
ffi = true
ffi_boundary = "strict" # strict | lenient
capability_required = false
mls_level = "public" # public | secret | top_secret
forbid_stdlib_extern = false

[test]​

[test]
differential = false # VBC vs LLVM AOT must agree
property_testing = true
proptest_cases = 256
fuzzing = false
timeout_secs = 60
parallel = true
coverage = false
deny_warnings = false

[debug]​

Debug Adapter Protocol (DAP) configuration for IDE integration.

[debug]
dap_enabled = true
step_granularity = "statement" # statement | line | instruction
inspect_depth = 8
port = 0 # 0 = auto-pick; used when --transport socket
show_erased_proofs = false

verum dap refuses to start when dap_enabled = false.

[linker] — native linking​

Full linker control (parsed by verum_compiler):

[linker]
output = "executable" # executable | shared | static | object
lto = "thin" # none | thin | full
use_lld = true # default true on Linux, false elsewhere
pic = true
strip = false
strip_debug_only = false
debug_info = true
static_link = false
entry_point = "main"
target = "native"
library_paths = ["/usr/local/lib"]
libraries = ["ssl", "crypto"]
exports = ["verum_init"]
extra_flags = ["-Wl,--gc-sections"]

[linker.linux]
library_paths = ["/opt/local/lib"]
libraries = ["dl"]

[linker.macos]
extra_flags = ["-framework", "CoreFoundation"]

[linker.windows]
libraries = ["kernel32", "user32"]

Profile-scoped overrides:

[profile.release.linker]
lto = "full"
strip = true

[llvm], [optimization], [lto], [pgo], [cross_compile]​

LLVM backend fine-tuning. These sections let you control the native code generator beyond what [build] exposes.

[llvm]
target_triple = "x86_64-unknown-linux-gnu" # override `[build].target` for LLVM
target_cpu = "native" # "native" | "generic" | "znver3" | …
target_features = "+avx2,+fma" # comma-separated, `+`/`-` prefixed

[optimization]
level = 3 # 0–3, orthogonal to `[build].opt_level` when you need
size_opt = false # to diverge from the CLI's debug/release mapping
inline = true

[lto]
enabled = true
mode = "thin" # "thin" (fast, moderate wins) | "full" (slow, maximum wins)

[pgo]
enabled = true
profile_path = "target/pgo/default.profdata"

[cross_compile]
target = "aarch64-apple-darwin"
sysroot = "/opt/xcode-sdks/MacOSX.sdk"
linker = "clang"

[cross_compile] is parsed and validated, but nothing yet wires target / sysroot / linker into the toolchain invocation — setting it is observed (a build emits a debug-log line noting the values) and otherwise has no effect. The active cross-compile path is [llvm].target_triple or the --target CLI flag; sysroot and linker have no working manifest equivalent yet — pass them to your system linker the way you would for any other cross-compiled toolchain (see Installation → Cross-compiling Verum programs).

CLI equivalents (build-time):

TOML fieldCLI flag
[build].target, [llvm].target_triple--target TRIPLE
[optimization].level, [build].opt_level--release (= 3)
[lto].enabled + [lto].mode--lto thin|full
[build].incremental--timings triggers report when enabled
static linking--static-link
stripping--strip / --strip-debug
emit artefacts--emit-asm, --emit-llvm, --emit-bc, --emit-types, --emit-vbc

CLI flags always override the manifest for the current build; verum.toml values describe the project baseline. [cross_compile] is the exception — per the note above, it doesn't reach the build at all yet.

[lint] — linter policy​

The [lint] section drives verum lint. It supports per-rule severity, profiles, per-file overrides, named architecture layers, naming-convention enforcement, plus refinement / capability / context / CBGR-tier / verification policies that are unique to Verum.

[lint]
extends = "recommended" # minimal | recommended | strict | relaxed
disabled = []
denied = ["deprecated-syntax"]

[lint.severity]
unused-import = "warn"
deprecated-syntax = "error"

[lint.rules.large-copy]
size-threshold-bytes = 256
exempt-types = ["UserId", "Hash"]

[lint.per_file_overrides]
"tests/**" = { allow = ["unused-result", "todo-in-code"] }

[lint.architecture.layers]
core = { allow_imports = ["core", "std"] }
domain = { allow_imports = ["core", "std", "domain"] }

[lint.refinement_policy]
public_api_must_refine_int = true
require_verify_on_refined_fn = true

[lint.context_policy.modules]
"core.*" = { forbid = ["Database", "Logger", "Clock"] }

Full schema, every knob, every preset and the precedence stack: Reference → Lint configuration.

[profile] — per-build-kind compilation settings​

Four sub-tables, one per build kind: [profile.dev], [profile.release], [profile.test], [profile.bench]. Each carries the same ten keys.

[profile.release]
tier = "1" # "0" (interpreter) | "1" (AOT)
verification = "static" # none | runtime | static | fast
opt_level = 3 # 0..3
debug = false
debug_assertions = false
overflow_checks = false
lto = true
incremental = false
codegen_units = 1 # omit for the compiler's choice
cbgr_checks = "optimized" # all | optimized | proven | audit-points

tier is written as the string "0" or "1"; "interpreter", "interp", "aot", "release" and "native" are accepted aliases. verification is lowercase; cbgr_checks is kebab-case, matching the three-tier reference model — optimized lets escape analysis elide what it can prove, proven checks only what it could not, and audit-points restricts checks to boundary points.

Every key is optional and every sub-table is optional: an absent [profile.release] gets the compiler's defaults, which are what verum build --release uses today.

[lsp]​

[lsp]
enable_cost_hints = true # show CBGR tier / refinement costs inline
validation_mode = "incremental" # incremental | batch
auto_import = true
format_on_save = false

Verum.lock​

Generated automatically. Pins exact versions of all transitive dependencies. Commit for binary projects; optional for libraries.

CLI overrides (-Z)​

Any manifest value can be overridden at the command line without editing the file:

verum build -Z codegen.tier=interpret -Z safety.unsafe_allowed=false
verum run --no-cubical -Z runtime.cbgr_mode=unsafe
verum test -Z test.parallel=false -Z test.timeout_secs=120

Precedence (low → high):

  1. Built-in defaults
  2. verum.toml values
  3. High-level CLI flags (--tier, --no-cubical, --cbgr, --gpu)
  4. -Z KEY=VALUE overrides

Invalid keys produce a descriptive error listing all valid prefixes. Typos trigger "did you mean" suggestions via edit distance.

Inspecting & validating​

verum config show # human-readable resolved feature set
verum config show --json # machine-readable JSON
verum config validate # exit 0 on valid, non-zero with diagnostics

verum config show displays every flag's effective value after all overrides are applied, so you can verify that your -Z flags and verum.toml produce the expected configuration.

See also​