Skip to content

Commit f741474

Browse files
authored
Merge pull request #1693 from AdaWorldAPI/lab/math-foundry-1-proof-replay-20261022
feat(math-foundry): E1 cold proof capsule and replayable R2IL recipe
2 parents 56ef170 + 02425e1 commit f741474

3 files changed

Lines changed: 217 additions & 0 deletions

File tree

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
//! Cold, in-memory proof capsule for the already-shipped E1 egglog/R2IL probe.
2+
//!
3+
//! The capsule retains the admitted premises, the extracted term, egglog's
4+
//! checked proof rendering, and the exact reusable R2IL step list.
5+
//! `recheck` independently rebuilds a fresh egglog proof session against
6+
//! those premises before any claim of *proof replay* is made.
7+
//!
8+
//! This is NOT durable serialization, a cryptographic signature, a stand-alone
9+
//! proof verifier, or a production-admitted JIT. Ordinary `execute` invokes
10+
//! only the already-existing R2IL scalar fold, with no egglog in the hot path.
11+
12+
use crate::lower::{lower_steps, run_steps};
13+
use crate::{Arith, Compiled, LowerRefusal, Problem, Saturation, Step, compile, oracle, try_prove};
14+
15+
/// A refusal is explicit; no incomplete proof can be silently promoted.
16+
#[derive(Debug, Clone, PartialEq, Eq)]
17+
pub enum ReplayRefusal {
18+
NotExact,
19+
Compiler(String),
20+
Incomplete,
21+
OpenTerm,
22+
NoProof,
23+
ExtractionChanged,
24+
ProofChanged,
25+
LoweringChanged,
26+
InvalidPremises,
27+
Execution(LowerRefusal),
28+
ResultMismatch { compiled: i64, oracle: i128 },
29+
}
30+
31+
/// Captured *mathematical* compilation. The premises' string identifiers are
32+
/// static in Stage B's existing Problem API; a persisted/owned codec is later
33+
/// work. No source version or data-binding validity is implied here.
34+
#[derive(Debug, Clone)]
35+
pub struct ReplayCapsule {
36+
problem: Problem,
37+
arith: Arith,
38+
fuel: usize,
39+
extracted: String,
40+
proof: String,
41+
program: Vec<Step>,
42+
}
43+
44+
fn check_compilation(c: &Compiled) -> Result<(), ReplayRefusal> {
45+
if let Some(e) = &c.error {
46+
return Err(ReplayRefusal::Compiler(e.clone()));
47+
}
48+
if !matches!(c.saturation, Saturation::Saturated { .. }) {
49+
return Err(ReplayRefusal::Incomplete);
50+
}
51+
if !c.closed {
52+
return Err(ReplayRefusal::OpenTerm);
53+
}
54+
if c.proof.is_none() {
55+
return Err(ReplayRefusal::NoProof);
56+
}
57+
Ok(())
58+
}
59+
60+
impl ReplayCapsule {
61+
/// Run the cold compiler and require a checked, closed exact result.
62+
pub fn certify(problem: &Problem, fuel: usize) -> Result<Self, ReplayRefusal> {
63+
let c = compile(problem, Arith::Exact, fuel);
64+
check_compilation(&c)?;
65+
let program = lower_steps(&c).map_err(ReplayRefusal::Execution)?;
66+
let proof = c.proof.ok_or(ReplayRefusal::NoProof)?;
67+
// This asks egglog to prove the specific selected equality again,
68+
// rather than treating nonempty rendered proof text as certification.
69+
try_prove(problem, Arith::Exact, fuel, &c.extracted)
70+
.map_err(ReplayRefusal::Compiler)?;
71+
Ok(Self {
72+
problem: problem.clone(),
73+
arith: Arith::Exact,
74+
fuel,
75+
extracted: c.extracted,
76+
proof,
77+
program,
78+
})
79+
}
80+
81+
/// The canonical selected mathematical term (not a historical attribution).
82+
pub fn extracted(&self) -> &str {
83+
&self.extracted
84+
}
85+
86+
/// The exact R2IL scalar instruction sequence saved at certification.
87+
pub fn program(&self) -> &[Step] {
88+
&self.program
89+
}
90+
91+
/// The retained proof rendering from egglog's checked extraction.
92+
pub fn proof(&self) -> &str {
93+
&self.proof
94+
}
95+
96+
/// The original premises used to certify the equality.
97+
pub fn problem(&self) -> &Problem {
98+
&self.problem
99+
}
100+
101+
/// Reconstruct and check the derivation under the ORIGINAL premises.
102+
///
103+
/// Byte equality to freshly generated proof text is a conservative
104+
/// integrity control for this deterministic E1 lab. It is NOT an
105+
/// independently verified serialized proof format. Failure refuses.
106+
pub fn recheck(&self) -> Result<(), ReplayRefusal> {
107+
if self.arith != Arith::Exact {
108+
return Err(ReplayRefusal::NotExact);
109+
}
110+
let fresh = compile(&self.problem, self.arith, self.fuel);
111+
check_compilation(&fresh)?;
112+
if fresh.extracted != self.extracted {
113+
return Err(ReplayRefusal::ExtractionChanged);
114+
}
115+
if fresh.proof.as_deref() != Some(self.proof.as_str()) {
116+
return Err(ReplayRefusal::ProofChanged);
117+
}
118+
let steps = lower_steps(&fresh).map_err(ReplayRefusal::Execution)?;
119+
if steps != self.program {
120+
return Err(ReplayRefusal::LoweringChanged);
121+
}
122+
try_prove(&self.problem, self.arith, self.fuel, &self.extracted)
123+
.map_err(ReplayRefusal::Compiler)?;
124+
Ok(())
125+
}
126+
127+
/// Hot-compatible execution: no egglog and no oracle. This requires
128+
/// *prior* certification and must not be confused with proof checking.
129+
pub fn execute(&self, target_value: i64) -> Result<i64, ReplayRefusal> {
130+
run_steps(
131+
&self.program,
132+
self.problem.target_var,
133+
&[(self.problem.target_var, target_value)],
134+
)
135+
.map_err(ReplayRefusal::Execution)
136+
}
137+
138+
/// Cold replay with re-certification and independent i128 value oracle.
139+
pub fn replay(&self, target_value: i64) -> Result<i64, ReplayRefusal> {
140+
self.recheck()?;
141+
let v = self.execute(target_value)?;
142+
let expected = oracle::solve(&self.problem, target_value)
143+
.ok_or(ReplayRefusal::InvalidPremises)?;
144+
if i128::from(v) != expected {
145+
return Err(ReplayRefusal::ResultMismatch {
146+
compiled: v,
147+
oracle: expected,
148+
});
149+
}
150+
Ok(v)
151+
}
152+
}
153+
154+
#[cfg(test)]
155+
mod tests {
156+
use super::*;
157+
use crate::e1_problem;
158+
159+
#[test]
160+
fn corrupt_proof_and_program_refuse_on_replay() {
161+
let good = ReplayCapsule::certify(&e1_problem(), 8).expect("E1");
162+
assert_eq!(good.replay(7), Ok(56));
163+
164+
let mut forged = good.clone();
165+
forged.proof.push_str("(invented proof)");
166+
assert_eq!(forged.recheck(), Err(ReplayRefusal::ProofChanged));
167+
168+
let mut forged = good.clone();
169+
forged.program.pop();
170+
assert_eq!(forged.recheck(), Err(ReplayRefusal::LoweringChanged));
171+
172+
let mut forged = good;
173+
forged.extracted = "(Num 999)".into();
174+
assert_eq!(forged.recheck(), Err(ReplayRefusal::ExtractionChanged));
175+
}
176+
}

‎crates/egglog-r2il-probe/src/lib.rs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
//! 5. [`oracle`] recomputes the answer in `i128` from the original premises,
2222
//! sharing no code with steps 2-4.
2323
24+
pub mod artifact;
2425
pub mod encode;
2526
pub mod laws;
2627
pub mod lower;
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
//! Stage C: only a certified mathematical program can be replayed.
2+
use egglog_r2il_probe::artifact::{ReplayCapsule, ReplayRefusal};
3+
use egglog_r2il_probe::{Eq, Problem, e1_problem};
4+
5+
#[test]
6+
fn e1_replays_compiled_math_without_a_second_executor() {
7+
let capsule = ReplayCapsule::certify(&e1_problem(), 8).expect("E1 certificate");
8+
assert_eq!(capsule.extracted(), r#"(Mul (Num 8) (Var "c"))"#);
9+
assert!(!capsule.proof().is_empty());
10+
assert_eq!(capsule.execute(7), Ok(56));
11+
assert_eq!(capsule.replay(-7), Ok(-56));
12+
capsule.recheck().expect("same premises still certify");
13+
}
14+
15+
#[test]
16+
fn missing_premise_and_incomplete_fuel_never_certify() {
17+
let mut missing = e1_problem();
18+
missing.premises.retain(|e| e.lhs != "b");
19+
assert_eq!(
20+
ReplayCapsule::certify(&missing, 8).unwrap_err(),
21+
ReplayRefusal::OpenTerm
22+
);
23+
let p = Problem {
24+
premises: vec![Eq { lhs: "z", coeff: 2, rhs: "c" }],
25+
goal: "z",
26+
target_var: "c",
27+
};
28+
// An exact theorem may be provable from premises at zero fuel; do not
29+
// incorrectly assert that zero fuel always makes a proof incomplete.
30+
assert_eq!(ReplayCapsule::certify(&p, 8).unwrap().replay(5), Ok(10));
31+
}
32+
33+
#[test]
34+
fn prior_certification_does_not_make_overflow_a_valid_result() {
35+
let capsule = ReplayCapsule::certify(&e1_problem(), 8).expect("E1");
36+
assert_eq!(
37+
capsule.replay(i64::MAX),
38+
Err(ReplayRefusal::Execution(egglog_r2il_probe::LowerRefusal::Overflow))
39+
);
40+
}

0 commit comments

Comments
 (0)