Skip to content

KaniStrategy has no label for symbolic-f32-array harnesses, the dominant DSP verification technique — 7 of rmedia's 8 real Kani proofs are unrepresentable #2530

Closed as not planned

Description

@noahgift

Summary

KaniStrategy offers four variants. None of them describes the technique that
7 of the 8 #[kani::proof] harnesses in paiml/rmedia actually use: a
fixed-length array of symbolic IEEE-754 f32, constrained to is_finite()
plus a magnitude bound, with transcendentals evaluated concretely (not stubbed)
.

Because strategy is a hard enum, a contract that names this technique fails to
parse at all — and every other check pv validate would apply to that
contract then passes vacuously. In rmedia this is not hypothetical:
contracts/audio-defect-v1.yaml declares 8 real, wired-up Kani harnesses and
pv validate reports it as "Kernel contract has no kani_harnesses", because
serde aborts on strategy before the harness list is ever seen. The reported
error is a downstream consequence of the vocabulary gap, not the actual defect.

The four existing variants and why each is a mislabel

crates/aprender-contracts/src/schema/types.rs:501-506:

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum KaniStrategy {
    Exhaustive,
    StubFloat,
    Compositional,
    BoundedInt,
}

The codegen bodies in crates/aprender-contracts/src/kani_gen/strategies.rs
are the authoritative semantics — each writes its own definition into the
generated harness:

variant codegen self-description element type emitted why it is wrong for these harnesses
exhaustive "exact verification / Integer/structural arithmetic verified without approximation" (strategies.rs:9) Vec<i32>, unbounded values not exact, not integer
stub_float "stub transcendentals (exp, log, sin, cos) … replaced with contract stubs" (strategies.rs:42-51) Vec<f32> finite these harnesses stub nothing — measure_true_peak runs a real 4-phase FIR and log10 under Kani
compositional "use #[kani::stub_verified] for sub-kernels" (strategies.rs:93) Vec<f32> finite no stub_verified anywhere
bounded_int "No floating-point — all inputs are bounded integers or indices" (strategies.rs:140-141) Vec<i64> bounded inputs are f32

stub_float is the closest, and it is still a false statement about the
harness: it asserts transcendental stubbing that does not happen. That
distinction is load-bearing — it is the difference between "this proof is
modulo a float axiomatization" and "this proof discharges the real IEEE-754
arithmetic", which is exactly what a reader consults strategy to learn.

Evidence: the actual harnesses

All 8 #[kani::proof] sites in rmedia live in
crates/rmedia-core/src/audio_defect/. Seven share one shape. Representative,
clipping.rs:203-218:

#[cfg(kani)]
#[kani::proof]
fn verify_clip_totality() {
    const N: usize = 8;
    let mut samples = [0.0f32; N];          // fixed-length array, not Vec
    for s in samples.iter_mut() {
        *s = kani::any();                    // symbolic f32
        kani::assume(s.is_finite());         // finite domain
        kani::assume(s.abs() <= 4.0);        // magnitude bound
    }
    let r = detect_clipping(&samples, 1, 1.0);
    kani::assert(r.clip_count <= samples.len(), "clip_count <= samples.len()");
    let sum: usize = r.clip_runs.iter().map(|(_, len)| len).sum();
    kani::assert(sum == r.clip_count, "runs sum to clip_count");
}

The full census:

harness site technique fits an existing variant?
verify_clip_totality clipping.rs:205 [f32;8] symbolic, finite, ≤4.0 no
verify_clip_symmetry clipping.rs:222 [f32;8] symbolic, finite, ≤4.0 no
verify_true_peak_arity true_peak.rs:414 symbolic u8 channels in 1..=4, concrete samples yes — bounded_int
verify_true_peak_lower_bound true_peak.rs:428 [f32;4] symbolic, finite, ≤1.0 no
verify_dropout_mask_gate dropout.rs:342 [f32;16] symbolic, finite, ≤1.0 no
verify_dropout_min_len dropout.rs:364 [f32;16] symbolic, finite, ≤1.0 no
verify_dc_ac_immunity dc_offset.rs:181 [f32;8] symbolic, finite, ≤1.0 no
verify_classify_fcap classify.rs:308 [f32;8] symbolic, finite, ≤4.0 no

One of eight is expressible. This is not an rmedia-local quirk — it is what
bounded model checking of any signal-processing kernel looks like: a short
symbolic window of real samples, constrained finite and in-range, fed to the
unmodified kernel.

Reproduction

$ pv --version
pv 0.49.0

$ cd rmedia && pv validate contracts/audio-defect-v1.yaml; echo "EXIT=$?"
error: Failed to parse YAML: kani_harnesses[0].strategy: unknown variant `bounded_array`,
  expected one of `exhaustive`, `stub_float`, `compositional`, `bounded_int`
  at line 193 column 15
EXIT=1

Then, after removing the unknown value so the file parses, the 8 harnesses
become visible and the contract validates — same file, same harnesses, the only
difference being whether the enum could name the strategy.

Requested change

Add one variant:

pub enum KaniStrategy {
    Exhaustive,
    StubFloat,
    Compositional,
    BoundedInt,
    /// Symbolic IEEE-754 floats over a fixed-length window, constrained to
    /// `is_finite()` and a magnitude bound. Transcendentals are NOT stubbed —
    /// the proof discharges real float arithmetic. Contrast `stub_float`,
    /// which axiomatizes exp/log/sin/cos.
    BoundedFloat,
}

with a generate_bounded_float_body mirroring generate_bounded_int_body
(strategies.rs:133) but emitting Vec<f32> with is_finite() and
|x| <= bound assumptions, and no stub attributes.

One variant, not two. rmedia's contract currently spells two invented names,
bounded_array (3 harnesses) and bounded_float (2 harnesses), but the code
census above shows both denote the same technique — the author was drawing a
shape/domain distinction the harnesses do not actually make. Array-shape
([T; N] vs symbolic-length Vec<T>) is an axis orthogonal to element domain
and is already carried by bound; it does not need its own strategy variant.
Please do not add bounded_array.

Interim state in rmedia

rmedia will not mislabel these harnesses as stub_float or bounded_int to
buy a parse. The five affected entries omit the optional strategy key —
strategy: Option<KaniStrategy> with #[serde(default)] (types.rs:485-486),
so None is a legal "unlabelled" state and generate_default_body
(strategies.rs:165) already emits a finite-f32 body — with a
# TODO(aprender#NNN) recording the intended value and a pointer to this issue.
They will be set to bounded_float when it lands.

Two further harnesses — verify_dropout_min_len and verify_classify_fcap —
already carry strategy: bounded_int, which parses but is equally untrue
(their inputs are symbolic f32). Those are tracked separately in rmedia
because they do not block the parse; they are counted here as evidence that the
gap silently produces wrong metadata as well as unparseable metadata. pv has
no way to catch that class today: the enum accepts bounded_int for an f32
harness because nothing cross-checks strategy against the harness body.

Activity

  1. added this to the 0.69.0 milestone on Sep 10, 2026
  2. modified the milestones: 0.69.0, 0.73.0, 0.70.0 on Sep 17, 2026
  3. noahgift commented on Sep 23, 2026

    @noahgift
    MemberAuthor

    triage 2026-09-23 (aprender-dd, standing triage owner): milestoned to 0.70.0, theme Fast Train (CI, gates, pv, release tooling, hygiene); it moved out of backlog (the 2026-09-17 overflow bucket, not a release).

  4. modified the milestones: 0.70.0, backlog, 0.71.0 on Sep 24, 2026
  5. added a commit that references this issue on Sep 25, 2026
    ae1887d
  6. noahgift commented on Sep 26, 2026

    @noahgift
    MemberAuthor

    CLAIM aprender-3d (cop aprender-77 assignment): adding the symbolic bounded-f32-array strategy variant + codegen, RED-first. Branch fix/2530-kani-bounded-array off main.

  7. noahgift commented on Sep 26, 2026

    @noahgift
    MemberAuthor

    Receipt: branch fix/2530-kani-bounded-array @ b7997e6. It adds KaniStrategy::BoundedFloat (bounded_float), a symbolic finite f32 window with a magnitude bound and no transcendental stubbed. It has its own codegen body and D3 weight 0.9, the same as bounded_int. bounded_array stays refused, and the parse error names bounded_float.

    • aprender-contracts --lib: 1725 passed, 0 failed (4 new tests). RED before the change: the missing variant fails to compile.
    • Mutants, 2/2 killed: routing the strategy to the stub_float body, and a weight of 0.8.
    • fmt, complexity ratchet, roadmap-aggregate, claim-literals and clippy -D warnings are all green.
    • Quorum 3/3 PASS (sonnet-5 ×2 + haiku-4-5), degraded: same-family.
      The PR cap is 10/10, so the branch is queued for fold.
  8. noahgift commented on Oct 14, 2026

    @noahgift
    MemberAuthor

    Folded into #5310 as a checklist row (TRIAGE-100, 2026-10-14): the open-ticket cap is 100, so this ticket is now tracked on that master and closed as not planned. Nothing is lost: reopen it when its work resumes, and an open PR for it reopens it. Pushed branch: fix/2530-kani-bounded-array.

    Agent: aprender-clerk

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions