Skip to content

feat(adr-001): sparse solve witness — per-entry residual audit - #41

Merged
ruvnet merged 2 commits into
mainfrom
adr/sparse-witness
May 19, 2026
Merged

ruvnet merged 2 commits into
mainfrom
adr/sparse-witness

Conversation

@ruvnet

@ruvnet ruvnet commented May 19, 2026

Copy link
Copy Markdown
Owner

Summary

Closes ADR-001 open question #3: "does `solve_on_change` need a witness?" YES — but a full `A·x` matvec costs `O(nnz(A))` and would dominate the SubLinear orchestrator's own cost. This PR's refinement: verify ONLY the closure entries.

Math

For each entry row `i` returned by the SubLinear orchestrator:

```
r_i = b[i] - Σ_j A[i,j] · x_new[j]
```

where `x_new[j]` uses the orchestrator's value if `j` is in the closure, and `prev_solution[j]` otherwise (the closure-boundary contract). Audit passes iff `max_i |r_i| ≤ tolerance · max(1, ‖b‖_∞)`. Cost is `O(|entries| · avg_row_nnz)` — same complexity class as the orchestrator.

API

```rust
pub fn verify_sparse_solution(
matrix: &dyn Matrix,
prev_solution: &[Precision],
b: &[Precision],
entries: &[(usize, Precision)],
tolerance: Precision,
) -> Result

pub struct WitnessReport {
pub max_residual: Precision,
pub threshold: Precision,
pub ok: bool,
pub worst_row: Option,
}

pub struct VerifySparseSolutionOp; // SubLinear class marker
```

Use cases

  • Audit mode in development — a witness failure on strict-DD input is a real solver bug, not just a tolerance miss.
  • Trust-but-verify in production — gate downstream actions on witness success at essentially the same cost as the solve.
  • Regression guards — property-test fuzz deltas + assert witness.

Test plan

  • 7 `witness::tests::*` covering Op class, pass on genuine output (strong-DD ring, depth=8 → ρ^8 ≈ 1e-8 trivially under 1e-3 audit), fail on deliberately corrupted entry, empty trivially-pass, DimensionMismatch on wrong-sized prev/b, OOB indices silently dropped
  • `cargo test --lib -- witness::` → 7/7 pass
  • Full CI

🤖 Generated with claude-flow

ruvnet and others added 2 commits May 19, 2026 11:44
Closes ADR-001 open question #3: "does solve_on_change need a
witness?" YES, but a full A·x matvec costs O(nnz(A)) and would
dominate the SubLinear orchestrator's own cost. This module's
twist: verify ONLY the closure entries.

Restricted residual:
  r_i = b[i] - Σ_j A[i,j] · x_new[j]  for each entry row i

where x_new[j] uses the orchestrator's value if j is in the closure,
and prev_solution[j] otherwise (the closure boundary contract).

Audit passes iff max_i |r_i| ≤ tolerance · max(1, ‖b‖_∞). Cost is
O(|entries| · avg_row_nnz) — same complexity class as the
orchestrator, independent of n.

Lands:

  src/witness.rs (new, 304 LOC)
    pub fn verify_sparse_solution(matrix, prev, b, entries, tolerance)
        -> Result<WitnessReport>

    pub struct WitnessReport {
        max_residual: Precision,
        threshold:    Precision,
        ok:           bool,
        worst_row:    Option<usize>,
    }

    pub struct VerifySparseSolutionOp; // SubLinear class marker

  src/lib.rs
    Re-exports verify_sparse_solution + WitnessReport +
    VerifySparseSolutionOp.

Use cases:
  - Audit mode in development: catch real solver bugs (witness
    failure on strict-DD input = bug, not just tolerance miss).
  - Trust-but-verify in production: gate downstream agent actions
    on witness success at essentially the same cost as the solve.
  - Regression guards: property-test fuzz deltas + assert witness.

Tests: 7 covering:
  - Op marker SubLinear class
  - Pass on genuine SubLinear output (strong-DD ring, depth=8)
  - Fail on a deliberately corrupted entry
  - Empty entries trivially pass
  - DimensionMismatch on wrong-sized prev / b
  - OOB indices silently dropped (no panic)

Co-Authored-By: claude-flow <ruv@ruv.net>
The witness PR added an Op marker (VerifySparseSolutionOp =
SubLinear) without updating .github/complexity-baseline.txt — the
regression-guard CI job correctly tripped. Regenerated via
scripts/extract_complexity_classes.sh.

Also picks up PlanBudget-adjacent declarations that landed in #40 if
the script discovers them (none in this iteration — PlanBudget's
class lives on individual ComplexityClass instances passed by callers,
not on the budget struct itself).

Co-Authored-By: claude-flow <ruv@ruv.net>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant