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]>
30 lines
1.3 KiB
Rust
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)
|
|
}
|