//! The full "metered, attested UI" with **both recognized edges real at once**: //! a joule-ui interaction gated by an actual signed **JCP grant** (the //! Authorizer edge, SPEC §6) and metered by the actual **Sandbox PowerMeter** //! (the Meter edge, SPEC §5). Each interaction's figure is debited from the //! grant's budget, and once the budget is spent the **real JCP runtime** — not //! a stub — denies the next interaction. Each one seals into the chain. //! //! Each interaction is metered as one `PowerMeter` span and sealed with that //! span's own figure and provenance (the `SpanMeter` in `support/`, the //! adapter `joule-ui-serve` uses), and the output says which it is. The //! handlers run on this process's own thread and spawn nothing, so the meter //! is `PowerMeter::detect_in_process()`: where this process's own CPU energy //! counter is live (every Apple Silicon host checked, macOS 27.0.1) each //! figure is `HwShunt`, that counter's delta over the interaction. Where it //! is not, the meter is `detect()`'s: a live machine-wide counter's delta, or //! the `Estimator` figure, wall-clock × TDP for the interaction's span — never //! 0 µJ for work that ran. Interactions fire until the runtime denies one, so //! the budget binds whatever the figures are. //! //! The grant is debited with the figure AND its provenance: `Authorizer::charge` //! is handed the interaction's sealed figure with its grade, and the authorizer //! here hands the JCP runtime an estimate as an estimate, never wrapped as a //! measurement. JCP debits a budget by the reading if there is one, else by the //! estimate, so estimated interactions spend the grant too. //! //! ```bash //! cargo run -p joule-ui-interaction --example jcp_metered --release //! ``` use joule_ui_interaction::{ Authorizer, ContentId, Energy, InteractionLog, Provenance, run_interaction, verify_chain, }; use sandbox_meter::PowerMeter; use std::time::{Duration, Instant}; #[path = "support/span_meter.rs"] mod span_meter; use span_meter::SpanMeter; /// The JCP runtime as a joule-ui `Authorizer` (SPEC §6 edge): a real signed, /// joule-budgeted grant, enforced by the real two-phase JCP runtime. struct JcpAuthorizer { runtime: jcp_runtime::Runtime, sg: jcp_schema::SignedGrant, grant: jcp_schema::Grant, caller: jcp_schema::Did, } impl JcpAuthorizer { fn frame(&self, action: &str) -> jcp_schema::Frame { jcp_schema::Frame { kind: jcp_schema::FrameKind::ToolCall, trust: jcp_schema::Trust::Control, grant_id: self.grant.id(), action: action.to_string(), resource: jcp_schema::ContentId::of_bytes(action.as_bytes()), payload_hash: jcp_schema::ContentId::of_bytes(b""), model: None, } } } impl Authorizer for JcpAuthorizer { fn authorize(&mut self, action: &str) -> Result { let frame = self.frame(action); let d = self.runtime.authorize(&frame, &self.sg, self.caller, 0); match d.verdict { jcp_schema::Verdict::Allow => Ok(d.budget_remaining_uj), jcp_schema::Verdict::Deny(r) => Err(r.label().to_string()), } } fn charge(&mut self, _action: &str, cost: &Energy) -> u64 { // The figure's own provenance, as the interaction is sealed with it: a // measurement is charged as one, an estimate as an estimate. let uj = cost.uj(); let cost = match cost.provenance { Provenance::HwShunt => jcp_schema::Energy::measured(uj), Provenance::ModelBased => jcp_schema::Energy::modeled(uj), Provenance::Estimator => jcp_schema::Energy::estimator(uj), }; self.runtime.charge(&self.grant, cost).budget_remaining_uj } fn grant_id(&self) -> Option { Some(self.grant.id().to_hex()) } } /// What an interaction's figure is, read from its own record: a 0 for work /// that ran is no figure, and only a hardware record is called a reading. fn figure(e: &Energy) -> String { match (e.uj(), e.provenance) { (0, p) => format!("no figure (0 µJ [{}]: the meter gave no reading for this work)", p.label()), (uj, Provenance::HwShunt) => format!("{uj} µJ [HwShunt]"), (uj, p) => format!("{uj} µJ [{}, an estimate]", p.label()), } } /// The grant's budget, as minted below. const BUDGET_UJ: u64 = 15_000_000; fn main() { // A real signed JCP grant for the `apply` action, with a joule budget the // interactions below exhaust. let issuer = ed25519_dalek::SigningKey::from_bytes(&[1u8; 32]); let did = jcp_schema::Did::from_verifying_key(&issuer.verifying_key()); let grant = jcp_schema::Grant { issuer: did, subject: did, capability: jcp_schema::Capability::new( jcp_schema::ContentId::of_bytes(b"apply"), ["apply"], ), budget_uj: BUDGET_UJ, // 15 J // Joules-only grant: no money ceiling, no caveats. An empty caveat list is // omitted from the canonical encoding, so this stays byte-identical to the // grant this example minted before JCP gained dual ceilings. budget_funds: None, caveats: Vec::new(), expiry: 0, parent: None, delegate_depth: None, nonce: [0; 16], }; let sg = jcp_grant::issue(&issuer, grant.clone()).expect("issue grant"); // The handlers below run on this thread and spawn nothing: in-process work. let pm = PowerMeter::detect_in_process(); println!( "edges: JCP grant (15 J budget, real signed) + {}", if pm.is_hardware() { format!("Sandbox hardware meter ({})", pm.sources().join(", ")) } else { let why = match sandbox_meter::ioreport_status() { // Apple Silicon: say why the SoC counters were declined. sandbox_meter::IoReportStatus::Unavailable => String::new(), status => format!(" ({status})"), }; format!( "no hardware probe{why} — each interaction below is charged and sealed with the \ estimator's wall-clock × TDP figure for its own span" ) } ); let meter_sources = pm.sources(); let meter = SpanMeter::new(pm); let mut auth = JcpAuthorizer { runtime: jcp_runtime::Runtime::new(), sg, grant, caller: did, }; let surface = ContentId::of_bytes(b"
"); let mut log = InteractionLog::new(ed25519_dalek::SigningKey::from_bytes(&[8u8; 32])); // Fire interactions doing ~150ms of real work each, until the real JCP // runtime denies one (at most MAX_FIRES). Each one's figure is debited from // the grant, so once the figures spend it the next one is denied. The first // five and the last three lines are printed. const MAX_FIRES: u64 = 200; let mut lines = Vec::new(); for n in 1..=MAX_FIRES { let it = run_interaction(&surface, "apply", "did:key:z6MkDemoUser", &n.to_le_bytes(), &meter, Some(&mut auth), || { let t = Instant::now(); let mut x = n; while t.elapsed() < Duration::from_millis(150) { for _ in 0..50_000 { x = x.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407); } } std::hint::black_box(x).to_le_bytes().to_vec() }); let denied = !it.verdict.is_allow(); let charged = if denied { "not run".into() } else { figure(&it.energy) }; lines.push(format!( " apply #{n}: {} · {charged} · grant {} µJ left", it.verdict.tag(), it.budget_remaining_uj.map(|b| b.to_string()).unwrap_or_else(|| "—".into()), )); log.record(it); if denied { break; } } if lines.len() <= 8 { lines.iter().for_each(|l| println!("{l}")); } else { lines[..5].iter().for_each(|l| println!("{l}")); println!(" … {} more allowed", lines.len() - 8); lines[lines.len() - 3..].iter().for_each(|l| println!("{l}")); } let verified = verify_chain(log.entries()).expect("chain verifies"); let allowed: Vec<&Energy> = log .entries() .iter() .filter(|e| e.interaction.verdict.is_allow()) .map(|e| &e.interaction.energy) .collect(); let denied = verified - allowed.len(); let sealed: u64 = allowed.iter().map(|e| e.uj()).sum(); // What the grant was actually debited: the runtime never debits past the // budget, so the last figure may have been only partly charged. let left = log .entries() .iter() .rev() .filter(|e| e.interaction.verdict.is_allow()) .find_map(|e| e.interaction.budget_remaining_uj) .unwrap_or(BUDGET_UJ); let debited = BUDGET_UJ - left; let unmetered = allowed.iter().filter(|e| e.uj() == 0).count(); // Label the figures by the sealed records, not by the meter. let what = if unmetered > 0 { format!("{unmetered} had no figure, 0 µJ for work that ran") } else if allowed.iter().all(|e| e.provenance == Provenance::HwShunt) { format!("{sealed} µJ sealed, hardware counter deltas (HwShunt) from {}", meter_sources.join(", ")) } else if allowed.iter().all(|e| e.provenance == Provenance::Estimator) { format!("{sealed} µJ sealed, estimated (wall-clock × TDP over each interaction's span), not measured") } else { format!("{sealed} µJ sealed, partly estimated, not measured") }; let what = format!("{what}; {debited} of the {BUDGET_UJ} µJ budget debited"); println!( "\n{verified} interactions sealed: {} allowed; {what}; {denied} denied by the real JCP runtime{}.", allowed.len(), if denied > 0 { " once the budget was spent" } else { ", as the budget was never spent" } ); }