wickra_proof_core/spec.rs
1//! [`ProofSpec`] — the job to be proven.
2
3use crate::error::{Error, Result};
4use serde::{Deserialize, Serialize};
5use serde_json::Value;
6
7/// The input to `prove`: an embedded backtest strategy, an opaque dataset
8/// reference, and an optional engine-version pin.
9#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
10pub struct ProofSpec {
11 /// The embedded wickra-backtest `StrategySpec`, kept as raw JSON so
12 /// wickra-proof-core stays decoupled from backtest struct internals across the FFI
13 /// boundary.
14 pub strategy: Value,
15 /// Opaque, caller-chosen identifier of the dataset (e.g. content hash, URL,
16 /// git ref). It is hashed into `inputs_hash` but wickra-proof-core does NOT fetch
17 /// it — data is passed explicitly.
18 pub dataset_ref: String,
19 /// Expected backtest engine version. If present and it differs from the
20 /// linked `wickra-backtest` `version()`, `prove` returns
21 /// [`Error::EngineMismatch`] (no silent drift).
22 #[serde(default, skip_serializing_if = "Option::is_none")]
23 pub engine_version: Option<String>,
24}
25
26impl ProofSpec {
27 /// Parse a `ProofSpec` from JSON.
28 pub fn from_json(s: &str) -> Result<Self> {
29 let spec: Self = serde_json::from_str(s)?;
30 spec.validate()?;
31 Ok(spec)
32 }
33
34 /// Parse a `ProofSpec` from TOML.
35 pub fn from_toml(s: &str) -> Result<Self> {
36 let spec: Self = toml::from_str(s).map_err(|e| Error::Parse(e.to_string()))?;
37 spec.validate()?;
38 Ok(spec)
39 }
40
41 /// Validate structural invariants: the embedded strategy must be a JSON
42 /// object (a `StrategySpec`), not a scalar or array.
43 pub(crate) fn validate(&self) -> Result<()> {
44 if !self.strategy.is_object() {
45 return Err(Error::BadSpec(
46 "strategy must be a JSON object (a StrategySpec)".to_string(),
47 ));
48 }
49 Ok(())
50 }
51}