open-standards/jcp/jcp-rs/crates/jcp-trace/examples/serve.rs
Transaction Science a5b8792f60 jcp-trace: a std-only OTLP /v1/traces receiver (server feature)
Makes jcp-trace a live receiver, not just a static ingest. Behind the `server`
feature (no async runtime, no extra deps — std::net only), POST a gen_ai.* OTLP
trace and get the sealed eval/1 receipt back the instant the run ends — the
incumbent logs the trace to a table; this seals it, with a joule on it, on
arrival.

- EvalServer holds a steward key, the quality gate every posted run is judged
  against, the Estimator µJ/token rate, and the hash-chained receipt state.
- POST /v1/traces accepts a bare OTLP doc or an envelope
  { trace: <otlp>, assessments: [ {name, source, value, …} ] } so a run posts
  its judge/human verdicts with the trace; the reply is the eval/1 wire plus
  envelope_hex. One connection at a time so the chain stays one ordered ledger.
  GET /healthz for liveness.

14 tests (was 13) incl. an in-process HTTP roundtrip asserting the sealed
receipt; clippy --all-features --all-targets clean; live curl smoke posts a
real OTLP gen_ai trace and gets a sealed eval/1 receipt (uj_per_accepted_token
+ a content-addressed gate verdict + the COSE envelope). Runnable example
`serve`.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-27 20:53:48 -04:00

30 lines
1.3 KiB
Rust

//! Run the OTLP `/v1/traces` receiver — POST a gen_ai trace, get back a sealed
//! eval/1 receipt with a joule on it.
//!
//! ```text
//! cargo run -p jcp-trace --example serve --features server -- 127.0.0.1:8090 &
//! curl -s localhost:8090/v1/traces -d '{"trace":{"resourceSpans":[{"scopeSpans":[{"spans":[
//! {"traceId":"a","spanId":"s1","name":"chat","attributes":[
//! {"key":"gen_ai.operation.name","value":{"stringValue":"chat"}},
//! {"key":"gen_ai.usage.output_tokens","value":{"intValue":"64"}}]}]}]}]},
//! "assessments":[{"name":"correctness","source":"llm_judge","value":"pass"}]}'
//! ```
use std::net::TcpListener;
use ed25519_dalek::SigningKey;
use jcp_trace::{server, Gate};
fn main() -> std::io::Result<()> {
let addr = std::env::args().nth(1).unwrap_or_else(|| "127.0.0.1:8090".to_string());
let listener = TcpListener::bind(&addr)?;
eprintln!("jcp-trace OTLP receiver: http://{addr}/v1/traces");
// Seal under one steward; every posted run is judged against this gate and
// priced at 12 µJ/token where a span carries no measured reading.
let sk = SigningKey::from_bytes(&[9u8; 32]);
let gate = Gate::require_pass(["correctness", "safety"]);
let mut srv = server::EvalServer::new(sk, "did:example:trace-receiver", gate, 12);
server::run(&listener, &mut srv)
}