Skip to content

verify: emit the full rule inventory (applied / verified / declined-with-reason) — today a consumer cannot compute coverage #935

Description

@avrabe

Companion to #933. That one is about a proof gap; this one is about reporting, and it is what makes the gap invisible.

What a consumer cannot do today

synth verify reports the rules it verified, but never the rules it applied and declined. So there is no way to compute a coverage denominator from its output.

Concretely, on a small dissolved object (a Health-Monitor component — pure scalar predicates, no imports):

Running translation validation for '...'
  ✓ i32.and → ... verified
  ✓ i32.sub → ... verified
  ...
Verification summary: 8 verified, 0 failed, 0 unknown

Reads as complete. It is not: the same object contains 29 i32.const, which BIN-VERIFY classes as a register operation and skips without reporting. A reader concludes "8 of 8"; the truth is "8 of 8 computational rules, plus an unreported const rule whose Rocq theorem (i32_const_correct) is Admitted — see #933."

On a larger object the reporting is better but still not a denominator: 15 of 23 functions print

No verifiable computational rules for this function.
(LocalGet/Set/Const are register operations, not verified by SMT)

— which names the category but not which rules, how many, or where.

Why this blocks a downstream obligation

We are trying to discharge a zero-coverage-gap object-code-verification requirement. Its denominator is deliberately the union of the two halves: a rule declined by BIN-VERIFY counts as covered only if its per-rule Rocq obligation is discharged. To evaluate that we need, per object:

  1. every codegen rule applied;
  2. for each: verified by SMT / declined (with reason) / not applicable;
  3. stable identifiers we can join against the Rocq theorem names.

Today (1) is unobtainable without disassembling the wasm ourselves and re-deriving synth's selection decisions — which would mean re-implementing part of the compiler to audit the compiler.

The precedent is in this tool already

--emit-wcet does exactly the right thing: a synth-wcet-v1 JSON sidecar with per-function status and machine-readable decline reasons (reason=loop, reason=call, reason=unmodeled-op). It made our WCET gap analysis mechanical.

Ask: the same for verify — e.g. --emit-verify-report writing a synth-verify-v1 sidecar:

{ "schema": "synth-verify-v1", "core_class": "cortex-m3",
  "functions": [
    { "name": "...", "rules": [
        { "rule": "i32.and",   "status": "verified" },
        { "rule": "i32.const", "status": "declined", "reason": "register-operation", "count": 29 }
    ]}]}

Even without the JSON, simply printing declined rules with counts would close the correctness hole in the current summary. The JSON is what makes it gateable in CI.

Impact

With this, our obligation becomes mechanically checkable and its one real gap (#933) becomes visible in the report rather than something a consumer has to already know to look for. Without it, any "N verified, 0 failed" line from this tool overstates coverage by an amount the reader cannot determine.

Reproducible on request — the objects are committed and we have a --features verify build wired up. Happy to test a candidate.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions