diff --git a/.claude/board/STATUS_BOARD.md b/.claude/board/STATUS_BOARD.md index c2d6ab0a..17607089 100644 --- a/.claude/board/STATUS_BOARD.md +++ b/.claude/board/STATUS_BOARD.md @@ -72,7 +72,7 @@ afterwards is a JIT kernel, not a rebuild. Plan path: |---|---|---|---| | D2.1 | Token-agreement harness scaffold (reference model stub + top-k comparator + stub result) | **In PR** | branch — `ReferenceModel::{load, stub}` + `TokenAgreementError` + `TopKAgreement::{compare, top1_rate, top5_rate, meets_cert_gate, aggregate}` + `TokenAgreementHarness::{measure_stub, measure_full}` + 13 tests. Real safetensors load + decode loop defer to D2.2. | | D2.2 | Decode-and-compare loop (top-k, per-layer MSE) | **Queued** | target ~220 LOC | -| D2.3 | Handler wiring for `/v1/shader/token-agreement` | **Queued** | target ~60 LOC | +| D2.3 | Handler wiring for `/v1/shader/token-agreement` | **In PR** | branch — `token_agreement_handler` routes `WireTokenAgreement` → TryFrom(CodecParams) at ingress (precision-ladder + overfit guard fire here) → `ReferenceModel::load` or stub fallback on nonexistent paths → `TokenAgreementHarness::measure_stub()` → `WireTokenAgreementResult { stub:true }`. Route added: `POST /v1/shader/token-agreement`. Phase 0 Wire + Phase 2 harness now round-trip end-to-end. | ### Phase 3 — Sweep driver + Lance logger — Queued diff --git a/crates/cognitive-shader-driver/src/serve.rs b/crates/cognitive-shader-driver/src/serve.rs index 10969bd0..9956c9d7 100644 --- a/crates/cognitive-shader-driver/src/serve.rs +++ b/crates/cognitive-shader-driver/src/serve.rs @@ -45,13 +45,16 @@ use serde_json::{json, Value}; use crate::codec_research; use crate::driver::ShaderDriver; use crate::engine_bridge::{self, unified_style, UNIFIED_STYLES}; +use crate::token_agreement::{ReferenceModel, TokenAgreementHarness}; use crate::wire::{ WireCalibrateRequest, WireCalibrateResponse, WireCrystal, WireDispatch, WireHealth, WireIngest, WirePlanRequest, WirePlanResponse, WireProbeRequest, WireProbeResponse, WireQualia, WireRunbookRequest, WireRunbookResponse, WireRunbookStep, WireRunbookStepResult, WireStepResult, WireStyleInfo, WireTensorsRequest, - WireTensorsResponse, WireUnifiedStep, + WireTensorsResponse, WireTokenAgreement, WireTokenAgreementResult, WireUnifiedStep, }; +use lance_graph_contract::cam::CodecParams; +use std::path::Path as StdPath; use lance_graph_contract::cognitive_shader::CognitiveShaderDriver; struct ServerState { @@ -87,6 +90,12 @@ pub fn router(driver: ShaderDriver) -> Router { .route("/v1/shader/tensors", post(tensors_handler)) .route("/v1/shader/calibrate", post(calibrate_handler)) .route("/v1/shader/probe", post(probe_handler)) + // D2.3 — I11 cert gate endpoint. Handler routes to + // TokenAgreementHarness::measure_stub() until D2.2 lands the real + // decode-and-compare loop. Stub result carries `stub:true` + + // `backend:"stub"` so clients cannot confuse Phase 0 stub output + // for a real measurement (anti-#219 defense, type-level). + .route("/v1/shader/token-agreement", post(token_agreement_handler)) // Scheduled runbook: one POST runs a list of steps. Test injection // lands here — a client script submits its full codec-research // protocol as a single DTO, the server executes and returns all @@ -219,6 +228,62 @@ async fn probe_handler( .map_err(|e| (StatusCode::BAD_REQUEST, Json(json!({"error": e})))) } +/// D2.3 — `POST /v1/shader/token-agreement` handler. +/// +/// Routes `WireTokenAgreement` through the Phase-0-honest stub path: +/// +/// 1. Validates `candidate: WireCodecParams → CodecParams` via TryFrom, +/// surfacing typed errors (precision-ladder, overfit guard) as HTTP 400. +/// 2. Loads reference model via `ReferenceModel::load` when `model_path` +/// points to a real directory; otherwise falls back to +/// `ReferenceModel::stub` so tests can drive the handler without a +/// filesystem. +/// 3. Builds `TokenAgreementHarness` + calls `measure_stub()` (D2.1 stub). +/// 4. Returns `WireTokenAgreementResult { stub:true, backend:"stub", … }`. +/// +/// Real decode-and-compare lands at D2.2; the Wire surface + routing are +/// frozen now so client integration work can proceed against the stub. +async fn token_agreement_handler( + Json(req): Json, +) -> Result, (StatusCode, Json)> { + // Validate CodecParams at ingress (precision-ladder / overfit guard + // fire here, not inside the harness). + let _params: CodecParams = req + .candidate + .clone() + .try_into() + .map_err(|e: lance_graph_contract::cam::CodecParamsError| { + (StatusCode::BAD_REQUEST, Json(json!({"error": format!("invalid CodecParams: {e}")}))) + })?; + + // Reference model — real path if it exists, stub otherwise. D2.2 + // replaces with a strict path check once the safetensors loader lands. + let model_path = StdPath::new(&req.model_path); + let reference = if model_path.exists() { + ReferenceModel::load(model_path).map_err(|e| { + (StatusCode::BAD_REQUEST, Json(json!({"error": format!("model load: {e}")}))) + })? + } else { + // Deterministic stub keyed on the path string so repeated calls + // return the same stub fingerprint (useful for cache/regression + // tests that POST synthetic model_path values). + let mut h = std::collections::hash_map::DefaultHasher::new(); + std::hash::Hash::hash(&req.model_path, &mut h); + ReferenceModel::stub(std::hash::Hasher::finish(&h), 0) + }; + + let harness = TokenAgreementHarness::new( + reference, + req.reference, + req.candidate, + req.n_tokens, + ); + harness + .measure_stub() + .map(Json) + .map_err(|e| (StatusCode::BAD_REQUEST, Json(json!({"error": format!("{e}")})))) +} + async fn route_handler( State(_state): State, Json(wire): Json,