//! `full_cascade_with_triangle` — one workload, three things at once: //! //! 1. **Cascade walk** — the L0–L4 default `JouleClawStack` resolves a //! free-form query, printing tier + joules. A second ask of the //! same question hits the L0 cache (cheapest-tier-that-works). //! //! 2. **The triangle (waves 41–45)** — a templated request walks the //! elicitor → cone → conservation → oracle pipeline via //! `jouleclaw_datum::dispatch_one_shot`, producing a verified //! `Candidate` with a conservation-conforming finding set and an //! oracle `Pass`. The harness, with no model called, is what //! decided this was a known template-class request and routed it. //! //! 3. **JCR-1 receipt** — at the end, a single signed `Receipt` is //! minted over the session's total joules (sum of cascade + triangle) //! using the JCR-1 wave-7 canonical det-CBOR + COSE_Sign1 path, //! cross-verifiable by the Python and Go reference verifiers in //! `conformance/`. //! //! ## Why all three in one file //! //! Before this example, the workspace had per-use-case demos //! (`agent_with_skill_induction`, `rag_with_retrieval`, //! `comprehend_demo`, …) and a separate JCR-1 minter //! (`jouleclaw-jcr1::jcr1_mint`), but no single binary that exercised //! the cascade + the new triangle + a signed receipt together. A sharp //! reader who runs this gets the whole story — runtime, the determinism //! gates, and the audit artifact — from one `cargo run` invocation. //! //! Run: //! cargo run -p jouleclaw-stack --example full_cascade_with_triangle use ed25519_dalek::SigningKey; use jouleclaw_cascade::tier::Runtime; use jouleclaw_cascade::types::{ContextRef, JouleBudget, QualityFloor, Query, QueryInput}; use jouleclaw_datum::{ AlwaysPassOracle, BandDispatch, CanonicalJsonByteCount, ConeCoord, Elicitor, NoveltyBand, RawRequest, Slot, SlotKind, SlotValue, SlotWalker, Template, TemplateId, dispatch_one_shot, }; use jouleclaw_jcr1::{TEST_KEYS_JSON, canonical_signed_core, cose_sign1_envelope, to_jcr1_wire}; use jouleclaw_prov::{CascadeTier, ReceiptBuilder, ToolTouch, input_hash}; use jouleclaw_stack::JouleClawStack; const CLOSED_AT: &str = "2026-06-04T12:00:00Z"; 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 a single-shot text query"); (answer.tier_used.wire_tag().to_string(), answer.joules_spent) } fn hello_template() -> Template { Template { id: TemplateId::new("hello-1"), signature: "print hello to NAME".into(), coord: ConeCoord::new(0.1, 0.05), language: "rust".into(), slots: vec![Slot { id: "name".into(), name: "Name".into(), prompt: "Whom should we greet?".into(), kind: SlotKind::Identifier, required: true, }], body: "fn main() { println!(\"hello, <>\"); }".into(), provenance: "demo".into(), } } #[tokio::main(flavor = "current_thread")] async fn main() { // ── 1. Cascade walk over a free-form query ───────────────────── println!("== 1. Cascade walk =="); let mut stack = JouleClawStack::with_defaults(); let question = "what is the magic word?"; let (tier_first, joules_first) = cascade_walk(&mut stack.runtime, question); let (tier_second, joules_second) = cascade_walk(&mut stack.runtime, question); println!(" first ask: tier={tier_first:<6} joules={joules_first:.9}"); println!(" second ask: tier={tier_second:<6} joules={joules_second:.9}"); println!( " Δ (cheapest-tier-that-works did its job): {:.9} J\n", joules_first - joules_second ); // ── 2. The triangle: elicitor → cone → conservation → oracle ── println!("== 2. The triangle (waves 41-45) =="); let template = hello_template(); let request = RawRequest::new("greet the world"); // Elicitor: walk the requester through the template's required slots. let mut walker = SlotWalker::new(&request, template.slots.clone()); let step = walker .submit(jouleclaw_datum::Answer::new( "name", SlotValue::Identifier { ident: "world".into(), }, )) .expect("answer accepted"); let draft = step .draft() .expect("required slots filled in one shot") .clone(); println!(" elicitor: filled {} slot(s)", draft.filled.len()); // Cone + dispatch_one_shot: classify, resolve, conservation-check, // oracle-gate. AlwaysPassOracle stands in for a real toolchain // oracle (e.g. rustc + cargo test) the consumer would plug in. let measure = CanonicalJsonByteCount; let dispatch = BandDispatch::new(NoveltyBand::datum_default()).expect("valid band table"); let oracle = AlwaysPassOracle; let outcome = dispatch_one_shot( &dispatch, &template, &draft, request.text.as_str(), &oracle, &measure, /* allow_stochastic = */ false, ) .await .expect("dispatch wires"); match &outcome { jouleclaw_datum::DatumOutcome::Verified { candidate, chain, findings, tier_id, } => { println!(" tier: {tier_id}"); println!( " conservation: {findings} finding(s) — all Conforming", findings = findings.len() ); println!(" oracle chain: {chain:?}"); println!(" candidate: {}", candidate.content); } other => panic!("expected Verified, got {other:?}"), } println!(); // ── 3. JCR-1 receipt over the session's total joules ────────── println!("== 3. JCR-1 receipt =="); // Session energy = sum of every joule charged across the two // cascade walks above. The triangle's deterministic path costs // nothing the wire-form accounts for (it's lookup), so the // receipt accounts only for the cascade tiers that actually // drew energy. let session_joules_uj = ((joules_first + joules_second) * 1_000_000.0).round().max(1.0) as u64; // Use the published test seed so the resulting envelope is // verifiable by anyone holding `conformance/test-keys.json`. 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("test seed present"); let seed: [u8; 32] = hex::decode(seed_hex) .expect("seed is hex") .try_into() .expect("seed is 32 bytes"); let sk = SigningKey::from_bytes(&seed); let steward = keys["steward_kid"].as_str().expect("steward kid"); 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: session_joules_uj, energy_provenance: jouleclaw_energy::Provenance::Estimator, }) .seal() .expect("seal"); let mut wire = to_jcr1_wire(&receipt).expect("project to wire form"); { let obj = wire.as_object_mut().expect("wire is a map"); obj.insert("id".into(), serde_json::json!("rcpt-demo-0001")); obj.insert("closed_at".into(), serde_json::json!(CLOSED_AT)); } let core = jouleclaw_jcr1::signed_core_bytes(&wire).expect("canonical signed core"); let envelope = cose_sign1_envelope(&core, &sk, steward); // The wire-form path above is authoritative; `canonical_signed_core` // off the typed receipt is computed only to exercise the second // code path. The two diverge when the demo patches `id`/`closed_at` // on the wire form (that's what the patch is for — the receipt's // builder uses fresh values). let _typed_core = canonical_signed_core(&receipt).expect("typed core"); println!(" spec: JCR-1"); println!(" energy: {session_joules_uj} µJ (Estimator)"); println!(" signed core: {} bytes (det-CBOR)", core.len()); println!(" COSE_Sign1: {} bytes (EdDSA/Ed25519)", envelope.len()); println!( " pub key (hex): {}", hex::encode(sk.verifying_key().to_bytes()) ); println!(); println!("✓ Cascade walked, triangle gated, receipt sealed — one run."); }