u280_source_evidence_oracle/
u280-source-evidence-oracle.rs1use anyhow::{Context, Result, bail, ensure};
4use std::env;
5use std::ffi::OsString;
6use std::path::PathBuf;
7use std::process::ExitCode;
8use u280_evidence_oracle::{
9 SourceOracleConfig, SourceOracleFailure, SourceOracleOutcome, execute_source,
10};
11
12const USAGE: &str = "Usage: u280-source-evidence-oracle --source-root DIR --result-dir NEW_DIR";
13
14fn main() -> ExitCode {
15 match parse_options().and_then(|config| execute_source(&config)) {
16 Ok(outcome) => {
17 match receipt_line(&outcome) {
18 Ok(line) => println!("{line}"),
19 Err(error) => {
20 eprintln!(
21 "u280-source-evidence-oracle: cannot serialize result receipt: {error:#}"
22 );
23 return ExitCode::FAILURE;
24 }
25 }
26 if outcome.cryptographic_oracle_valid {
27 ExitCode::SUCCESS
28 } else {
29 eprintln!(
30 "u280-source-evidence-oracle: completed comparison found a mismatch; see {}",
31 outcome.result_directory.join("summary.json").display()
32 );
33 ExitCode::FAILURE
34 }
35 }
36 Err(error) => {
37 if let Some(failure) = error.downcast_ref::<SourceOracleFailure>() {
38 match failure_receipt_line(failure) {
39 Ok(line) => println!("{line}"),
40 Err(receipt_error) => eprintln!(
41 "u280-source-evidence-oracle: cannot serialize failure receipt: {receipt_error:#}"
42 ),
43 }
44 }
45 eprintln!("u280-source-evidence-oracle: {error:#}");
46 ExitCode::FAILURE
47 }
48 }
49}
50
51fn receipt_line(outcome: &SourceOracleOutcome) -> Result<String> {
52 let result_directory = outcome
53 .result_directory
54 .to_str()
55 .context("result directory is not valid UTF-8")?;
56 serde_json::to_string(&serde_json::json!({
57 "schema": 2,
58 "kind": "hashsigs_u280_source_simulation_rust_oracle_receipt",
59 "result_kind": "completed_comparison",
60 "evidence_tier": "rhdl_source_simulation_plus_independent_rust_oracle",
61 "result_directory": result_directory,
62 "manifest": {
63 "file": "SHA256SUMS",
64 "bytes": outcome.result_manifest_bytes,
65 "sha256": outcome.result_manifest_sha256.as_str(),
66 },
67 "source_simulation_structurally_valid": true,
68 "cryptographic_oracle_valid": outcome.cryptographic_oracle_valid,
69 "source_revision_bound": false,
70 "source_worktree_state_bound": false,
71 "producing_command_bound": false,
72 "tool_versions_bound": false,
73 "promotion_blocked": true,
74 "hardware_execution": false,
75 "card": false,
76 "performance": false,
77 "hardware_completion_promotable": false,
78 "case": outcome.case.as_str(),
79 "batch": outcome.batch,
80 "compared_jobs": outcome.compared_jobs,
81 "all_unwritten_slots_valid": outcome.all_unwritten_slots_valid,
82 }))
83 .context("cannot serialize strict source-oracle receipt")
84}
85
86fn failure_receipt_line(failure: &SourceOracleFailure) -> Result<String> {
87 let result_directory = failure
88 .result_directory
89 .to_str()
90 .context("result directory is not valid UTF-8")?;
91 serde_json::to_string(&serde_json::json!({
92 "schema": 2,
93 "kind": "hashsigs_u280_source_simulation_rust_oracle_receipt",
94 "result_kind": "structural_failure",
95 "evidence_tier": "rhdl_source_simulation_plus_independent_rust_oracle",
96 "result_directory": result_directory,
97 "manifest": {
98 "file": "SHA256SUMS",
99 "bytes": failure.result_manifest_bytes,
100 "sha256": failure.result_manifest_sha256.as_str(),
101 },
102 "source_simulation_structurally_valid": false,
103 "cryptographic_oracle_valid": serde_json::Value::Null,
104 "source_revision_bound": false,
105 "source_worktree_state_bound": false,
106 "producing_command_bound": false,
107 "tool_versions_bound": false,
108 "promotion_blocked": true,
109 "hardware_execution": false,
110 "card": false,
111 "performance": false,
112 "hardware_completion_promotable": false,
113 "case": serde_json::Value::Null,
114 "batch": serde_json::Value::Null,
115 "compared_jobs": serde_json::Value::Null,
116 "all_unwritten_slots_valid": serde_json::Value::Null,
117 }))
118 .context("cannot serialize strict source-oracle failure receipt")
119}
120
121fn parse_options() -> Result<SourceOracleConfig> {
122 let mut arguments = env::args_os();
123 let _program = arguments.next();
124 let mut source_root = None;
125 let mut result_directory = None;
126 while let Some(argument) = arguments.next() {
127 match argument.to_str() {
128 Some("--source-root") => {
129 ensure!(source_root.is_none(), "--source-root was repeated");
130 source_root = Some(PathBuf::from(required_value(
131 &mut arguments,
132 "--source-root",
133 )?));
134 }
135 Some("--result-dir") => {
136 ensure!(result_directory.is_none(), "--result-dir was repeated");
137 result_directory = Some(PathBuf::from(required_value(
138 &mut arguments,
139 "--result-dir",
140 )?));
141 }
142 Some("--help" | "-h") => bail!(USAGE),
143 Some(value) => bail!("unknown option {value}; {USAGE}"),
144 None => bail!("option name is not valid UTF-8; {USAGE}"),
145 }
146 }
147 Ok(SourceOracleConfig {
148 source_root: source_root.with_context(|| USAGE.to_owned())?,
149 result_directory: result_directory.with_context(|| USAGE.to_owned())?,
150 })
151}
152
153fn required_value(
154 arguments: &mut impl Iterator<Item = OsString>,
155 option: &str,
156) -> Result<OsString> {
157 arguments
158 .next()
159 .with_context(|| format!("{option} requires a value; {USAGE}"))
160}
161
162#[cfg(test)]
163mod tests {
164 use super::{failure_receipt_line, receipt_line};
165 use std::path::PathBuf;
166 use u280_evidence_oracle::{SourceOracleFailure, SourceOracleOutcome};
167
168 #[test]
169 fn completed_receipt_preserves_the_source_only_boundary() {
170 let receipt = receipt_line(&SourceOracleOutcome {
171 cryptographic_oracle_valid: true,
172 case: "success_batch_1".to_owned(),
173 batch: 1,
174 compared_jobs: 1,
175 all_unwritten_slots_valid: true,
176 result_manifest_sha256: "ab".repeat(32),
177 result_manifest_bytes: 160,
178 result_directory: PathBuf::from("/tmp/source-result"),
179 })
180 .unwrap();
181 let value: serde_json::Value = serde_json::from_str(&receipt).unwrap();
182 assert_eq!(value["schema"].as_u64(), Some(2));
183 assert_eq!(value["cryptographic_oracle_valid"].as_bool(), Some(true));
184 assert_eq!(value["all_unwritten_slots_valid"].as_bool(), Some(true));
185 assert_eq!(value["hardware_execution"].as_bool(), Some(false));
186 assert_eq!(value["card"].as_bool(), Some(false));
187 assert_eq!(value["performance"].as_bool(), Some(false));
188 assert_eq!(value["source_revision_bound"].as_bool(), Some(false));
189 assert_eq!(value["promotion_blocked"].as_bool(), Some(true));
190 assert_eq!(
191 value["hardware_completion_promotable"].as_bool(),
192 Some(false)
193 );
194 }
195
196 #[test]
197 fn structural_failure_receipt_has_no_cryptographic_decision() {
198 let receipt = failure_receipt_line(&SourceOracleFailure {
199 result_directory: PathBuf::from("/tmp/source-failure"),
200 result_manifest_sha256: "cd".repeat(32),
201 result_manifest_bytes: 80,
202 failure: "malformed source archive".to_owned(),
203 })
204 .unwrap();
205 let value: serde_json::Value = serde_json::from_str(&receipt).unwrap();
206 assert_eq!(value["schema"].as_u64(), Some(2));
207 assert!(value["cryptographic_oracle_valid"].is_null());
208 assert!(value["all_unwritten_slots_valid"].is_null());
209 assert_eq!(value["hardware_execution"].as_bool(), Some(false));
210 assert_eq!(value["card"].as_bool(), Some(false));
211 assert_eq!(value["performance"].as_bool(), Some(false));
212 assert_eq!(value["source_revision_bound"].as_bool(), Some(false));
213 assert_eq!(value["promotion_blocked"].as_bool(), Some(true));
214 }
215}