07Docs
Verification
bounded verify compiles your policy into proof obligations and discharges them with an SMT solver (Z3). This page lists every obligation the verifier emits, what a counterexample looks like, and exactly which runtime enforces what, and how the proof report relates to deploy.
Verify reports; deploy gates
verify and deploy are separate commands with different jobs:
bounded verifyruns the Z3 prover and prints the full report: every obligation, every counterexample, and the non-blocking advisories. It is your fast feedback loop while you author the policy.bounded deployruns static validation and compilation (a malformed policy, an impossible tier/invariant pairing, or an uncompilable rule is rejected), and the server then re-runs the prover’s deploy gate: a policy whose blocking proof obligations fail is rejected, fail-closed. The previous good policy stays live.
So the workflow is: iterate with verify until every blocking obligation passes and you have reviewed the advisories, then deploy. A DISPROVED blocking obligation is a wall at deploy time. Intentional denies, runtime-safety notes, and unbound attestations are non-blocking advisories left to your judgment.
Proofs, not tests
A test checks the inputs you thought of. Each obligation below is a statement checked over all modeled inputs: every document state, every payload, every caller. Read each item’s proofStatus; a successful command or a passed: true advisory is not itself a proof:
- PROVED, holds universally; a report entry records the obligation, expression, solver result, and timing.
bounded verifydoes not write local proof certificate files; deploy re-runs the same gate server-side. - DISPROVED, a concrete counterexample exists, and you get it: the exact variable assignments that break the property. A blocking obligation with this verdict is rejected at deploy.
- UNKNOWN, no proof was established. This is the explicit status for valid runtime-only advisories such as
flowBound,windowSum, and aboundover.values. Those rows can be non-blocking and runtime-enforced or maintained, but they are neverPROVED. - UNSUPPORTED / TIMEOUT, the verifier could not encode or finish the obligation. Neither counts as
PROVED. Invalid declarations and over-claimed coverage can be rejected by validation; an inconclusive blocking obligation does not pass the deploy gate. A bare-string attestation remains a non-blocking advisory.
checkTautology(amount <= 100 || amount > 100) DISPROVED (89ms)
counterexample: @newData.amount = null
checkAuthRequired(create) DISPROVED (52ms)
counterexample: @user.id = null,
@newData.ownerId = null
null == null satisfies "ownerId == @user.id"
suggestion: add "@user.id != null" before the ownership checkThe null counterexample above is the canonical example of why this matters: “x ≤ 100 or x > 100” reads like a tautology until a field is missing. No test suite writes that case; the solver finds it in 89ms.
The obligations table
Rule-property obligations, generated for every collection from its rules:
| Obligation | What it proves |
|---|---|
rule is satisfiable | The rule can be true at all; accidental contradictions that silently deny everything are surfaced per action. A literal false rule is the supported intentional-deny idiom and is advisory. |
requires authentication | No assignment with @user.id = null passes a write rule, including the classic null == null ownership bypass. |
field immutability | Fields marked ! can never be rewritten by any payload that satisfies the update rule. |
implication / equivalence | Relations between rules, e.g. everything the update rule admits, the create rule also admits. Used by dead-rule and auth-consistency analysis. |
tautology / contradiction | A rule that is always true (no protection) or accidentally contradictory is reported with a witness. An intentional literal false rule is a non-blocking advisory. |
read rule uses no getAfter() | Read rules see committed state only; post-batch reads in a read rule are rejected. |
ownership field exists | A rule referencing an ownership field that is not in the schema fails the gate instead of failing at runtime. |
runtime safety (advisory) | Division/exponent expressions that can trap at runtime (divide by zero) are flagged with a suggested guard. Advisory, does not block deploy. |
Invariant proof rows and advisories, generated for declared invariants. Formal transaction postcondition obligations must be PROVED; runtime-only advisory rows stay UNKNOWN and are excluded from the combined formal claim:
| Obligation | What it proves |
|---|---|
conservation algebra | Two SMT proofs per conserve: the runtime postcondition is equivalent to “affected after-sum equals affected before-sum” (delta equivalence), plus an induction step showing the property folds correctly over arbitrary multi-document write sets. |
append-only rolling limit algebra | For each rollingSum: if the runtime admits only nonnegative appended records and the projected window sum is within limit, the resulting window sum is within limit. With scopeVariable, the same obligation holds independently per partition. |
tenant tag binding | If the generated runtime postcondition accepts a write, the tag field equals the declared path variable, modeled with an explicit runtimeAccepts variable, not a degenerate tautology. |
tenant edge preservation | An accepted reference write implies source and target tenant tags match. |
scalar field-bound postcondition | For an ordinary scalar offchain bound, every accepted write satisfies field op limit. This row is PROVED. |
runtime advisory | flowBound is runtime-enforced on its supported ordinary offchain document surfaces; windowSum runs best-effort derived maintenance on documented paths but can diverge across source/target/expiry failures and lifecycle changes; and a .values map bound is runtime-enforced offchain. Their rows are proofStatus: UNKNOWN, never proof claims. |
relationship edge coverage | (Opt-in) every declared relationship edge in the policy is covered by source tag + target tag + matching tenantEdge invariants, or the deploy fails. |
isolation depth / induction | (Opt-in) bounded-depth isolation proves the declared relationship graph is acyclic within k hops; the induction variant proves tenant equality composes over any finite declared path, cycles included. |
combined formal claim | When multiple invariants are declared, one policy-level conjunctive check (__policy__/formalClaims) composes the generated formal obligations. Runtime-only UNKNOWN advisories are deliberately excluded; an approved deploy does not upgrade them to PROVED. |
Function-auth obligations, generated per declared Bounded Function from its auth rule. Without actAs, function <name>: caller-scoped invocation records that writes run as the verified caller; it is not an admin-only proof. A function with actAs instead emits function <name>: actAs service identity is admin-gated: its auth rule must imply the admin predicate. An over-permissive privileged hatch (auth: "true" or "@user.id != null") is DISPROVED with a non-admin counterexample and blocks deploy.
The two-layer coverage model
Two distinct layers carry formal claims, with different runtime coverage. Knowing which layer a guarantee lives in tells you exactly where it is enforced.
Layer A, rule properties: one bytecode, one semantics
Auth-required, immutability, dead-rule, implication and counterexample analysis are proven once against your policy expressions, and enforced identically everywhere, because the same compiled rule bytecode executes in every enforcing runtime. Nothing is ported, so nothing can drift.
Layer B, invariants: proof status and runtime reach are separate
The six boundary types and windowSum do not all have the same proof or runtime coverage. PROVED, runtime-enforced, runtime-maintained, and onchain-supported are four distinct claims:
| Declaration | proofStatus | Offchain | Onchain |
|---|---|---|---|
conserve (direct) | PROVED | Enforced. | Enforced by the currently registered v1 programs. |
conserve (materialized/sharded) | PROVED | Enforced. | Source runtime v2+ only; the deploy capability gate rejects it for currently registered v1 programs. |
rollingSum | PROVED | Exact-window cap. | Source runtime v2+ implements conservative epoch buckets; currently registered v1 programs reject this metadata. |
tenantTag | PROVED | Enforced. | Enforced by the currently registered v1 programs. |
tenantEdge | PROVED | Enforced. | Full-path support is source runtime v2+ and unavailable on currently registered v1 programs; bare-id targetPathVariable is offchain-only. |
Scalar offchain bound | PROVED | Enforced on authoritative durable writes and live checkpoints. | Not enforced. |
bound .values | UNKNOWN | Runtime-enforced over all map values. | Not enforced. |
flowBound | UNKNOWN | Runtime-enforced on ordinary durable, non-session document collections. | Offchain-only. |
windowSum | UNKNOWN | Best-effort derived maintenance on documented paths; source/target/expiry atomicity and lifecycle completeness are not guaranteed. Not a cap or exactness proof. | Offchain-only. |
An UNKNOWN row may be valid and non-blocking, but the UI, CLI, SDK, or application must not relabel it PROVED. An onchain bound is not enforced at all. The source program implements more invariant forms than the currently registered v1 deployments; the deploy capability gate rejects newer metadata until the target program is upgraded and registered. See the declaration details on Invariants.
The honest trust boundary
- Proofs cover declared constraints. An invariant you didn’t declare isn’t proven,
bounded verifyproposes candidates from your schema, you arbitrate intent. - The proofs are about modeled policy obligations, not application code being bug-free. A frontend can still be wrong, and runtime-only constraints protect only their documented enforcement surfaces.
- Liveness is not claimed: a proof that invalid writes are rejected is not a proof that every valid write shape is accepted.
- Rolling caps account exactly the events written to the declared append-only scope, spending that bypasses the collection is outside the statement.