1use super::{OracleArgs, OracleCommand, OracleSyncArgs, OracleValidateArgs};
4use crate::rlm::oracle::{OracleResult, OracleTraceStorage, TraceValidator};
5use crate::rlm::{RlmAnalysisResult, RlmChunker, RlmStats, SubQuery};
6use anyhow::{Context, Result};
7use serde_json::json;
8use std::io::Read;
9
10pub async fn execute(args: OracleArgs) -> Result<()> {
11 match args.command {
12 OracleCommand::Validate(v) => execute_validate(v).await,
13 OracleCommand::Sync(s) => execute_sync(s).await,
14 }
15}
16
17async fn execute_validate(args: OracleValidateArgs) -> Result<()> {
18 let source = load_source(&args)?;
19 let payload = load_payload(&args)?;
20 let source_path = args.file.as_ref().map(|p| p.to_string_lossy().to_string());
21
22 let synthetic = RlmAnalysisResult {
23 answer: payload.clone(),
24 iterations: 1,
25 sub_queries: vec![SubQuery {
26 query: args.query.clone(),
27 context_slice: source_path.clone(),
28 response: payload.clone(),
29 tokens_used: 0,
30 }],
31 stats: RlmStats {
32 input_tokens: RlmChunker::estimate_tokens(&source),
33 output_tokens: RlmChunker::estimate_tokens(&payload),
34 iterations: 1,
35 subcalls: 0,
36 elapsed_ms: 0,
37 compression_ratio: 1.0,
38 },
39 };
40
41 let validator = TraceValidator::new();
42 let oracle_result = validator.validate(
43 &synthetic,
44 &source,
45 source_path.as_deref(),
46 None,
47 Some(Vec::new()),
48 );
49
50 let persist = if args.persist {
51 let storage = OracleTraceStorage::from_env_or_vault().await;
52 Some(storage.persist_result(&oracle_result).await?)
53 } else {
54 None
55 };
56
57 if args.json {
58 let out = json!({
59 "oracle": oracle_json(&oracle_result),
60 "record": oracle_result.to_record(),
61 "persist": persist,
62 });
63 println!("{}", serde_json::to_string_pretty(&out)?);
64 } else {
65 println!("{}", oracle_status_line(&oracle_result));
66 if let Some(ref p) = persist {
67 if p.uploaded {
68 println!(
69 "[persist: uploaded ✓] {}",
70 p.remote_url.clone().unwrap_or_default()
71 );
72 } else {
73 println!("[persist: spooled —] {}", p.spooled_path);
74 }
75 if let Some(ref w) = p.warning {
76 println!("warning: {}", w);
77 }
78 }
79 }
80
81 Ok(())
82}
83
84async fn execute_sync(args: OracleSyncArgs) -> Result<()> {
85 let storage = OracleTraceStorage::from_env_or_vault().await;
86 let stats = storage.sync_pending().await?;
87
88 if args.json {
89 println!("{}", serde_json::to_string_pretty(&stats)?);
90 } else {
91 println!(
92 "synced={} retained={} failed={} pending_after={}",
93 stats.uploaded, stats.retained, stats.failed, stats.pending_after
94 );
95 }
96
97 Ok(())
98}
99
100fn load_source(args: &OracleValidateArgs) -> Result<String> {
101 match (&args.file, &args.content) {
102 (Some(path), None) => std::fs::read_to_string(path)
103 .with_context(|| format!("Failed to read source file {}", path.display())),
104 (None, Some(content)) if content == "-" => {
105 let mut stdin_content = String::new();
106 std::io::stdin().read_to_string(&mut stdin_content)?;
107 Ok(stdin_content)
108 }
109 (None, Some(content)) => Ok(content.clone()),
110 (Some(_), Some(_)) => {
111 anyhow::bail!("Provide either --file or --content, not both");
112 }
113 (None, None) => {
114 anyhow::bail!("Provide --file <path> or --content <text|->");
115 }
116 }
117}
118
119fn load_payload(args: &OracleValidateArgs) -> Result<String> {
120 match (&args.payload, &args.payload_file) {
121 (Some(payload), None) => Ok(payload.clone()),
122 (None, Some(path)) => std::fs::read_to_string(path)
123 .with_context(|| format!("Failed to read payload file {}", path.display())),
124 (Some(_), Some(_)) => {
125 anyhow::bail!("Provide either --payload or --payload-file, not both");
126 }
127 (None, None) => {
128 anyhow::bail!("Provide --payload <json> or --payload-file <path>");
129 }
130 }
131}
132
133fn oracle_json(result: &OracleResult) -> serde_json::Value {
134 match result {
135 OracleResult::Golden(trace) => json!({
136 "status": "golden",
137 "verdict": trace.verdict,
138 "verification_method": format!("{:?}", trace.verification_method),
139 "trace_id": trace.trace_id,
140 }),
141 OracleResult::Consensus {
142 trace,
143 agreement_ratio,
144 } => json!({
145 "status": "consensus",
146 "agreement_ratio": agreement_ratio,
147 "verdict": trace.verdict,
148 "verification_method": format!("{:?}", trace.verification_method),
149 "trace_id": trace.trace_id,
150 }),
151 OracleResult::Unverified { reason, trace } => json!({
152 "status": "unverified",
153 "reason": reason,
154 "verdict": trace.verdict,
155 "verification_method": format!("{:?}", trace.verification_method),
156 "trace_id": trace.trace_id,
157 }),
158 OracleResult::Failed {
159 reason,
160 diff,
161 trace,
162 } => json!({
163 "status": "failed",
164 "reason": reason,
165 "diff": diff,
166 "verdict": trace.verdict,
167 "verification_method": format!("{:?}", trace.verification_method),
168 "trace_id": trace.trace_id,
169 }),
170 }
171}
172
173fn oracle_status_line(result: &OracleResult) -> String {
174 match result {
175 OracleResult::Golden(_) => {
176 "[oracle: golden ✓] deterministic verification passed".to_string()
177 }
178 OracleResult::Consensus {
179 agreement_ratio, ..
180 } => format!(
181 "[oracle: consensus ✓] semantic agreement {:.1}%",
182 agreement_ratio * 100.0
183 ),
184 OracleResult::Unverified { reason, .. } => {
185 format!("[oracle: unverified —] {}", reason)
186 }
187 OracleResult::Failed { reason, .. } => format!("[oracle: failed ✗] {}", reason),
188 }
189}