//! Ingest a real OTLP/JSON GenAI agent trace, attach joules, evaluate a quality //! gate, and seal one `eval/1` JCR-1 receipt — then verify it and show the ARL //! readiness projection. //! //! ```text //! cargo run -p jcp-trace --example ingest //! ``` use ed25519_dalek::SigningKey; use jcp_schema::ContentId; use jcp_trace::{otel, Assessment, EvalLog, Gate}; use serde_json::json; fn main() { // 1. The incumbent's output: an OTLP/JSON trace with gen_ai.* attributes — // an agent that planned, searched a tool, retrieved, and answered. let otlp = json!({ "resourceSpans": [{ "scopeSpans": [{ "spans": [ { "traceId": "4bf92f3577b34da6a3ce929d0e0e4736", "spanId": "a0", "name": "invoke_agent support-bot", "startTimeUnixNano": "1700000000000000000", "endTimeUnixNano": "1700000004000000000", "status": { "code": 1 }, "attributes": [{ "key": "mlflow.spanType", "value": { "stringValue": "AGENT" } }] }, { "traceId": "4bf92f3577b34da6a3ce929d0e0e4736", "spanId": "a1", "parentSpanId": "a0", "name": "chat plan", "startTimeUnixNano": "1700000000200000000", "endTimeUnixNano": "1700000001000000000", "status": { "code": 1 }, "attributes": [ { "key": "gen_ai.operation.name", "value": { "stringValue": "chat" } }, { "key": "gen_ai.request.model", "value": { "stringValue": "ssm-1b" } }, { "key": "gen_ai.usage.input_tokens", "value": { "intValue": "900" } }, { "key": "gen_ai.usage.output_tokens", "value": { "intValue": "110" } } ] }, { "traceId": "4bf92f3577b34da6a3ce929d0e0e4736", "spanId": "a2", "parentSpanId": "a0", "name": "execute_tool search_docs", "attributes": [{ "key": "gen_ai.operation.name", "value": { "stringValue": "execute_tool" } }] }, { "traceId": "4bf92f3577b34da6a3ce929d0e0e4736", "spanId": "a3", "parentSpanId": "a0", "name": "retrieve", "attributes": [{ "key": "mlflow.spanType", "value": { "stringValue": "RETRIEVER" } }] }, { "traceId": "4bf92f3577b34da6a3ce929d0e0e4736", "spanId": "a4", "parentSpanId": "a0", "name": "chat answer", "startTimeUnixNano": "1700000002000000000", "endTimeUnixNano": "1700000003800000000", "status": { "code": 1 }, "attributes": [ { "key": "gen_ai.operation.name", "value": { "stringValue": "chat" } }, { "key": "gen_ai.request.model", "value": { "stringValue": "ssm-1b" } }, { "key": "gen_ai.usage.input_tokens", "value": { "intValue": "1800" } }, { "key": "gen_ai.usage.output_tokens", "value": { "intValue": "240" } } ] } ] }] }] }); let mut trace = otel::from_otlp_json(&otlp).expect("valid OTLP gen_ai trace"); println!("ingested trace {} — {} spans", &trace.trace_id[..12], trace.spans.len()); // 2. Attach joules. Here: no hardware meter, so an honest Estimator from // token counts (12 µJ/token). A real deployment passes a measured reading // per span and the provenance reads HwShunt instead. trace.attach_estimator_energy(12); // 3. The incumbent's other output: assessments (a judge, a human). let assessments = vec![ Assessment::judge_pass("correctness", "openai:/gpt-4o") .with_rationale("all expected facts supported by the answer"), Assessment::judge_pass("safety", "family:/safety-judge"), Assessment::judge_score("groundedness", "openai:/gpt-4o", 0.93), Assessment::human("helpfulness", "reviewer:alice", true), ]; // 4. The quality gate: correctness AND safety must pass; groundedness >= 0.85. let gate = Gate::require_pass(["correctness", "safety"]).and_min("groundedness", 0.85); // 5. Seal it into a chained log (one ordered ledger of evaluated runs). let sk = SigningKey::from_bytes(&[9u8; 32]); let pk = sk.verifying_key().to_bytes(); let steward = "did:example:eval-steward"; let mut log = EvalLog::new(sk, steward); let envelope = log.append(&trace, &assessments, &gate).expect("seal").to_vec(); let t = trace.totals(); println!("\n── sealed eval/1 receipt ──"); println!(" tokens : {} in / {} out", t.input_tokens, t.output_tokens); println!(" energy : {} µJ ({})", t.uj_total, t.energy_provenance.jcr1_label()); println!( " uj_per_accepted_token: {}", // "—" when no tokens were accepted: 0 is this figure's optimum, so // printing it would rank a run that delivered nothing above every run // that did. t.uj_per_accepted_token .map_or_else(|| "—".to_string(), |uj| uj.to_string()) ); println!(" envelope : {} bytes, content id {}", envelope.len(), &ContentId::of_bytes(&envelope).to_hex()[..16]); // 6. Verify it — recover the gate verdict + readiness from the sealed bytes. let r = jcp_trace::verify(&envelope, steward, &pk).expect("verifies"); println!("\n── verified by steward key ──"); println!(" gate_passed : {}", r.gate_passed); if !r.gate_failures.is_empty() { println!(" gate_failures : {:?}", r.gate_failures); } println!(" sample_size : {}", r.sample_size); println!(" ARL validation hint : depth {} (a projection; the ARL rubric makes the claim)", r.validation_depth_hint); // 7. Tamper-evidence: flip one byte → the seal no longer verifies. let mut bad = envelope.clone(); let n = bad.len(); bad[n / 2] ^= 0x01; assert!(jcp_trace::verify(&bad, steward, &pk).is_err()); println!("\n tamper check : a single flipped byte fails verification ✓"); // 8. The chain: a second run links to the first by content id. let _ = log.append(&trace, &assessments, &gate).expect("seal #2"); let r1 = jcp_trace::verify(&log.entries()[1], steward, &pk).expect("verifies"); let first_cid = ContentId::of_bytes(&log.entries()[0]).to_hex(); println!(" chain : entry #1.prev == entry #0 id: {}", r1.prev == first_cid); }