//! A Joule Code coding operation under a JouleContract. //! //! The mapping is not an analogy; the objects line up field by field: //! //! - **The edit set is the frame.** A `CodingOp`'s `edits[].path` list is the //! declared mutable footprint (JouleContract §3.4). An edit outside the //! contracted paths is an out-of-frame effect — the breach primitive, made //! concrete for code, caught at the boundary by [`frame_check`]. //! - **The tier cost is the ceiling.** The cascade's per-tier joule class //! prices the contract's EnPI ceiling. //! - **JCR provenance maps onto the energy classes.** `HwShunt` → `Metered` //! (with a meter binding), `ModelBased` → `ModelBased` (with an anchor), //! `Estimator` → `Unaccounted`. Honest in, honest out — an `Estimator` //! figure can never serialize as `Metered` through this binding. //! //! The binding produces receipts that pass JouleContract's **receipt-local** //! validation (at most JR-2 — JouleContract SPEC §10's two-stage rule); //! composing them to JR-3+ with sealed evidence, a key log, and anchored //! intervals is the deployment's verifier-of-record work, not this crate's. use joule_code_cargo::CodingOp; use joule_code_receipt::{Energy, Provenance}; use joule_code_schema::ContentId; use joulecontract_receipt::{ DischargeEntry, DischargeResult, Engagement, FrameMode, Generator, KeyId, MeterBinding, Qualification, Receipt, TierClass, }; use joulecontract_schema::{ Ceiling, ContentAddress, Contract, DerivationBinding, EnergyClass, GovernanceClass, Profile, Rigor, }; use std::collections::BTreeSet; /// Microjoules per joule: JCR energy records are integer µJ; JouleContract /// ceilings are EOC joules. const UJ_PER_J: f64 = 1_000_000.0; /// Bridge a Joule Code content identity (`b3:…`, BLAKE3/base64) into a /// JouleContract content address (`sha2-256:…`). The foreign identity string /// is the addressed content — a cross-standard *reference*, not a re-hash /// claim: resolving it back to the Joule Code object goes through the /// original `ContentId`. pub fn bridge_id(id: &ContentId) -> ContentAddress { ContentAddress::of_bytes(id.0.as_bytes()) } /// JCR provenance → JouleContract energy class. The mapping only ever moves /// trust *down*: a coarse estimator is `Unaccounted`, never promoted. pub fn energy_class(p: Provenance) -> EnergyClass { match p { Provenance::HwShunt => EnergyClass::Metered, Provenance::ModelBased => EnergyClass::ModelBased, Provenance::Estimator => EnergyClass::Unaccounted, } } /// Build the JouleContract for a coding request: what must hold, what may be /// touched, what may be spent. pub fn contract_for_op( intent: &str, prior: &ContentId, frame_paths: &[String], ceiling_joules: f64, enb_ref: ContentAddress, derivation: Option, rigor_target: Rigor, ) -> Contract { Contract { joulecontract: "0.8.0".into(), subject: intent.to_string(), rigor_target, pre: vec![format!("op builds on prior tree {}", prior.0)], post: vec!["every edit applied within the declared frame".into()], inv: vec![ "op signature verifies over op_id ‖ prior".into(), "edits are ordered and pairwise disjoint".into(), ], frame: frame_paths.to_vec(), ceiling: Ceiling { enpi_joules: ceiling_joules, unit: "EOC-J".into(), enb_ref, derivation, }, sector_equivalence: None, amends: None, } } #[derive(Debug, thiserror::Error, PartialEq, Eq)] pub enum BindingError { /// An edit outside the contracted footprint: the out-of-frame effect, /// caught at the boundary instead of discovered in review. #[error("out-of-frame edit: `{path}` is not within the contracted footprint")] OutOfFrame { path: String }, #[error("contract addressing failed: {0}")] Address(String), } /// Check every edit path against the contract's frame. A path is in-frame /// when it equals a frame entry or sits under a frame entry treated as a /// directory prefix (`src/` covers `src/lib.rs`). pub fn frame_check(contract: &Contract, op: &CodingOp) -> Result<(), BindingError> { for edit in &op.edits { let in_frame = contract.frame.iter().any(|entry| { let dir = if entry.ends_with('/') { entry.clone() } else { format!("{entry}/") }; edit.path == *entry || edit.path.starts_with(&dir) }); if !in_frame { return Err(BindingError::OutOfFrame { path: edit.path.clone(), }); } } Ok(()) } /// The static identities a deployment binds once: who verifies, under what /// qualification, paid by whom; who generates; where the meter attestation /// comes from when the platform has one. pub struct Binder { pub verifier_keys: BTreeSet, pub verifier_entity: Option, pub engagement: Engagement, pub recourse: Option, pub qualification: Qualification, pub tier_operator_keys: BTreeSet, pub generator: Generator, pub receipt_signer: KeyId, /// Meter attestation for `HwShunt` readings (`Metered` binds or demotes). pub meter: Option, /// Calibration anchor for `ModelBased` readings. pub energy_anchor: Option, pub register_pin: ContentAddress, /// The sealed evidence bundle address, where the deployment seals one. pub evidence: Option, } impl Binder { /// Discharge a coding operation against its contract into a JouleContract /// receipt. The frame is checked **here** — its discharge mode is /// `Observed` because the footprint was compared against the actual edit /// set, not analyzed from the artifact. An out-of-frame op still gets a /// receipt: the frame serializes `undischarged` with the offending path, /// because a breach recorded honestly beats a breach driven underground. pub fn receipt_for_op( &self, contract: &Contract, op: &CodingOp, op_id: &ContentId, energy: &Energy, ) -> Result { let frame_result = match frame_check(contract, op) { Ok(()) => DischargeResult::Closed, Err(BindingError::OutOfFrame { path }) => DischargeResult::Undischarged { reason: format!("out-of-frame edit: {path}"), }, Err(e) => return Err(e), }; let provenance = energy_class(energy.provenance); let spent_uj = energy.measured_uj.unwrap_or(energy.estimated_uj); let joules_spent = spent_uj as f64 / UJ_PER_J; let binding_tier = ContentAddress::of_bytes(b"joule-code-joulecontract deterministic binding checks v1"); let discharge = contract .obligations() .into_iter() .map(|ob| { let is_frame = ob.kind == joulecontract_schema::ObligationKind::Frame; DischargeEntry { obligation: ob, tier: binding_tier.clone(), tier_class: TierClass::Deterministic, tier_operator_keys: self.tier_operator_keys.clone(), result: if is_frame { frame_result.clone() } else { DischargeResult::Closed }, mode: is_frame.then_some(FrameMode::Observed), qualification: Some(self.qualification.clone()), verifier_keys: self.verifier_keys.clone(), verifier_entity: self.verifier_entity.clone(), engagement: Some(self.engagement.clone()), recourse: self.recourse.clone(), re_discharged_by: None, } }) .collect(); Ok(Receipt { contract: contract .content_address() .map_err(|e| BindingError::Address(e.to_string()))?, transition: bridge_id(op_id), generator: self.generator.clone(), discharge, execution: vec![ContentAddress::of_bytes( format!("joule-code tier {:?}", op.tier).as_bytes(), )], joules_spent, meter: match provenance { EnergyClass::Metered => self.meter.clone(), _ => None, }, energy_anchor: match provenance { EnergyClass::ModelBased => self.energy_anchor.clone(), _ => None, }, joules_allowed: contract.ceiling.enpi_joules, provenance, enb_ref: contract.ceiling.enb_ref.clone(), grid_mix: None, register_pin: self.register_pin.clone(), evidence: self.evidence.clone(), claimed_profile: Profile { rigor: Rigor::Jr2, energy: provenance, governance: GovernanceClass::Managed, }, receipt_signer: self.receipt_signer.clone(), }) } } #[cfg(test)] mod tests { use super::*; use joule_code_cargo::{Anchor, CodingOp, Edit}; use joule_code_schema::Tier; use joulecontract_receipt::{validate, LoopGovernance, TrustRoot}; use joulecontract_schema::Demotion; fn key(label: &str) -> KeyId { ContentAddress::of_bytes(label.as_bytes()) } fn op(paths: &[&str]) -> (CodingOp, ContentId) { let op = CodingOp { alg: 0x01, prior: ContentId::of_bytes(b"prior tree"), edits: paths .iter() .map(|p| Edit { path: p.to_string(), anchor: Anchor::ByteRange { start: 0, end: 4 }, replacement: b"new!".to_vec(), }) .collect(), author: [7u8; 32], intent: "rename the settlement index field".into(), tier: Tier::L1, }; let id = ContentId::of_bytes(b"the op canonical bytes"); (op, id) } fn binder() -> Binder { Binder { verifier_keys: [key("audit-a")].into(), verifier_entity: Some("vLEI:AUDIT-A".into()), engagement: Engagement { selected_by: "did:web:relying.example".into(), compensated_by: "did:web:relying.example".into(), }, recourse: Some("ISAE3000".into()), qualification: Qualification { attestation: key("binding qualification"), signer_key: key("assessor"), valid_until: 20_000, validated_at: 10_000, root: Some(TrustRoot::ReferenceImpl), }, tier_operator_keys: [key("binding-operator")].into(), generator: Generator { system: "joule-code cascade".into(), version_digest: key("cascade build"), attestation: Some(key("cascade attestation")), attestation_signer: Some(key("registry")), key_set: [key("producer")].into(), entity: Some("vLEI:PRODUCER".into()), human_principals: vec!["did:web:eng.example".into()], loop_governance: Some(LoopGovernance { contract: key("loop contract"), discharged_by: [key("audit-a")].into(), root: TrustRoot::HumanAttested, }), }, receipt_signer: key("producer"), meter: Some(MeterBinding { attestation: key("rapl attestation"), signer_key: key("substrate-telemetry"), valid_until: 20_000, validated_at: 10_000, }), energy_anchor: Some(key("calibration anchor")), register_pin: key("register snapshot"), evidence: None, } } fn contract(frame: &[&str]) -> Contract { contract_for_op( "rename the settlement index field", &ContentId::of_bytes(b"prior tree"), &frame.iter().map(|s| s.to_string()).collect::>(), 0.01, key("enb"), Some(DerivationBinding { attestation: key("derivation"), basis: "metered median of comparable L1 ops".into(), signer: "did:web:energy-office.example".into(), }), Rigor::Jr2, ) } #[test] fn in_frame_op_discharges_and_validates_to_jr2() { let c = contract(&["src/", "Cargo.toml"]); let (op, id) = op(&["src/lib.rs", "Cargo.toml"]); let energy = Energy { measured_uj: Some(1_200), estimated_uj: 1_000, provenance: Provenance::HwShunt, }; let r = binder().receipt_for_op(&c, &op, &id, &energy).unwrap(); let v = validate(&c, &r).unwrap(); assert_eq!(v.max_profile.rigor, Rigor::Jr2); assert_eq!(v.max_profile.energy, EnergyClass::Metered); assert!(v.permits(&r.claimed_profile), "demotions: {:?}", v.demotions); assert!((r.joules_spent - 0.0012).abs() < 1e-9); assert!(r.residual() > 0.0); } #[test] fn out_of_frame_edit_is_caught_at_the_boundary() { let c = contract(&["src/"]); let (op, id) = op(&["src/lib.rs", "deploy/secrets.env"]); assert_eq!( frame_check(&c, &op), Err(BindingError::OutOfFrame { path: "deploy/secrets.env".into() }) ); // The receipt still issues — with the frame undischarged and the // offending path on the record. Honest breach beats hidden breach. let energy = Energy { measured_uj: None, estimated_uj: 900, provenance: Provenance::ModelBased, }; let r = binder().receipt_for_op(&c, &op, &id, &energy).unwrap(); let v = validate(&c, &r).unwrap(); assert!(v.demotions.iter().any(|d| matches!( d, Demotion::Undischarged { reason } if reason.contains("deploy/secrets.env") ))); assert_eq!(v.max_profile.rigor, Rigor::Jr1, "undischarged frame caps rigor"); } #[test] fn estimator_energy_is_unaccounted_never_promoted() { let c = contract(&["src/"]); let (op, id) = op(&["src/lib.rs"]); let energy = Energy::from_tokens(100, 10); let r = binder().receipt_for_op(&c, &op, &id, &energy).unwrap(); assert_eq!(r.provenance, EnergyClass::Unaccounted); assert!(r.meter.is_none(), "an estimator figure never carries a meter binding"); let v = validate(&c, &r).unwrap(); assert_eq!(v.max_profile.energy, EnergyClass::Unaccounted); } #[test] fn overrun_is_recorded_not_blocked() { // Receipt-time enforcement (JouleContract §5.3): the deviation is on // the record as a negative residual; amend-or-fix is the resolution. let c = contract(&["src/"]); let (op, id) = op(&["src/lib.rs"]); let energy = Energy { measured_uj: Some(50_000), // 0.05 J against a 0.01 J ceiling estimated_uj: 50_000, provenance: Provenance::HwShunt, }; let r = binder().receipt_for_op(&c, &op, &id, &energy).unwrap(); assert!(r.residual() < 0.0); assert!(validate(&c, &r).is_ok(), "the system keeps running; the record decides"); } #[test] fn provenance_mapping_is_total_and_downward_honest() { assert_eq!(energy_class(Provenance::HwShunt), EnergyClass::Metered); assert_eq!(energy_class(Provenance::ModelBased), EnergyClass::ModelBased); assert_eq!(energy_class(Provenance::Estimator), EnergyClass::Unaccounted); } }