//! `openpay_per_joule_billing` — cross-standard composition demo. //! //! The pay-per-joule pitch made real. A single binary that: //! //! 1. Runs a JouleClaw cascade walk through `JouleClawStack::with_defaults` //! and counts every joule actually spent. //! 2. Mints a signed **JCR-1** receipt over the session via the wave-7 //! canonical det-CBOR + COSE_Sign1 path. //! 3. Extracts `joules_uj` from the receipt, applies a deterministic //! price function, and emits an **OpenPay**-shape settlement entry //! (the same wire form `op-ledger::Entry` accepts). //! 4. Writes a tiny "binding" document that links the receipt to the //! entry by id, so an auditor can verify the joule count signed in //! one standard matches the joule count priced in the other. //! //! Three standards, three artifacts, one pipeline — composing at the //! WIRE LAYER (serde_json), not at the crate-graph layer. Standards //! that need to live in different language ecosystems compose this //! way by design: each standard owns its bytes-on-the-wire contract, //! and consumers compose by parsing. //! //! ## Why no `op-core` import //! //! OpenPay lives in a sibling Cargo workspace; cross-workspace path //! deps don't carry `workspace.package` inheritance, so a direct //! import would fail to build. The deeper reason to NOT import is //! that this demo proves the *wire-format composition* — the //! settlement entry it emits is consumable by the real //! `op-ledger::Entry` deserializer (or any other ledger that //! implements OpenPay's Entry schema). //! //! Run: //! cargo run -p jouleclaw-stack --example openpay_per_joule_billing //! //! Output goes to stdout (the three artifacts) and to //! `/tmp/openpay-per-joule-{receipt,entry,binding}.json`. use ed25519_dalek::SigningKey; use jouleclaw_cascade::tier::Runtime; use jouleclaw_cascade::types::{ContextRef, JouleBudget, QualityFloor, Query, QueryInput}; use jouleclaw_jcr1::{TEST_KEYS_JSON, cose_sign1_envelope, to_jcr1_wire}; use jouleclaw_prov::{CascadeTier, ReceiptBuilder, ToolTouch, input_hash}; use jouleclaw_stack::JouleClawStack; use serde_json::json; use std::fs; const CLOSED_AT: &str = "2026-06-04T13:00:00Z"; /// Deterministic price function: 1 cent per 100 mJ consumed /// (i.e. $0.0001 per joule, $0.10 per kJ). A merchant sets this /// once and operators see it in the published rate card. /// /// Returned as USD minor units (cents) so the result drops straight /// into OpenPay's `Money { minor_units, currency }` shape. fn price_joules_uj_in_usd_cents(joules_uj: u64) -> i64 { // 1 cent = 100 mJ = 100_000 µJ. (joules_uj / 100_000) as i64 } fn make_query(text: &str) -> Query { Query { input: QueryInput::Text(text.to_string()), budget: JouleBudget::expensive(), quality: QualityFloor::any(), context: ContextRef::fresh(), deadline: None, } } fn cascade_walk(runtime: &mut Runtime, question: &str) -> (String, f64) { let answer = runtime .answer(make_query(question)) .expect("cascade resolves"); (answer.tier_used.wire_tag().to_string(), answer.joules_spent) } fn main() { println!("== 1. JouleClaw cascade walk =="); let mut stack = JouleClawStack::with_defaults(); let question = "list my ec2 instances in us-east-1"; let (tier_a, joules_a) = cascade_walk(&mut stack.runtime, question); let (tier_b, joules_b) = cascade_walk(&mut stack.runtime, question); let total_joules_f = joules_a + joules_b; let total_joules_uj = (total_joules_f * 1_000_000.0).round().max(1.0) as u64; println!(" first ask: tier={tier_a:<6} joules={joules_a:.9}"); println!(" second ask: tier={tier_b:<6} joules={joules_b:.9}"); println!(" session total: {total_joules_uj} µJ\n"); println!("== 2. Mint a JCR-1 receipt =="); let keys: serde_json::Value = serde_json::from_str(TEST_KEYS_JSON).expect("test-keys.json parses"); let seed_hex = keys["ed25519_seed_hex"].as_str().expect("seed"); let seed: [u8; 32] = hex::decode(seed_hex) .expect("hex") .try_into() .expect("32 bytes"); let sk = SigningKey::from_bytes(&seed); let steward = keys["steward_kid"].as_str().expect("steward"); let tool_id = "jouleclaw:cascade.demo"; let receipt = ReceiptBuilder::new() .input_hash(&input_hash(question)) .tier(CascadeTier::L0Cache) .steward(steward) .account_tool(ToolTouch { tool_id: tool_id.into(), joules_uj: total_joules_uj, energy_provenance: jouleclaw_energy::Provenance::Estimator, }) .seal() .expect("seal"); let mut wire = to_jcr1_wire(&receipt).expect("project"); let receipt_id = "rcpt-billing-0001"; { let obj = wire.as_object_mut().unwrap(); obj.insert("id".into(), json!(receipt_id)); obj.insert("closed_at".into(), json!(CLOSED_AT)); } let core = jouleclaw_jcr1::signed_core_bytes(&wire).expect("core"); let envelope = cose_sign1_envelope(&core, &sk, steward); let receipt_doc = json!({ "spec": "JCR-1", "receipt": wire, "signed_core_cbor_hex": hex::encode(&core), "cose_sign1_hex": hex::encode(&envelope), }); println!(" receipt id: {receipt_id}"); println!(" signed core: {} bytes", core.len()); println!(" COSE_Sign1: {} bytes\n", envelope.len()); println!("== 3. Apply pricing → OpenPay settlement entry =="); let cents = price_joules_uj_in_usd_cents(total_joules_uj); println!(" price function: 1 ¢ per 100 mJ (= $0.0001 / J)"); println!(" joules_uj: {total_joules_uj}"); println!(" USD minor: {cents} (¢{cents}, = ${:.4})\n", cents as f64 / 100.0); // OpenPay-shape settlement transaction. Field set matches // op-ledger v1's serde-derived `Transaction` (id, ledger_id, // status, external_id, description, effective_at_unix_secs, // entries: Vec, metadata). Each Entry matches op-ledger // v1's serde shape: account_id (newtype around Uuid → serializes // as the bare Uuid string), direction (PascalCase enum), // amount: Money { minor_units, currency: { code: [u8; 3], // exponent: u8 } }. A real op-ledger ingester reads this via // serde_json::from_value::. let txn_uuid = "11111111-1111-1111-1111-000000000001"; let ledger_uuid = "00000000-0000-0000-0000-00000000ledg"; let merchant_account = "00000000-0000-0000-0000-000000000001"; let customer_account = "00000000-0000-0000-0000-000000000002"; let usd_currency = json!({ "code": [b'U' as u32, b'S' as u32, b'D' as u32], "exponent": 2 }); let entry_doc = json!({ "spec": "OpenPay/Ledger v1 — Transaction", "id": txn_uuid, "ledger_id": ledger_uuid, "status": "Posted", "external_id": receipt_id, "description": format!( "JouleClaw cascade — {total_joules_uj} µJ consumed (receipt {receipt_id})" ), "effective_at_unix_secs": 1_780_000_000u64, "entries": [ { "account_id": customer_account, "amount": {"minor_units": cents, "currency": usd_currency.clone()}, "direction": "Debit" }, { "account_id": merchant_account, "amount": {"minor_units": cents, "currency": usd_currency.clone()}, "direction": "Credit" } ], "metadata": [ ["jcr1_receipt_id", receipt_id], ["joules_uj", &total_joules_uj.to_string()] ] }); println!("== 4. Binding document — JCR-1 ↔ OpenPay =="); let binding = json!({ "spec": "JouleClaw cross-standard binding v1", "receipt": { "spec": "JCR-1", "id": receipt_id, "joules_uj": total_joules_uj, "signed_by_steward": steward, }, "settlement": { "spec": "OpenPay/Ledger v1", "transaction_id": txn_uuid, "amount_minor_units": cents, "currency": "USD", }, "price_function": { "kind": "linear_joules", "cents_per_microjoule_inverse": 100_000, "documentation": "1 USD cent = 100 mJ consumed (= $0.0001 / J)", }, "verifiable_invariant": "binding.receipt.joules_uj × cents_per_microjoule_inverse⁻¹ == binding.settlement.amount_minor_units (integer division)", }); // Write artifacts. fs::write( "/tmp/openpay-per-joule-receipt.json", serde_json::to_string_pretty(&receipt_doc).unwrap(), ) .expect("write receipt"); fs::write( "/tmp/openpay-per-joule-entry.json", serde_json::to_string_pretty(&entry_doc).unwrap(), ) .expect("write entry"); fs::write( "/tmp/openpay-per-joule-binding.json", serde_json::to_string_pretty(&binding).unwrap(), ) .expect("write binding"); println!(" ✓ wrote /tmp/openpay-per-joule-receipt.json"); println!(" ✓ wrote /tmp/openpay-per-joule-entry.json"); println!(" ✓ wrote /tmp/openpay-per-joule-binding.json"); println!(); println!("Cross-standard composition: three artifacts, three specs, one pipeline."); println!("Each artifact stands alone under its standard. Together they bind a"); println!("signed energy measurement (JCR-1) to a settlement entry (OpenPay) by"); println!("a documented, auditable price function."); // Independent self-check: the binding's invariant holds. let derived_cents = (total_joules_uj / 100_000) as i64; assert_eq!( derived_cents, cents, "binding invariant violated: derived cents ≠ entry cents" ); }