The consolidated end-to-end stake. Before this commit the workspace had per-use-case examples (minimal_cache_only, rag_with_retrieval, comprehend_demo, agent_with_skill_induction, save_memory_and_promote, ledger_breakdown) plus a separate JCR-1 minter in jouleclaw-jcr1, but no single binary that exercised the cascade + the new triangle + a signed receipt together. A sharp reader who ran the project came away without a single image of the whole stack working. The new example, full_cascade_with_triangle.rs, runs three things in one go: 1. Cascade walk over a free-form query through JouleClawStack::with_defaults() — same question twice; the first ask walks to L3 (~2 J), the second hits L0 (~7 nJ). Cheapest-tier- that-works in action. 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, a conservation-conforming finding set, and an oracle Pass chain. No model invoked. 3. JCR-1 receipt — at the end, one signed Receipt is minted over the session's total joules (cascade only; the triangle's deterministic path is free), using the wave-7 canonical det-CBOR + COSE_Sign1 path with the published test signing key. Output sizes (332 B core, 453 B envelope) and the EdDSA public key print so the reader can hand the envelope to the Python or Go reference verifier in conformance/. Three things proven in one cargo run, no external dependencies. Co-Authored-By: Claude Opus 4.7 <[email protected]>
211 lines
8.7 KiB
Rust
211 lines
8.7 KiB
Rust
//! `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, <<name>>\"); }".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.");
|
||
}
|