2828//! (e.g. `http://localhost:8080`). If the variable is absent or empty,
2929//! the writer is disabled and every call to `record` is a no-op.
3030//!
31+ //! If verisim-api sets `VERISIM_PROOF_ATTEMPTS_TOKEN`, set the same value in
32+ //! the echidnabot process environment so writes include
33+ //! `X-Proof-Attempts-Token`.
34+ //!
3135//! # Prover-agnosticism
3236//!
3337//! `prover_to_str` maps every `ProverKind` variant to the lowercase
@@ -104,6 +108,7 @@ pub struct VeriSimWriter {
104108 base_url : String ,
105109 http : Client ,
106110 enabled : bool ,
111+ write_token : Option < String > ,
107112}
108113
109114impl VeriSimWriter {
@@ -122,6 +127,10 @@ impl VeriSimWriter {
122127 base_url : url. trim_end_matches ( '/' ) . to_string ( ) ,
123128 http,
124129 enabled : true ,
130+ write_token : std:: env:: var ( "VERISIM_PROOF_ATTEMPTS_TOKEN" )
131+ . ok ( )
132+ . map ( |s| s. trim ( ) . to_string ( ) )
133+ . filter ( |s| !s. is_empty ( ) ) ,
125134 }
126135 }
127136 _ => {
@@ -132,6 +141,7 @@ impl VeriSimWriter {
132141 base_url : String :: new ( ) ,
133142 http : Client :: new ( ) ,
134143 enabled : false ,
144+ write_token : None ,
135145 }
136146 }
137147 }
@@ -163,9 +173,10 @@ impl VeriSimWriter {
163173
164174 let completed_at = Utc :: now ( ) ;
165175 let obligation_class = classify_obligation_from_path ( file_path, prover) ;
166- let outcome = outcome_str ( & result. status ) ;
176+ let outcome = outcome_str ( result) ;
167177 let confidence = confidence_from_status ( & result. status ) ;
168178 let prover_str = prover_to_str ( prover) ;
179+ let verified = result. status . is_verified ( ) ;
169180
170181 let row = ProofAttemptRow {
171182 attempt_id : Uuid :: new_v4 ( ) . to_string ( ) ,
@@ -183,16 +194,16 @@ impl VeriSimWriter {
183194 started_at : started_at. format ( "%Y-%m-%dT%H:%M:%S%.3f" ) . to_string ( ) ,
184195 completed_at : completed_at. format ( "%Y-%m-%dT%H:%M:%S%.3f" ) . to_string ( ) ,
185196 prover_output : truncate_utf8 ( & result. prover_output , 8 * 1024 ) ,
186- error_message : if result. status == ProofStatus :: Verified {
187- None
188- } else {
189- Some ( result. message . clone ( ) )
190- } ,
197+ error_message : if verified { None } else { Some ( build_error_message ( result) ) } ,
191198 } ;
192199
193200 let url = format ! ( "{}/api/v1/proof_attempts" , self . base_url) ;
201+ let mut request = self . http . post ( & url) . json ( & row) ;
202+ if let Some ( token) = & self . write_token {
203+ request = request. header ( "X-Proof-Attempts-Token" , token) ;
204+ }
194205
195- match self . http . post ( & url ) . json ( & row ) . send ( ) . await {
206+ match request . send ( ) . await {
196207 Ok ( resp) if resp. status ( ) . is_success ( ) => {
197208 debug ! (
198209 attempt_id = %row. attempt_id,
@@ -228,11 +239,21 @@ impl VeriSimWriter {
228239// ─── Helpers ──────────────────────────────────────────────────────────────────
229240
230241/// Map `ProofStatus` to the lowercase ClickHouse Enum8 outcome string.
231- fn outcome_str ( status : & ProofStatus ) -> String {
232- match status {
233- ProofStatus :: Verified => "success" ,
242+ fn outcome_str ( result : & ProofResult ) -> String {
243+ if result. status . is_verified ( ) {
244+ return "success" . to_string ( ) ;
245+ }
246+
247+ match result. status {
248+ ProofStatus :: Proved | ProofStatus :: Verified => "success" ,
249+ ProofStatus :: NoProofFound | ProofStatus :: Failed => "failure" ,
250+ ProofStatus :: InvalidInput
251+ | ProofStatus :: UnsupportedFeature
252+ | ProofStatus :: InconsistentPremises
253+ | ProofStatus :: ProverError => "failure" ,
234254 ProofStatus :: Timeout => "timeout" ,
235- ProofStatus :: Failed | ProofStatus :: Error => "failure" ,
255+ ProofStatus :: SystemError if is_prover_unavailable ( result) => "unknown" ,
256+ ProofStatus :: SystemError => "failure" ,
236257 ProofStatus :: Unknown => "unknown" ,
237258 }
238259 . to_string ( )
@@ -272,14 +293,39 @@ fn classify_obligation_from_path(file_path: &str, prover: ProverKind) -> String
272293/// is per-attempt metadata, not a prediction.
273294fn confidence_from_status ( status : & ProofStatus ) -> f32 {
274295 match status {
275- ProofStatus :: Verified => 0.95 ,
276- ProofStatus :: Failed => 0.10 ,
296+ ProofStatus :: Proved | ProofStatus :: Verified => 0.95 ,
297+ ProofStatus :: NoProofFound | ProofStatus :: Failed => 0.10 ,
298+ ProofStatus :: InvalidInput | ProofStatus :: UnsupportedFeature => 0.20 ,
299+ ProofStatus :: InconsistentPremises => 0.05 ,
277300 ProofStatus :: Timeout => 0.05 ,
278- ProofStatus :: Error => 0.10 ,
301+ ProofStatus :: ProverError | ProofStatus :: SystemError => 0.05 ,
279302 ProofStatus :: Unknown => 0.50 ,
280303 }
281304}
282305
306+ fn build_error_message ( result : & ProofResult ) -> String {
307+ if is_prover_unavailable ( result) {
308+ format ! ( "prover_unavailable: {}" , result. message)
309+ } else {
310+ result. message . clone ( )
311+ }
312+ }
313+
314+ fn is_prover_unavailable ( result : & ProofResult ) -> bool {
315+ matches ! ( result. status, ProofStatus :: SystemError )
316+ && {
317+ let merged = format ! (
318+ "{}\n {}" ,
319+ result. message. to_lowercase( ) ,
320+ result. prover_output. to_lowercase( )
321+ ) ;
322+ merged. contains ( "not installed" )
323+ || merged. contains ( "not available" )
324+ || merged. contains ( "unavailable" )
325+ || merged. contains ( "missing prover" )
326+ }
327+ }
328+
283329/// Stable 16-char hex obligation ID derived from `(repo, file)`.
284330///
285331/// Uses a non-cryptographic hash — good enough for grouping retries.
0 commit comments