//! Joule Code conformance harness (SPEC §8). //! //! Loads the published wire vectors and runs the four suites: //! C1 content addressing, C2 signing, C3 receipt, C4 cascade. Each vector is //! reported PASS / FAIL / SKIP. SKIP marks a positive crypto vector whose //! derived field is a placeholder pending regeneration against this library //! (see `conformance/v1/regeneration.md`); the structural and negative checks, //! the real Ed25519 known-answer vectors, and the entire cascade suite run for //! real. Any FAIL makes the run non-zero (CI-ready). use std::collections::BTreeMap; use std::path::Path; use ed25519_dalek::{Signature, Verifier, VerifyingKey}; use joule_code_cargo::{Anchor, CargoError, CodingOp, Edit}; use joule_code_receipt::{Energy, Provenance, Receipt, ReceiptError}; use joule_code_cascade::Cascade; use joule_code_schema::{ContentId, Tier}; use serde::Deserialize; /// The outcome of a run over one or more suites. #[derive(Default, Debug)] pub struct Report { pub pass: u32, pub fail: u32, pub skip: u32, pub lines: Vec, } impl Report { fn pass(&mut self, name: &str) { self.pass += 1; self.lines.push(format!(" PASS {name}")); } fn fail(&mut self, name: &str, why: &str) { self.fail += 1; self.lines.push(format!(" FAIL {name}: {why}")); } fn skip(&mut self, name: &str, why: &str) { self.skip += 1; self.lines.push(format!(" SKIP {name}: {why}")); } /// Merge another report into this one. pub fn absorb(&mut self, other: Report) { self.pass += other.pass; self.fail += other.fail; self.skip += other.skip; self.lines.extend(other.lines); } } fn is_placeholder(s: &str) -> bool { s.contains("PLACEHOLDER") } // ---------- JSON mirrors of the vector files ---------- #[derive(Deserialize)] struct OpVector { name: String, op: OpJson, expected_op_id: Option, error: Option, } #[derive(Deserialize)] struct OpJson { alg: u8, prior: String, edits: Vec, author: String, intent: String, tier: Tier, } #[derive(Deserialize)] struct EditJson { path: String, anchor: AnchorJson, replacement_hex: String, } #[derive(Deserialize)] enum AnchorJson { ByteRange { start: u64, end: u64 }, Hunk { hunk_id: String }, } impl OpJson { fn to_op(&self) -> Result { let mut author = [0u8; 32]; let bytes = hex::decode(&self.author).map_err(|e| e.to_string())?; if bytes.len() != 32 { return Err("author key must be 32 bytes".into()); } author.copy_from_slice(&bytes); let edits = self .edits .iter() .map(|e| { Ok(Edit { path: e.path.clone(), anchor: match &e.anchor { AnchorJson::ByteRange { start, end } => Anchor::ByteRange { start: *start, end: *end, }, AnchorJson::Hunk { hunk_id } => Anchor::Hunk { hunk_id: ContentId(hunk_id.clone()), }, }, replacement: hex::decode(&e.replacement_hex).map_err(|x| x.to_string())?, }) }) .collect::, String>>()?; Ok(CodingOp { alg: self.alg, prior: ContentId(self.prior.clone()), edits, author, intent: self.intent.clone(), tier: self.tier, }) } } #[derive(Deserialize)] struct SigVector { name: String, verifying_key: Option, message_hex: Option, signature: Option, /// Present for op-binding cases: the signature preimage is `op_id ‖ prior`. op_id: Option, prior: Option, expected_verify: bool, } #[derive(Deserialize)] struct ReceiptVector { name: String, receipt: ReceiptJson, steward: Option, endpoint_verifying_key: Option, expected_envelope: Option, expected_verify: bool, error: Option, } #[derive(Deserialize)] struct ReceiptJson { op_id: String, tier: Tier, energy: EnergyJson, sequence: u64, nonce: String, } #[derive(Deserialize)] struct EnergyJson { measured_uj: Option, estimated_uj: u64, provenance: Provenance, } #[derive(Deserialize)] struct CascadeVector { name: String, stages: Vec, stage_closes: BTreeMap, expected_closing_tier: Option, } fn parse_tier(s: &str) -> Option { match s { "L0" => Some(Tier::L0), "L1" => Some(Tier::L1), "L2" => Some(Tier::L2), "L3" => Some(Tier::L3), "L4" => Some(Tier::L4), _ => None, } } // Map a CargoError to the stable error token used in the vectors. fn cargo_error_token(e: &CargoError) -> &'static str { match e { CargoError::EditsOverlap => "edits_overlap", CargoError::EditsMisordered => "edits_misordered", CargoError::UnknownAlgorithm(_) => "unknown_algorithm", CargoError::NoEdits => "no_edits", _ => "other", } } fn receipt_error_token(e: &ReceiptError) -> &'static str { match e { ReceiptError::HwShuntRequiresMeasured => "hwshunt_requires_measured", ReceiptError::EstimatorMustNotCarryMeasured => "estimator_must_not_carry_measured", _ => "other", } } // ---------- the four suites ---------- /// C1 — content addressing. Recompute `op_id`; honor expected errors. pub fn verify_coding_ops(json: &str) -> Result { let vectors: Vec = serde_json::from_str(json).map_err(|e| e.to_string())?; let mut r = Report::default(); for v in vectors { let op = match v.op.to_op() { Ok(op) => op, Err(e) => { r.fail(&v.name, &format!("malformed vector: {e}")); continue; } }; match (op.op_id(), v.error.as_deref()) { // Expected an error and got the right one. (Err(e), Some(expected)) => { if cargo_error_token(&e) == expected { r.pass(&v.name); } else { r.fail(&v.name, &format!("wrong error: got {}, want {expected}", cargo_error_token(&e))); } } // Expected an error, got an id. (Ok(_), Some(expected)) => r.fail(&v.name, &format!("expected error {expected}, got an op_id")), // Expected success. (Ok(id), None) => match v.expected_op_id { Some(expected) if is_placeholder(&expected) => { r.skip(&v.name, &format!("op_id pending regen (computed {id})")); } Some(expected) => { if id.as_str() == expected { r.pass(&v.name); } else { r.fail(&v.name, &format!("op_id mismatch: got {id}, want {expected}")); } } None => r.fail(&v.name, "no expected_op_id and no error"), }, (Err(e), None) => r.fail(&v.name, &format!("unexpected error: {e}")), } } Ok(r) } /// C2 — signing. Real Ed25519 known-answer cases run; op-binding cases whose /// signature is a placeholder are skipped pending regeneration. pub fn verify_signatures(json: &str) -> Result { let vectors: Vec = serde_json::from_str(json).map_err(|e| e.to_string())?; let mut r = Report::default(); for v in vectors { let (Some(vk_hex), Some(sig_hex)) = (&v.verifying_key, &v.signature) else { r.skip(&v.name, "no key/signature"); continue; }; if is_placeholder(sig_hex) { r.skip(&v.name, "signature pending regen"); continue; } let ok = (|| -> Result { let vk_bytes: [u8; 32] = hex::decode(vk_hex) .map_err(|e| e.to_string())? .try_into() .map_err(|_| "vk len".to_string())?; let sig_bytes: [u8; 64] = hex::decode(sig_hex) .map_err(|e| e.to_string())? .try_into() .map_err(|_| "sig len".to_string())?; // KAT cases verify a raw message; op-binding cases verify over the // preimage `op_id ‖ prior` (joule-code-cargo's signing preimage). let msg: Vec = if let Some(msg_hex) = &v.message_hex { hex::decode(msg_hex).map_err(|e| e.to_string())? } else if let (Some(op_id), Some(prior)) = (&v.op_id, &v.prior) { let mut pre = op_id.as_bytes().to_vec(); pre.extend_from_slice(prior.as_bytes()); pre } else { return Err("vector has neither message_hex nor op_id+prior".into()); }; let vk = VerifyingKey::from_bytes(&vk_bytes).map_err(|e| e.to_string())?; Ok(vk.verify(&msg, &Signature::from_bytes(&sig_bytes)).is_ok()) })(); match ok { Ok(got) if got == v.expected_verify => r.pass(&v.name), Ok(got) => r.fail(&v.name, &format!("verify={got}, expected {}", v.expected_verify)), Err(e) => r.fail(&v.name, &e), } } Ok(r) } /// C3 — receipt. The provenance↔measured coupling runs for real; positive /// signature reproduction is skipped pending regeneration. pub fn verify_receipts(json: &str) -> Result { let vectors: Vec = serde_json::from_str(json).map_err(|e| e.to_string())?; let mut r = Report::default(); for v in vectors { let energy = Energy { measured_uj: v.receipt.energy.measured_uj, estimated_uj: v.receipt.energy.estimated_uj, provenance: v.receipt.energy.provenance, }; match (energy.check_provenance(), v.error.as_deref()) { (Err(e), Some(expected)) => { if receipt_error_token(&e) == expected { r.pass(&v.name); } else { r.fail(&v.name, &format!("wrong error: got {}, want {expected}", receipt_error_token(&e))); } } (Ok(()), Some(expected)) => r.fail(&v.name, &format!("expected error {expected}, got valid")), (Err(e), None) => r.fail(&v.name, &format!("unexpected provenance error: {e}")), (Ok(()), None) => { // Coupling holds; verify the JCR-1 COSE_Sign1 envelope for real. let Some(env_hex) = v.expected_envelope.as_deref() else { r.fail(&v.name, "positive vector missing expected_envelope"); continue; }; if is_placeholder(env_hex) { r.skip(&v.name, "coupling OK; envelope pending regen"); continue; } match verify_receipt_envelope(&v, energy, env_hex) { Ok(ok) if ok == v.expected_verify => r.pass(&v.name), Ok(ok) => r.fail(&v.name, &format!("verify={ok}, expected {}", v.expected_verify)), Err(e) => r.fail(&v.name, &e), } } } } Ok(r) } /// Reconstruct the [`Receipt`] from a vector and verify its JCR-1 COSE_Sign1 /// envelope against the endpoint key + steward (SPEC §6). fn verify_receipt_envelope(v: &ReceiptVector, energy: Energy, env_hex: &str) -> Result { let steward = v.steward.as_deref().ok_or("missing steward")?; let vk_hex = v .endpoint_verifying_key .as_deref() .ok_or("missing endpoint_verifying_key")?; let vk_bytes: [u8; 32] = hex::decode(vk_hex) .map_err(|e| e.to_string())? .try_into() .map_err(|_| "endpoint key len".to_string())?; let envelope = hex::decode(env_hex).map_err(|e| e.to_string())?; let nonce: [u8; 8] = hex::decode(&v.receipt.nonce) .map_err(|e| e.to_string())? .try_into() .map_err(|_| "nonce len".to_string())?; let receipt = Receipt { op_id: ContentId(v.receipt.op_id.clone()), tier: v.receipt.tier, energy, sequence: v.receipt.sequence, nonce, }; Ok(receipt.verify_envelope(&envelope, steward, &vk_bytes).is_ok()) } /// C4 — cascade. Fully checkable: select the closing tier under /// stop-at-first-close and compare to the vector. pub fn verify_cascade(json: &str) -> Result { let vectors: Vec = serde_json::from_str(json).map_err(|e| e.to_string())?; let mut r = Report::default(); for v in vectors { let closes = |t: Tier| -> bool { v.stage_closes .iter() .any(|(k, &c)| parse_tier(k) == Some(t) && c) }; let got = Cascade::select_closing_tier(&v.stages, closes); if got == v.expected_closing_tier { r.pass(&v.name); } else { r.fail( &v.name, &format!("closing tier got {got:?}, expected {:?}", v.expected_closing_tier), ); } } Ok(r) } // ---------- C5: the JCP-gated cascade ---------- #[derive(Deserialize)] struct GatedStageVector { tier: String, closes: bool, } #[derive(Deserialize)] struct GatedExpect { closing_tiers: Vec>, spent_uj: u64, verdicts: Vec, deny_reasons: Vec, } #[derive(Deserialize)] struct GatedVector { name: String, stages: Vec, granted_actions: Vec, budget_uj: u64, runs: u32, expected: GatedExpect, } /// Scripted stage for C5: closes (or not) at its tier. struct Scripted { tier: Tier, closes: bool, } impl joule_code_cascade::Stage for Scripted { fn tier(&self) -> Tier { self.tier } fn try_resolve( &self, _req: &joule_code_cascade::Request, ) -> Option { if !self.closes { return None; } Some(joule_code_cascade::Resolution { op: None, answer: Some(format!("closed at {:?}", self.tier)), receipt: Receipt { op_id: ContentId::of_bytes(b"c5-op"), tier: self.tier, energy: Energy { measured_uj: None, estimated_uj: joule_code_jcp::tier_cost_estimate(self.tier), provenance: Provenance::Estimator, }, sequence: 0, nonce: [0; 8], }, }) } } /// C5 — the JCP-gated cascade (SPEC §5.1). Runs the REAL jcp runtime: per /// vector, issue a grant naming `granted_actions` with `budget_uj` over the /// fixed fixture identities, wrap the scripted stages with the gate, run the /// cascade `runs` times, and compare closing tiers, total debit, and the /// sealed receipt verdicts (deny reasons included — a denial is evidence, not /// a silent skip). /// /// Fixture constants (third-party implementations must use the same): /// issuer key = [1;32], agent key = [2;32], steward key = [7;32], /// steward did = "did:example:joule-code", /// resource = ContentId::of_bytes(b"repo:joule-code"), /// request = { prior: "tree-hash", intent: "rename the function" }, /// per-tier costs = `tier_cost_estimate` (SPEC §1.1 class markers). pub fn verify_gated_cascade(json: &str) -> Result { use ed25519_dalek::SigningKey; use jcp_schema::{Capability, Did, Grant}; let vectors: Vec = serde_json::from_str(json).map_err(|e| e.to_string())?; let mut r = Report::default(); let issuer = SigningKey::from_bytes(&[1; 32]); let agent = SigningKey::from_bytes(&[2; 32]); let steward_sk = SigningKey::from_bytes(&[7; 32]); let steward = "did:example:joule-code"; let steward_vk = steward_sk.verifying_key().to_bytes(); let tree = jcp_schema::ContentId::of_bytes(b"repo:joule-code"); 'vectors: for v in vectors { let grant = Grant { issuer: Did::from_verifying_key(&issuer.verifying_key()), subject: Did::from_verifying_key(&agent.verifying_key()), capability: Capability::new(tree, v.granted_actions.iter().map(String::as_str)), budget_uj: v.budget_uj, budget_funds: None, caveats: Vec::new(), expiry: 0, parent: None, delegate_depth: None, nonce: [0; 16], }; let chain = match jcp_grant::issue(&issuer, grant) { Ok(sg) => vec![sg], Err(e) => { r.fail(&v.name, &format!("grant issue: {e:?}")); continue; } }; let gate = joule_code_jcp::JcpGate::new( chain, Did::from_verifying_key(&agent.verifying_key()), steward_sk.clone(), steward, ); let mut stages: Vec> = Vec::new(); for s in &v.stages { let Some(tier) = parse_tier(&s.tier.to_uppercase()) else { r.fail(&v.name, &format!("bad tier {:?}", s.tier)); continue 'vectors; }; stages.push(Box::new(gate.wrap(Scripted { tier, closes: s.closes }))); } let cascade = Cascade::new(stages); let req = joule_code_cascade::Request { prior: "tree-hash".into(), intent: "rename the function".into(), }; let mut closings: Vec> = Vec::new(); for _ in 0..v.runs { closings.push( cascade .resolve(&req) .map(|res| format!("{:?}", res.receipt.tier).to_lowercase()), ); } let expected_closings: Vec> = v .expected .closing_tiers .iter() .map(|t| t.as_ref().map(|s| s.to_lowercase())) .collect(); if closings != expected_closings { r.fail(&v.name, &format!("closings got {closings:?}, want {expected_closings:?}")); continue; } if gate.spent() != v.expected.spent_uj { r.fail(&v.name, &format!("spent got {}, want {}", gate.spent(), v.expected.spent_uj)); continue; } let mut verdicts = Vec::new(); let mut deny_reasons = Vec::new(); for sealed in gate.receipts() { match jcp_receipt::extract(&sealed, steward, &steward_vk) { Ok(rec) => { verdicts.push(rec.verdict.clone()); if let Some(reason) = rec.deny_reason { deny_reasons.push(reason); } } Err(e) => { r.fail(&v.name, &format!("receipt extract: {e:?}")); continue 'vectors; } } } if verdicts != v.expected.verdicts { r.fail(&v.name, &format!("verdicts got {verdicts:?}, want {:?}", v.expected.verdicts)); continue; } if deny_reasons != v.expected.deny_reasons { r.fail(&v.name, &format!("deny reasons got {deny_reasons:?}, want {:?}", v.expected.deny_reasons)); continue; } r.pass(&v.name); } Ok(r) } /// Verify a whole pack directory (the five suite files). pub fn verify_pack(dir: &Path) -> Result { let mut report = Report::default(); type Suite = (&'static str, fn(&str) -> Result); let suites: &[Suite] = &[ ("coding_op_vectors.json", verify_coding_ops), ("signature_vectors.json", verify_signatures), ("receipt_vectors.json", verify_receipts), ("cascade_vectors.json", verify_cascade), ("gated_cascade_vectors.json", verify_gated_cascade), ]; for (file, f) in suites { let path = dir.join(file); report.lines.push(format!("[{file}]")); let json = std::fs::read_to_string(&path) .map_err(|e| format!("{}: {e}", path.display()))?; report.absorb(f(&json)?); } Ok(report) }