open-standards/joule-code/joule-code-rs/crates/joule-code-remediate/examples/formal_fix.rs
Transaction Science 3645849e2d
Some checks failed
standards workspaces build + test / aac-rs (pull_request) Successful in 43s
joule-code conformance gate / Build + round-trip C1-C4 vectors (pull_request) Failing after 1m2s
standards workspaces build + test / joule-code-rs (pull_request) Successful in 57s
jouleclaw public surface must stay documented / Public-surface audit (jouleclaw-rs) (pull_request) Successful in 57s
no-vanity-metrics / Scan committed Markdown for vanity metrics (pull_request) Successful in 2s
standards workspaces build + test / joulecontract-rs (pull_request) Successful in 42s
standards workspaces build + test / map-rs (pull_request) Successful in 34s
standards workspaces build + test / mesh-rs (pull_request) Successful in 37s
standards workspaces build + test / eoc-rs (pull_request) Successful in 1m45s
standards workspaces build + test / jcp-rs (pull_request) Successful in 1m13s
standards workspaces build + test / openpay (pull_request) Successful in 1m36s
standards workspaces build + test / proof-rs (pull_request) Successful in 1m9s
standards workspaces build + test / sandbox-rs (pull_request) Successful in 2m1s
standards workspaces build + test / smart-byte-rs (pull_request) Successful in 2m33s
standards workspaces build + test / arl-rs (pull_request) Successful in 11m38s
standards workspaces build + test / joule-ui-rs (pull_request) Successful in 9m26s
standards workspaces build + test / jouledesk-rs (pull_request) Successful in 8m27s
standards workspaces build + test / joulehook-rs (pull_request) Successful in 8m29s
standards workspaces build + test / sense-rs (pull_request) Successful in 8m14s
proof: an absent solver yields Unproved, never a fabricated proof
`real_ladder()` was `[Propositional, Z3Smt, SmallModel, FrontierModel]`. The
first two are real; the last two are stand-ins that gate on `GoalKind` and
whose "artifact" is a hash of the goal restated. `FrontierModel` has no gate at
all — it proves any goal unconditionally.

So on a machine without `z3`, every goal came back Proved: the real rungs
declined, the router escalated, and T3 obliged. The receipt claimed
`closed_by: BigModel`, `provenance: Estimator`, `total_uj: 50_513_884` — the
energy was as invented as the proof. `run_z3`'s own doc says a missing solver
is read "as escalate, never as proof", but escalation *was* proof.

This is the second instance of one bug. The first — a refutation escalated into
a proof — was fixed by halting the router on `Verdict::Refuted`. The remaining
hole was known: `formal_fix.rs` hand-built `[Propositional, Z3Smt]` to dodge it,
noting "the stubs would still claim proved there". That workaround is now the
library's behaviour instead of one call site's.

  * `Solver::is_decision_procedure()` — does this rung's `Proved` mean a
    verifier would accept the artifact? Defaults to **false**, so a newly
    written stand-in is excluded from the real ladders automatically and a real
    solver must claim to be checkable. Real: Propositional, Z3Smt, Z3Server,
    LeanVerifier.
  * `real_ladder` / `resident_z3_ladder` hold decision procedures only.
    Exhausting them yields Unproved — the honest answer to "nothing here could
    decide this". There is no T2/T3 rung because no real one exists yet; a
    missing rung must never be papered over with a stand-in, which converts
    "we could not decide" into "we decided".
  * `real_t0_ladder` → `reference_ladder_with_real_t0`. A mixed ladder under a
    `real_` name is the confusion that produced this bug.
  * `ladder_is_sound` asserts the split structurally;
    `ladder_never_fabricates_a_proof_when_a_rung_is_absent` drives the real
    router, because the defect lived in the composition of router and ladder,
    not in either alone. It uses a goal `z3` cannot parse, so it asserts the
    same thing whether or not a solver is installed.

The conformance vector pack is byte-identical: it is generated from
`reference_ladder`, which is unchanged and stays total by design — that is what
makes routing vectors deterministic, and why it is a fixture and never evidence.

What this was costing: `joule-code-release`'s pipeline already printed
"z3 absent — postcondition honestly unproved, never faked" while the stand-in
faked it, and certified releases on the result. It now reports gate=red and
refuses. `formal_fix` no longer says "z3 found a counter-model" when z3 never ran.

394 tests pass across proof-rs, joule-code and joulecontract, with and without
`z3` on PATH. CI keeps installing z3 — not for correctness now, but so the real
T1 rung is exercised rather than skipped.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-29 05:06:17 -04:00

194 lines
9.1 KiB
Rust

//! The **formally-verified fix tier** — the verification rung past build+test.
//!
//! `joule-fix`'s `run_once` discharges six *operational* gates (build, tests,
//! lint-delta, frame, diff). They answer "did this change break anything we
//! tested?" — not "is this change *correct*?". For a fix whose correctness is a
//! formal property, there is a stronger gate, and every piece to run it already
//! ships in this monorepo:
//!
//! - `joulecontract-proof` — the vericoding bridge: a contract obligation that
//! carries a formalization becomes a content-addressed proof goal.
//! - `proof-solvers::real_ladder()` — a cheapest-sufficient cascade ending in
//! a **real `z3`** SMT backend (assert the negation; `unsat` ⇒ valid).
//! - the proof cascade meters every attempt in joules.
//!
//! This example wires them into a fix flow the harness does not yet run inline.
//! A settlement-netting function must satisfy one postcondition:
//!
//! when debits == credits, the net is zero.
//!
//! Two candidate fixes arrive. The proof — not a test suite — decides which one
//! is correct, and the buggy one is *refused before it can ship*:
//!
//! - candidate A (`net = debits + credits`): z3 finds a counter-model
//! (debits = credits = 1 ⇒ net = 2 ≠ 0) → **Unproved** → the formal gate
//! rejects the fix, with the wasted joules on record.
//! - candidate B (`net = debits - credits`): z3 finds the negation `unsat` →
//! the postcondition holds for *all* integers → **Proved** by the SMT rung,
//! measured joules sealed.
//!
//! That is the rigor nobody in the remediation market has reached: a fix gated
//! by a machine-checked proof of its own postcondition, priced in joules. It is
//! honest by construction — where `z3` is absent both candidates come back
//! Unproved (never a fabricated pass), and the unformalized obligations are
//! demoted, never dropped.
//!
//! Run with: `cargo run -p joule-code-remediate --example formal_fix`
use joulecontract_proof::{discharge, DischargeStatus, Formalization};
use joulecontract_schema::{Ceiling, Contract, ContentAddress, Rigor};
use proof_cascade::{ProofCache, Router};
use proof_schema::{GoalKind, SpecGrounding};
use proof_solvers::{real_ladder, z3_available};
// SOUNDNESS NOTE — why this example uses a decision-procedure ladder.
//
// This example originally exposed a soundness hole: against
// `proof_solvers::real_ladder()` = `[Propositional, Z3Smt, SmallModel,
// FrontierModel]`, candidate A (the bug) came back "PROVED by BigModel" at
// ~50 J. The cascade had no `Verdict::Refuted`, so `Z3Smt` collapsed z3's `sat`
// (a real counter-model) into `Unproved`; the router escalated it; and
// `FrontierModel` proves ANY goal unconditionally — so a disproof was
// fabricated into a proof.
//
// That was fixed in the proof crate: `Verdict::Refuted` exists, `Z3Smt` emits
// it on `sat`, and the router HALTS on a refutation instead of escalating
// (regression test in proof-cascade).
//
// A SECOND hole survived that fix, and this note used to describe the local
// workaround for it: escalation past a rung that *could not run at all*. With
// `z3` absent, `Z3Smt` returned `Unproved`, the router escalated, and
// `FrontierModel` closed the goal — so on any machine without `z3`, every goal
// came back proved at ~50 J of imaginary energy. This example dodged it by
// hand-building `[Propositional, Z3Smt]` instead of calling `real_ladder()`.
//
// That is now fixed at the source rather than at this call site:
// `real_ladder()` IS the decision-procedure ladder, the stand-ins live only in
// the `reference_*` fixtures, and `Solver::is_decision_procedure` makes the
// distinction machine-checkable (`ladder_is_sound` in proof-solvers). So the
// hand-rolled ladder is gone and the guarantee is unchanged: "a decision
// procedure decided this", nothing weaker.
/// The contract over the netting function: one precondition, one postcondition
/// (the property the fix must honor), one frame. Same spec for both candidates
/// — only the fix's computed `net` differs.
fn netting_contract() -> Contract {
Contract {
joulecontract: "joulecontract/0.6".into(),
subject: "settlement netting — net(debits, credits)".into(),
rigor_target: Rigor::Jr2,
pre: vec!["debits and credits are integers".into()],
post: vec!["when debits == credits, the net is zero".into()],
inv: vec![],
frame: vec!["the settlement ledger row".into()],
ceiling: Ceiling {
enpi_joules: 10.0,
unit: "joules/settlement".into(),
enb_ref: ContentAddress::of_bytes(b"enb-settlement-2026"),
derivation: None,
},
sector_equivalence: None,
amends: None,
}
}
/// An SMT-LIB2 goal: the candidate's computed `net`, the premise `debits ==
/// credits`, and the **negation** of the postcondition. `unsat` ⇒ no
/// counter-model exists ⇒ the postcondition is a theorem for that candidate.
fn postcondition_goal(net_definition: &str) -> String {
format!(
"(declare-const debits Int)\n\
(declare-const credits Int)\n\
(declare-const net Int)\n\
(assert (= net {net_definition}))\n\
(assert (= debits credits))\n\
(assert (not (= net 0)))"
)
}
/// Discharge the post-condition (obligation index 1: pre=0, post=1, frame=2)
/// for one candidate fix, and report the verdict + measured joules.
fn judge(label: &str, net_definition: &str) -> bool {
let contract = netting_contract();
let formalization = Formalization {
obligation: 1, // the postcondition
statement: postcondition_goal(net_definition),
kind: GoalKind::Decidable,
grounding: SpecGrounding::CrossChecked,
};
// A fresh cascade + cache per candidate so the joules reported are this
// candidate's own proof work, not a cache hit from the other. `real_ladder`
// is decision-procedures only (see the SOUNDNESS NOTE at the top) — a formal
// gate may not fall through to a model stand-in that fabricates a proof.
let router = Router::new(real_ladder());
let mut cache = ProofCache::new();
let d = discharge(&contract, &[formalization], &router, &mut cache)
.expect("discharge runs (bookkeeping is well-formed)");
let entry = &d.entries[1];
println!("── candidate {label}: net = {net_definition}");
match &entry.status {
DischargeStatus::Proved(result) => {
println!(
" PROVED by {:?} — postcondition holds for all integers ({} µJ measured)",
result.closed_by, result.total_uj
);
println!(" → formal gate ACCEPTS this fix.");
true
}
DischargeStatus::Unproved(result) => {
// "Unproved" has two very different causes and they must not read
// the same: z3 ran and refuted the claim, or z3 was never there to
// ask. Printing "found a counter-model" in the second case would
// report evidence that was never gathered — the exact failure this
// example exists to demonstrate.
let why = if z3_available() {
"z3 found a counter-model; the postcondition can fail"
} else {
"no decision procedure available to decide it (z3 absent)"
};
println!(" UNPROVED — {why} ({} µJ measured)", result.total_uj);
println!(" → formal gate REFUSES this fix (it would be reverted, never shipped).");
false
}
DischargeStatus::Unformalized => {
println!(" UNFORMALIZED — no proof goal supplied (recorded, never dropped).");
false
}
}
}
fn main() {
println!("formally-verified fix tier — settlement netting postcondition\n");
println!("postcondition under proof: debits == credits ⇒ net == 0\n");
if !z3_available() {
println!(
"note: `z3` is not on PATH — both candidates will come back UNPROVED.\n\
That is the honesty rule, not a failure: the harness never fabricates\n\
a proof it could not run. Install z3 to see the SMT rung discharge.\n"
);
}
let buggy_ok = judge("A (the bug)", "(+ debits credits)");
println!();
let fixed_ok = judge("B (the fix)", "(- debits credits)");
println!("\n── verdict");
if z3_available() {
assert!(!buggy_ok, "the buggy candidate must NOT pass the formal gate");
assert!(fixed_ok, "the correct fix must pass the formal gate");
println!(
"the proof discriminated: candidate A refused, candidate B verified.\n\
the shipped fix is gated by a machine-checked proof of its postcondition —\n\
a rung above build+test, priced in measured joules. that is the tier the\n\
remediation market has not reached."
);
} else {
println!(
"z3 absent: both candidates Unproved (honest). the gate refused the bug AND\n\
the fix — refusing a correct fix you could not prove beats shipping an\n\
unproven one. with z3 present, candidate B verifies and candidate A is refused."
);
}
}