|
| 1 | +--- |
| 2 | +name: verus-verification |
| 3 | +description: >- |
| 4 | + Rigorous Verus specification and proof work for Regorus. Use when adding, |
| 5 | + strengthening, debugging, or reviewing Verus contracts, proofs, external-body |
| 6 | + boundaries, assume_specification declarations, BigInt or Number models, or |
| 7 | + minimal Verus bug reproducers. Preserves executable behavior while minimizing |
| 8 | + trusted assumptions and verifier workarounds. |
| 9 | +--- |
| 10 | + |
| 11 | +# Regorus Verus Verification |
| 12 | + |
| 13 | +Use this workflow for proof-oriented changes in Regorus, especially `Number`, |
| 14 | +`BigInt`, arithmetic, conversions, and policy-critical value semantics. |
| 15 | + |
| 16 | +The objective is not merely to make Verus pass. The objective is to establish an |
| 17 | +exact, useful contract for the real executable implementation with the smallest |
| 18 | +honest trusted boundary. |
| 19 | + |
| 20 | +## Core Rules |
| 21 | + |
| 22 | +1. **Specify executable semantics exactly.** |
| 23 | + - Model every meaningful result variant and error path. |
| 24 | + - Preserve distinctions such as integer versus float representation. |
| 25 | + - For floating-point operations, specify IEEE-754 behavior rather than ideal |
| 26 | + real arithmetic. |
| 27 | + - Do not weaken a contract just because the stronger proof is inconvenient. |
| 28 | + |
| 29 | +2. **Prove bodies whenever Verus supports them.** |
| 30 | + - Prefer a verified implementation over `assume_specification`. |
| 31 | + - Remove a trusted assumption once the implementation carries a proved spec. |
| 32 | + - Never describe an `external_body` function as body-proved. |
| 33 | + |
| 34 | +3. **Preserve executable behavior.** |
| 35 | + - Before editing, compare the function with `main` or the relevant base. |
| 36 | + - Keep executable statements unchanged unless the task explicitly requires a |
| 37 | + runtime fix. |
| 38 | + - Never use conditional compilation to give Verus and ordinary Rust different |
| 39 | + executable bodies or behavior. If Verus cannot verify the shared body, |
| 40 | + retain the narrowest `external_body` boundary and document the unsupported |
| 41 | + construct. |
| 42 | + - Put ghost reasoning in `proof!` blocks. Move proof work to the beginning of |
| 43 | + the function when it depends only on inputs. |
| 44 | + - Afterward, inspect the focused diff against the base and confirm that only |
| 45 | + contracts and erased proof code differ, unless a runtime change was intended. |
| 46 | + |
| 47 | +4. **Do not use preconditions to hide valid edge cases.** |
| 48 | + - Check minimum signed values, maximum values, zero, and representation |
| 49 | + boundaries explicitly. |
| 50 | + - Negating `i32::MIN` as `i32` overflows, but its magnitude $2^31$ fits in |
| 51 | + `u32`. Widen before negation, for example `(-(e as i64)) as u32`. |
| 52 | + - If the API can compute a valid result, prove and compute it instead of |
| 53 | + excluding the input or returning an invented error. |
| 54 | + |
| 55 | +5. **Reuse existing semantic models.** |
| 56 | + - Search `src/verify/` before adding an uninterpreted spec function. |
| 57 | + - Prefer established models such as `pow2`, `NumberView`, |
| 58 | + `to_f64_lossy_ensures`, and BigInt view/spec traits. |
| 59 | + - If the same mathematical value can have representation-dependent runtime |
| 60 | + behavior, quantify over the concrete modeled value rather than pretending |
| 61 | + the view alone determines the result. |
| 62 | + - When a view deliberately merges concrete variants, use a relational |
| 63 | + postcondition for representation-sensitive operations. For example, |
| 64 | + `NumberView::Integer` merges `Int`, `UInt`, and `BigInt`, whose lossy float |
| 65 | + conversions and boundary behavior need not be a function of the view alone. |
| 66 | + - Propagate that relation through callers with existential result witnesses. |
| 67 | + Do not recover hidden representation by existentially inventing a concrete |
| 68 | + `Number` whose view matches; that leaks internals and may choose a witness |
| 69 | + unrelated to the executable receiver. |
| 70 | + |
| 71 | +6. **Minimize and explain trust.** |
| 72 | + - Use `external_body` only at the smallest unsupported boundary. |
| 73 | + - Give an exact postcondition, not merely positivity or successful return, |
| 74 | + whenever downstream proofs depend on exact behavior. |
| 75 | + - Add a short comment naming the concrete verifier limitation, for example: |
| 76 | + overloaded `<<=`/`>>=` is unsupported, or overloaded `!` on external |
| 77 | + `BigInt` crashes this Verus version. |
| 78 | + - Avoid broad external wrappers around otherwise verifiable callers. |
| 79 | + |
| 80 | +## Workflow |
| 81 | + |
| 82 | +### 1. Establish the Runtime Baseline |
| 83 | + |
| 84 | +Start with the function, its helper contracts, its callers, and any existing |
| 85 | +trusted specification. |
| 86 | + |
| 87 | +```bash |
| 88 | +git show main:path/to/file.rs |
| 89 | +rg -n 'function_name|assume_specification|relevant_helper' src tests |
| 90 | +``` |
| 91 | + |
| 92 | +Record one falsifiable hypothesis: |
| 93 | +- what the exact behavior should be; |
| 94 | +- which helper contracts it depends on; |
| 95 | +- the cheapest verification or runtime check that could disprove it. |
| 96 | + |
| 97 | +Do not map the whole subsystem before making a small grounded edit. |
| 98 | + |
| 99 | +### 2. Write the Contract Before the Proof |
| 100 | + |
| 101 | +The contract should answer: |
| 102 | +- Which inputs return `Ok`, `Err`, `Some`, or `None`? |
| 103 | +- What exact mathematical value is represented? |
| 104 | +- Is the result an integer or float variant? |
| 105 | +- Which rounding, overflow, saturation, or lossy-conversion rule applies? |
| 106 | +- Are multiple concrete representations possible for the same view? |
| 107 | + |
| 108 | +For arithmetic returning `Result`, avoid vague contracts such as only |
| 109 | +`result is Ok` when the exact value is knowable. |
| 110 | + |
| 111 | +Write contracts around semantic inputs first, then state the result with |
| 112 | +`result matches ...`, `result is None`, or `result is Err`. This is usually |
| 113 | +clearer than matching every input/result tuple and separately excluding each |
| 114 | +impossible result variant. |
| 115 | + |
| 116 | +If an abstract view erases representation but the API result depends on it, |
| 117 | +allow the honest overlap in the postcondition and explain the boundary. For |
| 118 | +example, at `+/-2^53`, primitive and BigInt-backed `Number` values with the same |
| 119 | +view can legitimately differ between `Some` and `None` in an exact-float API. |
| 120 | + |
| 121 | +For BigInt operators, provide exact operator models and prove the caller against |
| 122 | +them. For division producing a float, model the exact lossy conversions used by |
| 123 | +the executable code. |
| 124 | + |
| 125 | +### 3. Remove Redundant Trust |
| 126 | + |
| 127 | +Search for existing assumptions: |
| 128 | + |
| 129 | +```bash |
| 130 | +rg -n 'assume_specification.*function_name|uninterp spec fn' src/verify src |
| 131 | +``` |
| 132 | + |
| 133 | +When moving a spec onto a body-verified function: |
| 134 | +- delete the old `assume_specification` in the same change; |
| 135 | +- ensure no duplicate specification remains; |
| 136 | +- strengthen helper contracts only as much as the body proof requires. |
| 137 | + |
| 138 | +An external helper may remain trusted when Verus cannot translate its syntax, |
| 139 | +but its contract must expose all facts needed by verified callers. |
| 140 | + |
| 141 | +### 4. Keep Proofs Separate From Execution |
| 142 | + |
| 143 | +Prefer this shape: |
| 144 | + |
| 145 | +```rust |
| 146 | +pub fn operation(input: i32) -> Result<Number> { |
| 147 | + proof! { |
| 148 | + // Input-only lemmas, cast equalities, and arithmetic facts. |
| 149 | + } |
| 150 | + |
| 151 | + // Original executable body. |
| 152 | +} |
| 153 | +``` |
| 154 | + |
| 155 | +Use local proof blocks later only when facts genuinely depend on an executable |
| 156 | +value produced at that point. |
| 157 | + |
| 158 | +Do not introduce executable temporaries solely to help a proof. If a temporary |
| 159 | +is ghost-only, keep it inside `proof!`. |
| 160 | + |
| 161 | +### 5. Handle Casts and Boundaries Explicitly |
| 162 | + |
| 163 | +Verus often needs explicit facts connecting machine integers and mathematical |
| 164 | +integers/naturals: |
| 165 | + |
| 166 | +```rust |
| 167 | +assert((e as u32) as nat == e as nat); |
| 168 | +``` |
| 169 | + |
| 170 | +For negative signed values, widen before negating: |
| 171 | + |
| 172 | +```rust |
| 173 | +let magnitude = (-(e as i64)) as u32; |
| 174 | +``` |
| 175 | + |
| 176 | +Then prove: |
| 177 | +- the magnitude is positive; |
| 178 | +- its cast equals the intended mathematical magnitude; |
| 179 | +- required power/division lemmas apply; |
| 180 | +- remainder is nonzero when the runtime should choose floating division. |
| 181 | + |
| 182 | +Check memory implications separately. A mathematically valid BigInt may be very |
| 183 | +large. Prove extreme paths, but do not execute resource-heavy regression tests |
| 184 | +unless the cost is acceptable and intentional. Test the conversion and a smaller |
| 185 | +representative behavior instead. |
| 186 | + |
| 187 | +For signed division and remainder, model Rust semantics with `rust_div` and |
| 188 | +`rust_rem`; mathematical `/` and `%` do not capture truncation toward zero for |
| 189 | +all negative inputs. Bridge primitive operator specs such as `RemSpec` to those |
| 190 | +models with focused lemmas. Handle `MIN / -1` before either `/` or `%`, because |
| 191 | +both machine operations overflow, and prove the exact quotient fits before |
| 192 | +connecting a mathematical result to `checked_div` or a narrowing cast. |
| 193 | + |
| 194 | +### 6. Use Verification Attributes Deliberately |
| 195 | + |
| 196 | +- `#[verus_verify]` on an `impl` applies to all methods in that impl. |
| 197 | +- Do not split adjacent inherent impls merely to change verification scope when |
| 198 | + one impl-level annotation plus narrow method overrides is clearer. |
| 199 | +- Use `#[verus_verify(external)]` only when an item must remain entirely outside |
| 200 | + verification and has a separate specification. |
| 201 | +- Use `#[verus_verify(external_body)]` when Verus should trust a stated contract |
| 202 | + but cannot verify the implementation body. |
| 203 | +- Method-level attributes can override the impl-wide default. |
| 204 | + |
| 205 | +Before diagnosing missing internal markers or macro bugs, inspect braces and |
| 206 | +attributes. Confirm the method is actually inside the annotated impl. |
| 207 | + |
| 208 | +### 7. Preserve Production Macros |
| 209 | + |
| 210 | +Do not replace `bail!`, `anyhow!`, or formatting in the executable body merely |
| 211 | +to make translation easier. Inspect the macro expansion and specify the |
| 212 | +smallest unsupported pieces. For `anyhow!`, this may mean narrow specifications |
| 213 | +for `Arguments::from_str`, `format_err`, and `must_use`; if verified callers |
| 214 | +only rely on taking the error branch, those assumptions need not promise |
| 215 | +anything about the error value. |
| 216 | + |
| 217 | +After a Verus upgrade, retry the original macro and previously externalized |
| 218 | +bodies. Translation support changes, so stale shims and `external_body` |
| 219 | +annotations should not become permanent trusted surface by inertia. |
| 220 | + |
| 221 | +## Diagnosing Verus Failures |
| 222 | + |
| 223 | +### Trigger Failure |
| 224 | + |
| 225 | +Before repairing or replacing a rejected trigger, check whether the quantifier |
| 226 | +is semantically necessary. If its bound variables merely name fields of fixed |
| 227 | +arguments through equalities such as `lhs == NumberView::Integer(integer_lhs)`, |
| 228 | +match on those arguments and state the branch-specific condition directly. This |
| 229 | +preserves the contract while removing the quantifier, its trigger, and needless |
| 230 | +solver instantiation. Use a natural or artificial trigger only when the contract |
| 231 | +genuinely ranges over multiple values that are not determined by fixed inputs. |
| 232 | + |
| 233 | +### Translation or Compiler Failure |
| 234 | + |
| 235 | +1. Reduce to the exact operator, type, attribute, and impl context. |
| 236 | +2. Test a one-file reproducer with the same relevant structure. |
| 237 | +3. Do not introduce macros, missing impl annotations, or different ownership |
| 238 | + patterns unless they exist in the failing code. |
| 239 | +4. If a small candidate passes, it is not a reproducer. Keep reducing the real |
| 240 | + context or state that the failure was caused by local annotation structure. |
| 241 | +5. Inspect `~/verus` only after the local code path is understood. |
| 242 | + |
| 243 | +A valid verifier bug report must: |
| 244 | +- fail on the stated Verus version; |
| 245 | +- contain no unrelated repository dependencies when avoidable; |
| 246 | +- reproduce the same failure mechanism; |
| 247 | +- document any workaround retained in Regorus. |
| 248 | + |
| 249 | +### Proof Failure |
| 250 | + |
| 251 | +Treat the first focused failure as evidence: |
| 252 | +- failed arithmetic safety means the implementation has an unhandled machine |
| 253 | + boundary or needs a justified precondition; |
| 254 | +- failed postcondition may indicate a missing helper fact, a representation |
| 255 | + mismatch, or an incorrect contract; |
| 256 | +- unsupported library internals should be isolated in the narrowest helper, not |
| 257 | + used to externalize the verified caller. |
| 258 | + |
| 259 | +Do not respond to a failed proof by immediately weakening the postcondition. |
| 260 | +First trace a concrete input through the runtime behavior. |
| 261 | + |
| 262 | +## Validation |
| 263 | + |
| 264 | +After the first substantive edit, immediately run the narrowest check: |
| 265 | + |
| 266 | +```bash |
| 267 | +cargo verus verify \ |
| 268 | + --fwd-verus-args-to roots -- --verify-module number |
| 269 | +``` |
| 270 | + |
| 271 | +Use a fresh target directory when checking for stale macro or compiler behavior. |
| 272 | + |
| 273 | +After the focused proof passes: |
| 274 | + |
| 275 | +```bash |
| 276 | +cargo test focused_test_name |
| 277 | +cargo fmt --all -- --check |
| 278 | +git diff --check |
| 279 | +``` |
| 280 | + |
| 281 | +For broader or final validation, use repository commands as appropriate: |
| 282 | + |
| 283 | +```bash |
| 284 | +cargo xtask fmt |
| 285 | +cargo xtask clippy |
| 286 | +cargo xtask ci-debug |
| 287 | +``` |
| 288 | + |
| 289 | +Report verification counts accurately. Distinguish: |
| 290 | +- body-verified functions; |
| 291 | +- external-body contracts; |
| 292 | +- trusted assumptions; |
| 293 | +- runtime tests actually executed; |
| 294 | +- extreme tests skipped due to resource cost. |
| 295 | + |
| 296 | +## Completion Checklist |
| 297 | + |
| 298 | +- [ ] Contract matches exact executable semantics. |
| 299 | +- [ ] Integer/float and `Undefined` distinctions remain intact where relevant. |
| 300 | +- [ ] Minimum/maximum signed values and casts were considered. |
| 301 | +- [ ] Original executable body is preserved unless a runtime bug was fixed. |
| 302 | +- [ ] Proof-only code is inside `proof!` and placed early when possible. |
| 303 | +- [ ] No redundant uninterpreted helper or trusted assumption remains. |
| 304 | +- [ ] Every `external_body` has the narrowest useful exact contract and a reason. |
| 305 | +- [ ] Impl-level verification annotations cover the intended methods without |
| 306 | + unnecessary splits. |
| 307 | +- [ ] Any claimed verifier reproducer is representative and independently fails. |
| 308 | +- [ ] Focused Verus verification passes. |
| 309 | +- [ ] Relevant runtime tests, formatting, and diff checks pass. |
0 commit comments