use std::sync::Arc;
use std::time::Duration;
use axum::routing::post;
use axum::{Json, Router};
use bytes::Bytes;
use firstpass_core::{GENESIS_HASH, verify_chain};
use serde_json::{Value, json};
use crate::provider::ProviderRegistry;
use crate::proxy::AppState;
use crate::{ProxyConfig, app, store};
type Fail = Box<dyn std::error::Error>;
pub async fn run() -> Result<(), Fail> {
let upstream = spawn_upstream().await?;
let db = std::env::temp_dir().join(format!("firstpass-demo-{}.db", uuid::Uuid::now_v7()));
let (proxy, _writer) = spawn_proxy(&upstream, &db).await?;
println!("\n\x1b[1mFirstpass demo\x1b[0m — routing one request through a real enforce ladder");
println!("no API keys, no config: the upstream is local, everything else is the real proxy\n");
let client = reqwest::Client::new();
let served: Value = client
.post(format!("{proxy}/v1/messages"))
.header("x-api-key", "byok-demo")
.json(&json!({
"model": "claude-haiku-4-5",
"max_tokens": 256,
"messages": [{ "role": "user", "content": "write a hello world in rust" }],
}))
.send()
.await?
.json()
.await?;
println!("served output : {}", served["content"][0]["text"]);
println!("served model : {}\n", served["model"]);
let trace = wait_for_trace(&db).await.ok_or("no trace recorded")?;
println!("\x1b[1m── audit receipt ──────────────────────────────────\x1b[0m");
for a in &trace.attempts {
let verdict = match a.verdict {
firstpass_core::Verdict::Pass => "\x1b[32mPASS\x1b[0m",
firstpass_core::Verdict::Fail => "\x1b[31mFAIL\x1b[0m",
firstpass_core::Verdict::Abstain => "\x1b[33mABSTAIN\x1b[0m",
};
println!(
" rung {} · {:<28} · {verdict} · ${:.4}",
a.rung, a.model, a.cost_usd
);
}
let f = &trace.final_;
println!(" ─────────────────────────────────────────────────");
println!(" total ${:.4}", f.total_cost_usd);
println!(
" baseline ${:.4} (always top-tier)",
f.counterfactual_baseline_usd
);
let pct = if f.counterfactual_baseline_usd > 0.0 {
f.savings_usd / f.counterfactual_baseline_usd * 100.0
} else {
0.0
};
println!(
" \x1b[32mSAVED ${:.4} ({pct:.0}% cheaper at proven quality)\x1b[0m",
f.savings_usd
);
println!(" trace_id {}", trace.trace_id);
println!(
" chain {}\n",
if verify_chain(std::slice::from_ref(&trace), &trace.prev_hash).is_ok() {
"verified ✓"
} else {
"BROKEN"
}
);
let trace_id = trace.trace_id.to_string();
let fb = client
.post(format!("{proxy}/v1/feedback"))
.json(&json!({
"trace_id": trace_id, "gate_id": "ci-tests",
"verdict": "pass", "reporter": "github-actions",
}))
.send()
.await?;
println!(
"feedback POST /v1/feedback → {} (downstream outcome recorded)",
fb.status()
);
let view = store::load_trace_view(&db, "default", &trace_id)?.ok_or("trace view missing")?;
let reporter = view
.deferred
.first()
.map_or("(none)", |d| d.reporter.as_str());
println!(
"deferred verdicts on trace: {} ({reporter} reported it)",
view.deferred.len()
);
let all = store::load_all_traces(&db)?;
println!(
"audit chain after feedback : {}\n",
if verify_chain(&all, GENESIS_HASH).is_ok() {
"\x1b[32mstill verified ✓\x1b[0m — the sealed record never changed"
} else {
"BROKEN"
}
);
println!("next: `firstpass onboard` wires your own agent through this in observe mode.\n");
let _ = std::fs::remove_file(&db);
Ok(())
}
async fn spawn_upstream() -> Result<String, Fail> {
async fn messages(body: Bytes) -> Json<Value> {
let model = serde_json::from_slice::<Value>(&body)
.ok()
.and_then(|v| v.get("model").and_then(Value::as_str).map(str::to_owned))
.unwrap_or_default();
let text = if model.contains("haiku") {
""
} else {
"fn main() { println!(\"hello world\"); }"
};
Json(json!({
"id": "msg_demo", "type": "message", "role": "assistant", "model": model,
"content": [{ "type": "text", "text": text }],
"usage": { "input_tokens": 1200, "output_tokens": 220 },
}))
}
let router = Router::new().route("/v1/messages", post(messages));
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await?;
let addr = listener.local_addr()?;
tokio::spawn(async move {
let _ = axum::serve(listener, router).await;
});
Ok(format!("http://{addr}"))
}
async fn spawn_proxy(
upstream: &str,
db: &std::path::Path,
) -> Result<(String, tokio::task::JoinHandle<()>), Fail> {
let routing = "[[route]]\nmatch = {}\nmode = \"enforce\"\nladder = [\"anthropic/claude-haiku-4-5\", \"anthropic/claude-sonnet-5\", \"anthropic/claude-opus-4-8\"]\ngates = [\"non-empty\"]\n";
let (up, dbs) = (upstream.to_owned(), db.to_string_lossy().into_owned());
let config = ProxyConfig::from_lookup(move |k| match k {
"FIRSTPASS_UPSTREAM_ANTHROPIC" => Some(up.clone()),
"FIRSTPASS_MODE" => Some("enforce".to_owned()),
"FIRSTPASS_CONFIG_TOML" => Some(routing.to_owned()),
"FIRSTPASS_DB" => Some(dbs.clone()),
_ => None,
})?;
let providers = ProviderRegistry::new(&config.upstream_anthropic, &config.upstream_openai);
let (traces, writer) = store::open(db)?;
let state = AppState {
config: Arc::new(config),
http: reqwest::Client::new(),
providers,
gate_health: Arc::new(crate::gate::GateHealthRegistry::new()),
shadow_ledger: Arc::new(crate::shadow::ShadowLedger::new()),
guardrails: Arc::new(crate::guard::GuardrailRegistry::new()),
traces,
adaptive: None,
bandit: None,
predictor: None,
tenant_rate_limiter: None,
spill: None,
};
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await?;
let addr = listener.local_addr()?;
let router = app(state)?;
tokio::spawn(async move {
let _ = axum::serve(listener, router).await;
});
Ok((format!("http://{addr}"), writer))
}
async fn wait_for_trace(db: &std::path::Path) -> Option<firstpass_core::Trace> {
for _ in 0..150 {
if let Ok(t) = store::load_all_traces(db)
&& let Some(first) = t.into_iter().next()
{
return Some(first);
}
tokio::time::sleep(Duration::from_millis(20)).await;
}
None
}