@@ -50,8 +50,9 @@ use crate::wire::{
5050 WireCalibrateRequest , WireCalibrateResponse , WireCrystal , WireDispatch , WireHealth ,
5151 WireIngest , WirePlanRequest , WirePlanResponse , WireProbeRequest , WireProbeResponse ,
5252 WireQualia , WireRunbookRequest , WireRunbookResponse , WireRunbookStep ,
53- WireRunbookStepResult , WireStepResult , WireStyleInfo , WireTensorsRequest ,
54- WireTensorsResponse , WireTokenAgreement , WireTokenAgreementResult , WireUnifiedStep ,
53+ WireRunbookStepResult , WireStepResult , WireStyleInfo , WireSweepRequest ,
54+ WireSweepResponse , WireSweepResult , WireTensorsRequest , WireTensorsResponse ,
55+ WireTokenAgreement , WireTokenAgreementResult , WireUnifiedStep ,
5556} ;
5657use lance_graph_contract:: cam:: CodecParams ;
5758use std:: path:: Path as StdPath ;
@@ -96,6 +97,13 @@ pub fn router(driver: ShaderDriver) -> Router {
9697 // `backend:"stub"` so clients cannot confuse Phase 0 stub output
9798 // for a real measurement (anti-#219 defense, type-level).
9899 . route ( "/v1/shader/token-agreement" , post ( token_agreement_handler) )
100+ // D3.1 — codec sweep endpoint (batch mode). Client POSTs a
101+ // WireSweepRequest containing a cross-product grid; handler
102+ // enumerates grid, validates each candidate, builds stub results,
103+ // returns WireSweepResponse. SSE streaming + Lance append land in
104+ // D3.1b; this batch path stays for clients that want all results
105+ // in one response without streaming.
106+ . route ( "/v1/shader/sweep" , post ( sweep_handler) )
99107 // Scheduled runbook: one POST runs a list of steps. Test injection
100108 // lands here — a client script submits its full codec-research
101109 // protocol as a single DTO, the server executes and returns all
@@ -284,6 +292,54 @@ async fn token_agreement_handler(
284292 . map_err ( |e| ( StatusCode :: BAD_REQUEST , Json ( json ! ( { "error" : format!( "{e}" ) } ) ) ) )
285293}
286294
295+ /// D3.1 — `POST /v1/shader/sweep` handler (batch mode).
296+ ///
297+ /// Enumerates the cross-product grid from `WireSweepRequest`, validates
298+ /// each candidate via TryFrom(CodecParams), computes kernel_signature +
299+ /// backend per point, and returns all results in one `WireSweepResponse`.
300+ ///
301+ /// Stub: per-point calibrate/token_agreement are `None`; Phase 3 real
302+ /// handler invokes the actual codec_research + token_agreement harness.
303+ /// SSE streaming variant (D3.1b) replaces the batch return with per-point
304+ /// Server-Sent Events.
305+ async fn sweep_handler (
306+ Json ( req) : Json < WireSweepRequest > ,
307+ ) -> Result < Json < WireSweepResponse > , ( StatusCode , Json < Value > ) > {
308+ let start = std:: time:: Instant :: now ( ) ;
309+ let candidates = req. grid . enumerate ( ) ;
310+ let cardinality = candidates. len ( ) as u32 ;
311+
312+ let mut results = Vec :: with_capacity ( candidates. len ( ) ) ;
313+ for ( idx, wire_params) in candidates. into_iter ( ) . enumerate ( ) {
314+ // Validate each grid point at ingress — surface typed errors early.
315+ let params: CodecParams = wire_params
316+ . clone ( )
317+ . try_into ( )
318+ . map_err ( |e : lance_graph_contract:: cam:: CodecParamsError | {
319+ ( StatusCode :: BAD_REQUEST , Json ( json ! ( {
320+ "error" : format!( "grid point {idx}: invalid CodecParams: {e}" )
321+ } ) ) )
322+ } ) ?;
323+
324+ results. push ( WireSweepResult {
325+ grid_index : idx as u32 ,
326+ candidate : wire_params,
327+ kernel_hash : params. kernel_signature ( ) ,
328+ calibrate : None ,
329+ token_agreement : None ,
330+ stub : true ,
331+ } ) ;
332+ }
333+
334+ Ok ( Json ( WireSweepResponse {
335+ label : req. label ,
336+ cardinality,
337+ results,
338+ elapsed_ms : start. elapsed ( ) . as_millis ( ) as u64 ,
339+ lance_fragment_path : req. log_to_lance ,
340+ } ) )
341+ }
342+
287343async fn route_handler (
288344 State ( _state) : State < AppState > ,
289345 Json ( wire) : Json < WireUnifiedStep > ,
0 commit comments