Skip to main content

inferlab_protocol/
wire.rs

1//! The versioned wire types used by framework integrations
2//! ([[RFC-0006:C-INTEGRATIONS]]) and by the release-owned Eval and Bench
3//! measurement clients ([[RFC-0004:C-MEASUREMENTS]]). [`AdapterProtocol`] and
4//! [`MeasurementProtocol`] are separate schema roots. Measurement data-asset
5//! declarations live in their owning sibling module.
6
7use crate::measurement_data_asset::{
8    EvalPreparedSourceBinding, MeasurementDataAssetPreparationRequest,
9    MeasurementDataAssetPreparationResult,
10};
11use schemars::JsonSchema;
12use serde::{Deserialize, Serialize};
13use std::collections::BTreeMap;
14use std::path::PathBuf;
15
16// Shared base types.
17
18/// The shared protocol version used by framework integrations and release-owned
19/// measurement clients. The only accepted value is `13` (serialized as the
20/// string `"13"`); a mismatch is rejected before lowering.
21#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
22pub enum ProtocolVersion {
23    /// Protocol version 13.
24    #[serde(rename = "13")]
25    V13,
26}
27
28impl ProtocolVersion {
29    /// The current adapter protocol version.
30    pub const CURRENT: Self = Self::V13;
31
32    /// The protocol version as spelled on the wire, projected for surfaces
33    /// such as the control plane version output ([[RFC-0006:C-INTEGRATIONS]]).
34    /// Kept exhaustive so a future variant forces this projection to follow.
35    #[must_use]
36    pub const fn as_str(self) -> &'static str {
37        match self {
38            Self::V13 => "13",
39        }
40    }
41}
42
43/// A framework-specific server setting value carried as structured JSON data
44/// (never a pre-rendered shell fragment) across the integration boundary.
45#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
46#[serde(untagged)]
47pub enum SettingValue {
48    /// A JSON boolean.
49    Bool(bool),
50    /// A JSON integer.
51    Integer(i64),
52    /// A JSON floating-point number.
53    Float(f64),
54    /// A JSON string.
55    String(String),
56    /// A JSON array of setting values.
57    Array(Vec<SettingValue>),
58    /// A JSON object of named setting values.
59    Object(BTreeMap<String, SettingValue>),
60}
61
62/// The workload-attached capture mechanism selected for a profiled serving
63/// topology ([[RFC-0004:C-WORKLOAD-PROFILING]]).
64#[derive(
65    Clone, Copy, Debug, Default, Deserialize, Eq, JsonSchema, Ord, PartialEq, PartialOrd, Serialize,
66)]
67#[serde(rename_all = "snake_case")]
68pub enum CaptureMechanism {
69    /// InferLab wraps each target process with a managed Nsight Systems
70    /// collection.
71    #[default]
72    ManagedCollection,
73    /// The serving framework's internal profiler writes per-rank trace
74    /// artifacts under InferLab-directed storage.
75    EngineTrace,
76}
77
78/// The synthetic acceptance declaration carried to the selected integration
79/// ([[RFC-0003:C-SERVE-SYNTHETIC-ACCEPTANCE]]): exactly one of the explicit
80/// acceptance length or the digest-verified golden curve with its lookup
81/// coordinates. The draft count is not declared; the integration determines
82/// it from the operator's speculative configuration ([[ADR-0043]]).
83#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
84#[serde(rename_all = "snake_case", deny_unknown_fields)]
85pub enum SyntheticAcceptanceInput {
86    /// The explicit form: the declared acceptance length, overlaid as-is.
87    Explicit {
88        /// The declared mean acceptance length.
89        acceptance_length: f64,
90    },
91    /// The curve form: the digest-verified golden curve text with its digest
92    /// and lookup coordinates; the integration resolves the effective
93    /// acceptance length from it.
94    Curve(SyntheticAcceptanceCurveInput),
95}
96
97/// The curve form's wire payload ([[RFC-0003:C-SERVE-SYNTHETIC-ACCEPTANCE]]).
98#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
99#[serde(deny_unknown_fields)]
100pub struct SyntheticAcceptanceCurveInput {
101    pub model_key: String,
102    /// The effective thinking mode: the declared mode, or `thinking_on` when
103    /// the declaration omits it and the model entry uses the thinking-mode
104    /// shape. Absent when the model entry is a flat list (no mode applies).
105    #[serde(default, skip_serializing_if = "Option::is_none")]
106    pub thinking_mode: Option<String>,
107    /// The complete digest-verified curve file text.
108    pub text: String,
109    /// The SHA-256 digest of the curve file bytes, verified by the control
110    /// plane against the declaration.
111    pub sha256: String,
112}
113
114/// The integration's resolved synthetic acceptance outcome, returned in the
115/// `plan_serve` response whenever the request carried the declaration
116/// ([[RFC-0006:C-INTEGRATIONS]]).
117#[derive(Clone, Copy, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
118#[serde(deny_unknown_fields)]
119pub struct SyntheticAcceptanceOutcome {
120    /// The effective acceptance length the integration resolved and overlaid.
121    pub acceptance_length: f64,
122    /// The draft count the integration determined from the operator's
123    /// speculative configuration; present for the curve form, absent for the
124    /// explicit form.
125    #[serde(default, skip_serializing_if = "Option::is_none")]
126    pub draft_count: Option<u32>,
127}
128
129/// A concrete host/port endpoint the control plane allocated for a process.
130#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
131#[serde(deny_unknown_fields)]
132pub struct EndpointAssignment {
133    pub host: String,
134    pub port: u16,
135}
136
137/// The application protocol a workload endpoint speaks.
138#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
139#[serde(rename_all = "snake_case")]
140pub enum EndpointProtocol {
141    /// HTTP.
142    Http,
143}
144
145/// The HTTP method of an action specification.
146#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
147#[serde(rename_all = "snake_case")]
148pub enum HttpMethod {
149    /// HTTP POST.
150    Post,
151}
152
153// Serve-adapter request and response envelope.
154
155/// The one JSON request an integration reads from stdin, tagged by the
156/// requested operation.
157#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
158#[serde(tag = "operation", rename_all = "snake_case", deny_unknown_fields)]
159pub enum AdapterRequest {
160    /// Plan a serve topology: declare roles, per-replica requirements, links,
161    /// and endpoint requirements from the requested shape.
162    PlanServe {
163        protocol_version: ProtocolVersion,
164        input: PlanServeInput,
165    },
166    /// Render final process invocations for a planned topology, given the
167    /// control plane's concrete process allocations.
168    RenderServe {
169        protocol_version: ProtocolVersion,
170        input: RenderServeInput,
171    },
172}
173
174impl AdapterRequest {
175    /// The protocol version carried by this request, regardless of operation.
176    #[must_use]
177    pub const fn protocol_version(&self) -> ProtocolVersion {
178        match self {
179            Self::PlanServe {
180                protocol_version, ..
181            }
182            | Self::RenderServe {
183                protocol_version, ..
184            } => *protocol_version,
185        }
186    }
187}
188
189/// The requested serve shape a `PlanServe` operation lowers into a topology.
190#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
191#[serde(deny_unknown_fields)]
192pub struct PlanServeInput {
193    pub model: ServeModelInput,
194    pub topology: ServeTopology,
195    /// The control-plane-owned state directory spelling,
196    /// workspace-root-relative, that the integration resolves operator-declared
197    /// relative paths against when reading them through the workspace
198    /// filesystem and when declaring the supplied render-input source paths
199    /// those reads produce.
200    pub state_dir: String,
201    #[serde(default, skip_serializing_if = "Option::is_none")]
202    pub gateway_backend: Option<String>,
203    #[serde(default, skip_serializing_if = "Option::is_none")]
204    pub pd_router_backend: Option<String>,
205    #[serde(default, skip_serializing_if = "Option::is_none")]
206    pub kv_transfer: Option<KvTransferMechanism>,
207    pub roles: Vec<ServeRoleInput>,
208    /// The effective capture mechanism when profiling is requested; absent
209    /// means no profiling ([[RFC-0004:C-WORKLOAD-PROFILING]]).
210    #[serde(default, skip_serializing_if = "Option::is_none")]
211    pub profiling: Option<CaptureMechanism>,
212    /// The synthetic acceptance declaration when the serve declaration
213    /// carries one ([[RFC-0003:C-SERVE-SYNTHETIC-ACCEPTANCE]]).
214    #[serde(default, skip_serializing_if = "Option::is_none")]
215    pub synthetic_acceptance: Option<SyntheticAcceptanceInput>,
216    /// The auxiliary model declaration when the serve declaration carries
217    /// one ([[RFC-0003:C-SERVE-AUXILIARY-MODELS]]).
218    #[serde(default, skip_serializing_if = "Vec::is_empty")]
219    pub auxiliary_models: Vec<AuxiliaryModelInput>,
220}
221
222/// The planned topology plus the control plane's concrete allocations that a
223/// `RenderServe` operation turns into final process invocations.
224#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
225#[serde(deny_unknown_fields)]
226pub struct RenderServeInput {
227    pub model: ServeModelInput,
228    pub topology: ServeTopology,
229    /// The control-plane-owned state directory spelling,
230    /// workspace-root-relative, that the integration resolves operator-declared
231    /// relative paths against when matching them to supplied render-input
232    /// source paths.
233    pub state_dir: String,
234    #[serde(default, skip_serializing_if = "Option::is_none")]
235    pub gateway_backend: Option<String>,
236    #[serde(default, skip_serializing_if = "Option::is_none")]
237    pub pd_router_backend: Option<String>,
238    #[serde(default, skip_serializing_if = "Option::is_none")]
239    pub kv_transfer: Option<KvTransferMechanism>,
240    pub allocations: Vec<ServeProcessAllocation>,
241    /// The effective capture mechanism when profiling is requested; absent
242    /// means no profiling ([[RFC-0004:C-WORKLOAD-PROFILING]]).
243    #[serde(default, skip_serializing_if = "Option::is_none")]
244    pub profiling: Option<CaptureMechanism>,
245    /// The synthetic acceptance declaration when the serve declaration
246    /// carries one ([[RFC-0003:C-SERVE-SYNTHETIC-ACCEPTANCE]]).
247    #[serde(default, skip_serializing_if = "Option::is_none")]
248    pub synthetic_acceptance: Option<SyntheticAcceptanceInput>,
249    /// The auxiliary model declaration when the serve declaration carries
250    /// one ([[RFC-0003:C-SERVE-AUXILIARY-MODELS]]).
251    #[serde(default, skip_serializing_if = "Vec::is_empty")]
252    pub auxiliary_models: Vec<AuxiliaryModelInput>,
253}
254
255/// The one JSON response an integration writes to stdout, tagged by outcome.
256#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
257#[serde(tag = "status", rename_all = "snake_case", deny_unknown_fields)]
258pub enum AdapterResponse {
259    /// The operation succeeded and carries its result.
260    Ok {
261        protocol_version: ProtocolVersion,
262        result: Box<AdapterResult>,
263    },
264    /// The operation was rejected with a structured error.
265    Error {
266        protocol_version: ProtocolVersion,
267        error: AdapterError,
268    },
269}
270
271impl AdapterResponse {
272    /// The protocol version carried by this response, regardless of outcome.
273    #[must_use]
274    pub const fn protocol_version(&self) -> ProtocolVersion {
275        match self {
276            Self::Ok {
277                protocol_version, ..
278            }
279            | Self::Error {
280                protocol_version, ..
281            } => *protocol_version,
282        }
283    }
284}
285
286/// The successful result of an [`AdapterRequest`], tagged by the operation it
287/// answers.
288#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
289#[serde(tag = "operation", rename_all = "snake_case", deny_unknown_fields)]
290pub enum AdapterResult {
291    /// The planned topology from a `PlanServe` request.
292    PlanServe { output: Box<PlanServeResult> },
293    /// The rendered process invocations from a `RenderServe` request.
294    RenderServe { output: Box<RenderServeResult> },
295}
296
297/// A structured rejection an integration returns in an [`AdapterResponse::Error`].
298#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
299#[serde(deny_unknown_fields)]
300pub struct AdapterError {
301    pub code: AdapterErrorCode,
302    pub message: String,
303}
304
305/// Machine-readable failure category an adapter reports in an [`AdapterError`].
306#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
307#[serde(rename_all = "snake_case")]
308pub enum AdapterErrorCode {
309    /// The request was malformed or missing required fields.
310    InvalidRequest,
311    /// The request's protocol version is not accepted.
312    UnsupportedProtocolVersion,
313    /// A framework setting was unknown or invalid.
314    InvalidSettings,
315    /// An unexpected internal failure occurred in the integration.
316    Internal,
317    /// The requested operation is not supported by this integration.
318    UnsupportedOperation,
319}
320
321/// The integration's identity recorded on its results: adapter id, adapter
322/// version, and the framework it lowers to.
323#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
324#[serde(deny_unknown_fields)]
325pub struct IntegrationIdentity {
326    pub adapter_id: String,
327    pub adapter_version: String,
328    pub framework: String,
329    pub framework_version: String,
330}
331
332// Serve-adapter plan and render shapes.
333
334/// The serving deployment topology.
335#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
336#[serde(rename_all = "snake_case")]
337pub enum ServeTopology {
338    /// One aggregated serving role.
339    Single,
340    /// Disaggregated prefill and decode roles.
341    PrefillDecode,
342}
343
344/// The logical role a replica plays in a serve topology.
345#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
346#[serde(rename_all = "snake_case")]
347pub enum ServeRoleKind {
348    /// Aggregated serving role of a single topology.
349    Serve,
350    /// Prefill role of a disaggregated topology.
351    Prefill,
352    /// Decode role of a disaggregated topology.
353    Decode,
354}
355
356impl ServeRoleKind {
357    /// The role id as spelled on the wire, projected for control-plane
358    /// surfaces that name roles. Kept exhaustive so a future variant forces
359    /// this projection to follow.
360    #[must_use]
361    pub const fn as_str(self) -> &'static str {
362        match self {
363            Self::Serve => "serve",
364            Self::Prefill => "prefill",
365            Self::Decode => "decode",
366        }
367    }
368}
369
370/// The KV-transfer mechanism connecting prefill and decode.
371#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
372#[serde(rename_all = "snake_case")]
373pub enum KvTransferMechanism {
374    /// Mooncake KV-cache transfer.
375    Mooncake,
376    /// NIXL KV-cache transfer.
377    Nixl,
378}
379
380/// Framework-neutral component-aware parallelism ([[RFC-0003:C-SERVE-PARALLELISM]]).
381/// Every component is optional so an operator can override one without
382/// repeating the rest; omitted components are filled by the integration into a
383/// complete effective shape.
384#[derive(Clone, Debug, Default, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
385#[serde(default, deny_unknown_fields)]
386pub struct Parallelism {
387    /// Outer deployment parallelism shared by attention and experts.
388    #[serde(skip_serializing_if = "Option::is_none")]
389    pub outer: Option<ParallelismOuter>,
390    /// Attention-block parallelism.
391    #[serde(skip_serializing_if = "Option::is_none")]
392    pub attention: Option<ParallelismAttention>,
393    /// MoE expert parallelism.
394    #[serde(skip_serializing_if = "Option::is_none")]
395    pub experts: Option<ParallelismExperts>,
396}
397
398impl Parallelism {
399    /// The declared parallelism dimensions as dotted-name/value pairs — the
400    /// single field projection consumed by both the load-time and the
401    /// resolution-time validators, so a new axis cannot silently escape one
402    /// of them ([[RFC-0003:C-SERVE-PARALLELISM]]).
403    pub fn field_values(&self) -> [(&'static str, Option<u32>); 9] {
404        [
405            (
406                "outer.tensor_parallel_size",
407                self.outer
408                    .as_ref()
409                    .and_then(|value| value.tensor_parallel_size),
410            ),
411            (
412                "outer.pipeline_parallel_size",
413                self.outer
414                    .as_ref()
415                    .and_then(|value| value.pipeline_parallel_size),
416            ),
417            (
418                "attention.tensor_parallel_size",
419                self.attention
420                    .as_ref()
421                    .and_then(|value| value.tensor_parallel_size),
422            ),
423            (
424                "attention.data_parallel_size",
425                self.attention
426                    .as_ref()
427                    .and_then(|value| value.data_parallel_size),
428            ),
429            (
430                "attention.context_parallel_size",
431                self.attention
432                    .as_ref()
433                    .and_then(|value| value.context_parallel_size),
434            ),
435            (
436                "experts.tensor_parallel_size",
437                self.experts
438                    .as_ref()
439                    .and_then(|value| value.tensor_parallel_size),
440            ),
441            (
442                "experts.data_parallel_size",
443                self.experts
444                    .as_ref()
445                    .and_then(|value| value.data_parallel_size),
446            ),
447            (
448                "experts.expert_parallel_size",
449                self.experts
450                    .as_ref()
451                    .and_then(|value| value.expert_parallel_size),
452            ),
453            (
454                "experts.dense_tensor_parallel_size",
455                self.experts
456                    .as_ref()
457                    .and_then(|value| value.dense_tensor_parallel_size),
458            ),
459        ]
460    }
461
462    /// Overlay the components present in `other` onto `self`, leaving
463    /// components `other` omits untouched (the per-component precedence merge).
464    pub fn merge_from(&mut self, other: &Self) {
465        if let Some(other) = &other.outer {
466            self.outer.get_or_insert_default().merge_from(other);
467        }
468        if let Some(other) = &other.attention {
469            self.attention.get_or_insert_default().merge_from(other);
470        }
471        if let Some(other) = &other.experts {
472            self.experts.get_or_insert_default().merge_from(other);
473        }
474    }
475}
476
477/// Outer deployment parallelism: tensor and pipeline degrees.
478#[derive(Clone, Debug, Default, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
479#[serde(default, deny_unknown_fields)]
480pub struct ParallelismOuter {
481    #[schemars(range(min = 1))]
482    #[serde(skip_serializing_if = "Option::is_none")]
483    pub tensor_parallel_size: Option<u32>,
484    #[schemars(range(min = 1))]
485    #[serde(skip_serializing_if = "Option::is_none")]
486    pub pipeline_parallel_size: Option<u32>,
487}
488
489impl ParallelismOuter {
490    fn merge_from(&mut self, other: &Self) {
491        if other.tensor_parallel_size.is_some() {
492            self.tensor_parallel_size = other.tensor_parallel_size;
493        }
494        if other.pipeline_parallel_size.is_some() {
495            self.pipeline_parallel_size = other.pipeline_parallel_size;
496        }
497    }
498}
499
500/// Attention-block parallelism: tensor, data, and context degrees.
501#[derive(Clone, Debug, Default, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
502#[serde(default, deny_unknown_fields)]
503pub struct ParallelismAttention {
504    #[schemars(range(min = 1))]
505    #[serde(skip_serializing_if = "Option::is_none")]
506    pub tensor_parallel_size: Option<u32>,
507    #[schemars(range(min = 1))]
508    #[serde(skip_serializing_if = "Option::is_none")]
509    pub data_parallel_size: Option<u32>,
510    #[schemars(range(min = 1))]
511    #[serde(skip_serializing_if = "Option::is_none")]
512    pub context_parallel_size: Option<u32>,
513}
514
515impl ParallelismAttention {
516    fn merge_from(&mut self, other: &Self) {
517        if other.tensor_parallel_size.is_some() {
518            self.tensor_parallel_size = other.tensor_parallel_size;
519        }
520        if other.data_parallel_size.is_some() {
521            self.data_parallel_size = other.data_parallel_size;
522        }
523        if other.context_parallel_size.is_some() {
524            self.context_parallel_size = other.context_parallel_size;
525        }
526    }
527}
528
529/// MoE expert parallelism: tensor, data, expert, and dense-tensor degrees.
530#[derive(Clone, Debug, Default, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
531#[serde(default, deny_unknown_fields)]
532pub struct ParallelismExperts {
533    #[schemars(range(min = 1))]
534    #[serde(skip_serializing_if = "Option::is_none")]
535    pub tensor_parallel_size: Option<u32>,
536    #[schemars(range(min = 1))]
537    #[serde(skip_serializing_if = "Option::is_none")]
538    pub data_parallel_size: Option<u32>,
539    #[schemars(range(min = 1))]
540    #[serde(skip_serializing_if = "Option::is_none")]
541    pub expert_parallel_size: Option<u32>,
542    #[schemars(range(min = 1))]
543    #[serde(skip_serializing_if = "Option::is_none")]
544    pub dense_tensor_parallel_size: Option<u32>,
545}
546
547impl ParallelismExperts {
548    fn merge_from(&mut self, other: &Self) {
549        if other.tensor_parallel_size.is_some() {
550            self.tensor_parallel_size = other.tensor_parallel_size;
551        }
552        if other.data_parallel_size.is_some() {
553            self.data_parallel_size = other.data_parallel_size;
554        }
555        if other.expert_parallel_size.is_some() {
556            self.expert_parallel_size = other.expert_parallel_size;
557        }
558        if other.dense_tensor_parallel_size.is_some() {
559            self.dense_tensor_parallel_size = other.dense_tensor_parallel_size;
560        }
561    }
562}
563
564/// The logical model supplied during serving planning and rendering.
565#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
566#[serde(deny_unknown_fields)]
567pub struct ServeModelInput {
568    pub id: String,
569    pub served_name: String,
570}
571
572/// The governed auxiliary weight artifact kinds a serve declaration may attach
573/// ([[RFC-0003:C-SERVE-AUXILIARY-MODELS]]).
574#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
575#[serde(rename_all = "kebab-case")]
576pub enum AuxiliaryModelKind {
577    /// Separate weights the operator's framework speculative decoding
578    /// configuration consumes as its draft.
579    DraftModel,
580}
581
582/// A logical auxiliary weight identity supplied during serving planning and
583/// rendering: its governed kind plus the referenced logical model
584/// ([[RFC-0003:C-SERVE-AUXILIARY-MODELS]]).
585#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
586#[serde(deny_unknown_fields)]
587pub struct AuxiliaryModelInput {
588    pub kind: AuxiliaryModelKind,
589    pub model: ServeModelInput,
590}
591
592/// A machine-resolved auxiliary weight locator on a model-rank allocation
593/// ([[RFC-0003:C-SERVE-AUXILIARY-MODELS]]).
594#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
595#[serde(deny_unknown_fields)]
596pub struct AuxiliaryModelLocator {
597    pub kind: AuxiliaryModelKind,
598    pub locator: String,
599}
600
601/// A requested serving role: its identity, kind, replica cardinality, and
602/// declared (not-yet-completed) parallelism and settings.
603#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
604#[serde(deny_unknown_fields)]
605pub struct ServeRoleInput {
606    pub id: String,
607    pub kind: ServeRoleKind,
608    pub replica_count: u32,
609    pub parallelism: Parallelism,
610    pub settings: BTreeMap<String, SettingValue>,
611}
612
613/// A role as the integration resolved it: preserved identity and cardinality
614/// with the complete effective settings and parallelism.
615#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
616#[serde(deny_unknown_fields)]
617pub struct ServeRoleResult {
618    pub id: String,
619    pub kind: ServeRoleKind,
620    pub declared_replica_count: u32,
621    pub effective_replica_count: u32,
622    pub effective_settings: BTreeMap<String, SettingValue>,
623    pub effective_parallelism: Parallelism,
624    /// The public endpoint capability declaration for a direct `single`
625    /// Engine. Gateway-backed shapes leave this absent and carry the
626    /// declaration on Gateway.
627    #[serde(default, skip_serializing_if = "Option::is_none")]
628    pub public_endpoint: Option<EndpointDeclaration>,
629    /// A prefix-cache reset invoked on the entry endpoint of every replica of
630    /// this role; a Gateway-backed server has a per-target reset capability
631    /// only when every model-serving role declares one
632    /// ([[RFC-0006:C-INTEGRATIONS]]).
633    #[serde(default, skip_serializing_if = "Option::is_none")]
634    pub replica_prefix_cache_reset: Option<HttpActionSpec>,
635    #[serde(default)]
636    pub render_inputs: Vec<RenderInputDeclaration>,
637}
638
639/// The lowered topology returned by a `PlanServe`: effective Engine roles,
640/// whole-replica requirements, logical links, and separate optional Gateway
641/// and P/D Router component plans.
642#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
643#[serde(deny_unknown_fields)]
644pub struct PlanServeResult {
645    pub integration: IntegrationIdentity,
646    /// The resolved synthetic acceptance outcome; required in the accepted
647    /// plan whenever the request carried the declaration, and forbidden
648    /// otherwise ([[RFC-0003:C-SERVE-SYNTHETIC-ACCEPTANCE]]).
649    #[serde(default, skip_serializing_if = "Option::is_none")]
650    pub synthetic_acceptance: Option<SyntheticAcceptanceOutcome>,
651    pub roles: Vec<ServeRoleResult>,
652    pub replicas: Vec<ServeReplicaRequirement>,
653    pub links: Vec<ServeRoleLink>,
654    #[serde(default, skip_serializing_if = "Option::is_none")]
655    pub gateway: Option<GatewayPlan>,
656    #[serde(default, skip_serializing_if = "Option::is_none")]
657    pub pd_router: Option<PdRouterPlan>,
658}
659
660/// A whole-replica resource and readiness requirement the integration declares
661/// without choosing placement, ranks, or concrete endpoints.
662#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
663#[serde(deny_unknown_fields)]
664pub struct ServeReplicaRequirement {
665    pub id: String,
666    pub role_id: String,
667    pub replica_index: u32,
668    pub device_count: u32,
669    pub ports: Vec<String>,
670    pub primary_ports: Vec<String>,
671    pub primary_readiness: ReadinessProbe,
672    pub worker_readiness: ReadinessProbe,
673    #[serde(default, skip_serializing_if = "Option::is_none")]
674    pub capture_target: Option<CaptureTargetRequirement>,
675}
676
677/// A directed link between serve roles the integration declares as part of the
678/// topology.
679#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
680#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
681pub enum ServeRoleLink {
682    /// The source role routes requests to the target roles.
683    RequestRouting {
684        source: String,
685        targets: Vec<String>,
686    },
687    /// KV cache is transferred from source to target over `mechanism`.
688    KvTransfer {
689        source: String,
690        target: String,
691        mechanism: KvTransferMechanism,
692    },
693    /// The source discovers the target through a bootstrap port.
694    Bootstrap {
695        source: String,
696        target: String,
697        port: String,
698    },
699    /// The source and target exchange out-of-band data over a side-channel port.
700    SideChannel {
701        source: String,
702        target: String,
703        port: String,
704    },
705}
706
707/// One workspace-authored UTF-8 source file an integration declares during
708/// planning for the control plane to supply during final rendering.
709#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
710#[serde(deny_unknown_fields)]
711pub struct RenderInputDeclaration {
712    pub source_path: String,
713}
714
715/// The original declared path plus the exact UTF-8 contents and digest the
716/// control plane supplies to an integration during final rendering.
717#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
718#[serde(deny_unknown_fields)]
719pub struct SuppliedRenderInput {
720    pub source_path: String,
721    pub text: String,
722    pub sha256: String,
723}
724
725/// The plan-response endpoint capability declaration
726/// ([[RFC-0006:C-OPENAI-ENDPOINT-CONTRACT]]): the protocol plus the
727/// integration-owned capability members (server metrics, prefix-cache
728/// actions, and the cache-read representation). The control plane owns the
729/// route paths, so this declaration carries no path values and rejects them
730/// as unknown fields.
731#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
732#[serde(deny_unknown_fields)]
733pub struct EndpointDeclaration {
734    pub protocol: EndpointProtocol,
735    #[serde(default, skip_serializing_if = "Option::is_none")]
736    pub server_metrics: Option<ServerMetricsEndpointRequirement>,
737    #[serde(default, skip_serializing_if = "Option::is_none")]
738    pub prefix_cache_reset: Option<HttpActionSpec>,
739    /// A Gateway-backend conditioning fan-out action: the frontend routes one
740    /// conditioning request per prefill replica and attention data-parallel
741    /// rank ([[RFC-0004:C-BENCH-CACHE-STATE]]).
742    #[serde(default, skip_serializing_if = "Option::is_none")]
743    pub prefix_cache_conditioning: Option<HttpActionSpec>,
744    #[serde(default, skip_serializing_if = "Option::is_none")]
745    pub prompt_cache_read_zero_representation: Option<PromptCacheReadZeroRepresentation>,
746}
747
748/// The resolved workload endpoint requirement: the accepted declaration plus
749/// the named OpenAI paths. The control plane fills the pinned
750/// `/v1/completions` and `/v1/chat/completions` paths at plan acceptance
751/// ([[RFC-0006:C-OPENAI-ENDPOINT-CONTRACT]]); resolved evidence and client
752/// inputs preserve both strings exactly.
753#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
754#[serde(deny_unknown_fields)]
755pub struct EndpointRequirement {
756    pub protocol: EndpointProtocol,
757    pub completions_path: String,
758    pub chat_completions_path: String,
759    #[serde(default, skip_serializing_if = "Option::is_none")]
760    pub server_metrics: Option<ServerMetricsEndpointRequirement>,
761    #[serde(default, skip_serializing_if = "Option::is_none")]
762    pub prefix_cache_reset: Option<HttpActionSpec>,
763    /// A Gateway-backend conditioning fan-out action: the frontend routes one
764    /// conditioning request per prefill replica and attention data-parallel
765    /// rank ([[RFC-0004:C-BENCH-CACHE-STATE]]).
766    #[serde(default, skip_serializing_if = "Option::is_none")]
767    pub prefix_cache_conditioning: Option<HttpActionSpec>,
768    #[serde(default, skip_serializing_if = "Option::is_none")]
769    pub prompt_cache_read_zero_representation: Option<PromptCacheReadZeroRepresentation>,
770}
771
772/// An integration-owned logical server-metrics endpoint.
773#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
774#[serde(deny_unknown_fields)]
775pub struct ServerMetricsEndpointRequirement {
776    pub path: String,
777    #[serde(default, skip_serializing_if = "Option::is_none")]
778    pub port: Option<String>,
779}
780
781/// How the control plane decides a process is ready.
782#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
783#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
784pub enum ReadinessProbe {
785    /// Ready when an HTTP GET of `path` succeeds.
786    Http { path: String },
787    /// Ready when the public endpoint succeeds and its HTTP target registry
788    /// contains every control-plane-derived serving target.
789    HttpTargetRegistry(Box<HttpTargetRegistryReadiness>),
790    /// Ready when a JSON registry lists every rank-zero model-serving process
791    /// under its role at its allocated address and, when declared, a model
792    /// list names the served model ([[RFC-0006:C-INTEGRATIONS]]).
793    RegistryMembership(Box<RegistryMembershipReadiness>),
794    /// Ready as soon as the process is alive.
795    ProcessAlive,
796}
797
798/// A framework-neutral registry-membership readiness contract. Every pointer
799/// is an RFC 6901 JSON Pointer; entry pointers are evaluated against one
800/// registry entry.
801#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
802#[serde(deny_unknown_fields)]
803pub struct RegistryMembershipReadiness {
804    /// Absolute HTTP path of the registry on the owning process's endpoint.
805    pub registry_path: String,
806    /// Logical port of each model-serving process whose allocated `host:port`
807    /// the registry entries carry.
808    pub target_port: String,
809    /// Pointer from the registry document to the entry array.
810    pub entries_pointer: String,
811    /// Entries whose value at the pointer differs from the expected value are
812    /// ignored.
813    #[serde(default, skip_serializing_if = "Option::is_none")]
814    pub entry_filter: Option<JsonValueMatch>,
815    pub role_pointer: String,
816    pub role_values: RegistryRoleValues,
817    pub address_pointer: String,
818    #[serde(default, skip_serializing_if = "Option::is_none")]
819    pub model_list: Option<ModelListRequirement>,
820}
821
822/// The registry role value expected for each model-serving role kind present
823/// in the topology.
824#[derive(Clone, Debug, Default, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
825#[serde(deny_unknown_fields)]
826pub struct RegistryRoleValues {
827    #[serde(default, skip_serializing_if = "Option::is_none")]
828    pub serve: Option<String>,
829    #[serde(default, skip_serializing_if = "Option::is_none")]
830    pub prefill: Option<String>,
831    #[serde(default, skip_serializing_if = "Option::is_none")]
832    pub decode: Option<String>,
833}
834
835/// A served-model list that must name the served model.
836#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
837#[serde(deny_unknown_fields)]
838pub struct ModelListRequirement {
839    /// Absolute HTTP path of the list on the owning process's endpoint.
840    pub path: String,
841    /// Pointer from the list document to the model array.
842    pub models_pointer: String,
843    /// Pointer from one model element to its name.
844    pub name_pointer: String,
845}
846
847/// A JSON Pointer and the string value expected at it.
848#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
849#[serde(deny_unknown_fields)]
850pub struct JsonValueMatch {
851    pub pointer: String,
852    pub value: String,
853}
854
855/// The JSON scalar an HTTP action success predicate expects: a string, or
856/// from protocol version 13 a boolean ([[RFC-0006:C-INTEGRATIONS]]).
857#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
858#[serde(untagged)]
859pub enum JsonScalar {
860    Boolean(bool),
861    String(String),
862}
863
864impl JsonScalar {
865    /// Whether an observed JSON value equals this scalar with the same JSON
866    /// type: the string `"true"` never matches the boolean `true`.
867    #[must_use]
868    pub fn matches(&self, observed: &serde_json::Value) -> bool {
869        match (self, observed) {
870            (Self::Boolean(expected), serde_json::Value::Bool(value)) => expected == value,
871            (Self::String(expected), serde_json::Value::String(value)) => expected == value,
872            _ => false,
873        }
874    }
875}
876
877impl std::fmt::Display for JsonScalar {
878    /// The scalar in its JSON spelling.
879    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
880        match self {
881            Self::Boolean(value) => write!(formatter, "{value}"),
882            Self::String(value) => write!(formatter, "{}", serde_json::Value::from(value.as_str())),
883        }
884    }
885}
886
887/// A JSON Pointer and the scalar an HTTP action success predicate expects at
888/// it.
889#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
890#[serde(deny_unknown_fields)]
891pub struct SuccessMatch {
892    pub pointer: String,
893    pub value: JsonScalar,
894}
895
896/// The integration-owned HTTP registry contract for target-aware readiness.
897#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
898#[serde(deny_unknown_fields)]
899pub struct HttpTargetRegistryReadiness {
900    pub target_scheme: TargetEndpointScheme,
901    pub readiness_path: String,
902    pub registry_path: String,
903    pub targets_field: String,
904    pub target_url_field: String,
905    pub target_role_field: String,
906    pub target_healthy_field: String,
907    pub target_bootstrap_port_field: String,
908    pub prefill_role_value: String,
909    pub decode_role_value: String,
910    pub prefill_bootstrap_port: String,
911}
912
913/// An HTTP action invoked against a logical serving endpoint.
914#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
915#[serde(deny_unknown_fields)]
916pub struct HttpActionSpec {
917    pub method: HttpMethod,
918    pub path: String,
919    /// When present, a 2xx response succeeds only if its JSON body holds the
920    /// expected value at the pointer ([[RFC-0006:C-INTEGRATIONS]]).
921    #[serde(default, skip_serializing_if = "Option::is_none")]
922    pub success: Option<SuccessMatch>,
923}
924
925// Capture window control ([[RFC-0004:C-WORKLOAD-PROFILING]]).
926
927/// Marks a replica as a profiling capture target and carries its window
928/// control ([[RFC-0004:C-WORKLOAD-PROFILING]]).
929#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
930#[serde(deny_unknown_fields)]
931pub struct CaptureTargetRequirement {
932    /// The capture mechanism this target declares; it must equal the
933    /// effective mechanism requested on the plan.
934    pub mechanism: CaptureMechanism,
935    pub window_control: CaptureWindowControlRequirement,
936}
937
938/// The logical workload endpoint and typed actions that open and close a
939/// capture window.
940#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
941#[serde(deny_unknown_fields)]
942pub struct CaptureWindowControlRequirement {
943    pub endpoint: CaptureWindowControlEndpoint,
944    pub start: CaptureWindowHttpActionSpec,
945    pub stop: CaptureWindowHttpActionSpec,
946}
947
948/// The logical workload endpoint exposing a capture target's window-control
949/// actions.
950#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
951#[serde(rename_all = "snake_case")]
952pub enum CaptureWindowControlEndpoint {
953    /// The entry process of the capture target's Engine replica.
954    ReplicaEntry,
955    /// The separately planned Gateway process.
956    Gateway,
957}
958
959/// A capture-window HTTP action invoked against a logical serving endpoint.
960/// The integration owns any framework-specific JSON body; the control plane
961/// owns execution and evidence.
962#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
963#[serde(deny_unknown_fields)]
964pub struct CaptureWindowHttpActionSpec {
965    pub method: HttpMethod,
966    pub path: String,
967    #[serde(default, skip_serializing_if = "Option::is_none")]
968    pub body: Option<BTreeMap<String, SettingValue>>,
969    /// When present, a 2xx response succeeds only if its JSON body holds the
970    /// expected value at the pointer ([[RFC-0006:C-INTEGRATIONS]]).
971    #[serde(default, skip_serializing_if = "Option::is_none")]
972    pub success: Option<SuccessMatch>,
973}
974
975/// One concrete process allocation supplied to `RenderServe`. Model-rank and
976/// process-only frontend identities are distinct so a frontend cannot acquire
977/// model coordinates or a model locator by construction.
978#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
979#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
980pub enum ServeProcessAllocation {
981    ModelRank {
982        process: String,
983        role: String,
984        role_kind: ServeRoleKind,
985        replica: u32,
986        rank: u32,
987        rank_count: u32,
988        machine: String,
989        devices: Vec<u32>,
990        model_locator: String,
991        /// The machine-resolved auxiliary weight locators for each declared
992        /// kind ([[RFC-0003:C-SERVE-AUXILIARY-MODELS]]).
993        #[serde(default, skip_serializing_if = "Vec::is_empty")]
994        auxiliary_model_locators: Vec<AuxiliaryModelLocator>,
995        #[serde(default, skip_serializing_if = "Option::is_none")]
996        endpoint: Option<EndpointAssignment>,
997        ports: BTreeMap<String, EndpointAssignment>,
998        cache: String,
999        /// The control-plane-assigned persistent trace directory; present
1000        /// only when this rank belongs to an engine-trace capture target
1001        /// ([[RFC-0004:C-WORKLOAD-PROFILING]]).
1002        #[serde(default, skip_serializing_if = "Option::is_none")]
1003        capture_storage: Option<String>,
1004        launch: AllocationLaunch,
1005        effective_settings: BTreeMap<String, SettingValue>,
1006        effective_parallelism: Parallelism,
1007        #[serde(default)]
1008        links: Vec<ServeRoleLink>,
1009        #[serde(default)]
1010        dependencies: Vec<String>,
1011        /// The discovery process this process registers with, when the
1012        /// topology has one ([[RFC-0003:C-SERVE-TOPOLOGY]]).
1013        #[serde(default, skip_serializing_if = "Option::is_none")]
1014        discovery: Option<String>,
1015        #[serde(default)]
1016        render_inputs: Vec<SuppliedRenderInput>,
1017    },
1018    Frontend {
1019        process: String,
1020        process_role: FrontendProcessRole,
1021        components: FrontendComponents,
1022        machine: String,
1023        devices: Vec<u32>,
1024        endpoint: EndpointAssignment,
1025        ports: BTreeMap<String, EndpointAssignment>,
1026        cache: String,
1027        launch: AllocationLaunch,
1028        gateway: Box<GatewayPlan>,
1029        #[serde(default, skip_serializing_if = "Option::is_none")]
1030        pd_router: Option<Box<PdRouterPlan>>,
1031        #[serde(default)]
1032        links: Vec<ServeRoleLink>,
1033        #[serde(default)]
1034        dependencies: Vec<String>,
1035        /// The discovery process this process registers with, when the
1036        /// topology has one ([[RFC-0003:C-SERVE-TOPOLOGY]]).
1037        #[serde(default, skip_serializing_if = "Option::is_none")]
1038        discovery: Option<String>,
1039        #[serde(default)]
1040        render_inputs: Vec<SuppliedRenderInput>,
1041    },
1042    /// The discovery service a Gateway result requires
1043    /// ([[RFC-0003:C-SERVE-TOPOLOGY]]): process-only, zero devices, placed on
1044    /// the Gateway's machine, with an internal (not public) endpoint.
1045    Discovery {
1046        process: String,
1047        process_role: DiscoveryProcessRole,
1048        components: DiscoveryBinding,
1049        machine: String,
1050        devices: Vec<u32>,
1051        endpoint: EndpointAssignment,
1052        ports: BTreeMap<String, EndpointAssignment>,
1053        cache: String,
1054        /// The control-plane-assigned empty data directory under the
1055        /// machine's runtime cache.
1056        data_directory: String,
1057        launch: AllocationLaunch,
1058        discovery: Box<DiscoveryRequirement>,
1059        #[serde(default)]
1060        render_inputs: Vec<SuppliedRenderInput>,
1061    },
1062}
1063
1064/// The machine-local launch channel selected by the control plane.
1065#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1066#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
1067pub enum AllocationLaunch {
1068    Local,
1069    Ssh { target: String },
1070}
1071
1072/// The final process invocations returned by a `RenderServe`, one per supplied
1073/// allocation.
1074#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1075#[serde(deny_unknown_fields)]
1076pub struct RenderServeResult {
1077    pub integration: IntegrationIdentity,
1078    pub processes: Vec<RenderedServeProcess>,
1079}
1080
1081/// A rendered process bound to the model-rank or frontend allocation identity
1082/// it was produced for.
1083#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1084#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
1085pub enum RenderedServeProcess {
1086    ModelRank {
1087        process: String,
1088        role: String,
1089        replica: u32,
1090        rank: u32,
1091        rank_count: u32,
1092        launch_files: Vec<LaunchFileDeclaration>,
1093        command: ProcessSpec,
1094    },
1095    Frontend {
1096        process: String,
1097        process_role: FrontendProcessRole,
1098        components: FrontendComponents,
1099        launch_files: Vec<LaunchFileDeclaration>,
1100        command: ProcessSpec,
1101    },
1102    Discovery {
1103        process: String,
1104        process_role: DiscoveryProcessRole,
1105        components: DiscoveryBinding,
1106        launch_files: Vec<LaunchFileDeclaration>,
1107        command: ProcessSpec,
1108    },
1109}
1110
1111/// One immutable text input a rendered process requires before it can launch.
1112#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1113#[serde(deny_unknown_fields)]
1114pub struct LaunchFileDeclaration {
1115    pub relative_path: String,
1116    pub text: String,
1117    pub sha256: String,
1118}
1119
1120/// A launchable process: its argument vector and environment.
1121#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1122#[serde(deny_unknown_fields)]
1123pub struct ProcessSpec {
1124    pub argv: Vec<String>,
1125    pub env: BTreeMap<String, String>,
1126}
1127
1128// Frontend components.
1129
1130/// Which bounded implementation renders an accepted concrete frontend
1131/// allocation. This is a lowering boundary only; the control plane always
1132/// owns placement, lifecycle, cleanup, endpoints, and records.
1133#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1134#[serde(rename_all = "snake_case")]
1135pub enum RenderSource {
1136    ControlPlane,
1137    Integration,
1138}
1139
1140/// The one canonical process role available to a frontend.
1141#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1142#[serde(rename_all = "snake_case")]
1143pub enum FrontendProcessRole {
1144    Gateway,
1145}
1146
1147/// The canonical process role of the discovery process.
1148#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1149#[serde(rename_all = "snake_case")]
1150pub enum DiscoveryProcessRole {
1151    Discovery,
1152}
1153
1154/// The only member of the closed discovery component binding.
1155#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1156#[serde(rename_all = "snake_case")]
1157pub enum DiscoveryComponent {
1158    Discovery,
1159}
1160
1161/// The closed `["discovery"]` component binding.
1162#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1163#[serde(transparent)]
1164pub struct DiscoveryBinding(pub [DiscoveryComponent; 1]);
1165
1166impl DiscoveryBinding {
1167    #[must_use]
1168    pub const fn discovery() -> Self {
1169        Self([DiscoveryComponent::Discovery])
1170    }
1171}
1172
1173/// A discovery service a Gateway backend requires: its logical ports beyond
1174/// its endpoint, readiness, and render inputs ([[RFC-0006:C-INTEGRATIONS]]).
1175#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1176#[serde(deny_unknown_fields)]
1177pub struct DiscoveryRequirement {
1178    #[serde(default)]
1179    pub ports: Vec<String>,
1180    pub readiness: ReadinessProbe,
1181    #[serde(default)]
1182    pub render_inputs: Vec<RenderInputDeclaration>,
1183}
1184
1185/// The fixed co-rendering requirement shared by compatible frontend plans.
1186#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1187#[serde(deny_unknown_fields)]
1188pub struct FrontendCoRendering {
1189    pub process_role: FrontendProcessRole,
1190}
1191
1192/// The literal first member of every closed frontend component binding.
1193#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1194#[serde(rename_all = "snake_case")]
1195pub enum FrontendGatewayComponent {
1196    Gateway,
1197}
1198
1199/// The literal second member of the fused P/D frontend component binding.
1200#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1201#[serde(rename_all = "snake_case")]
1202pub enum FrontendPdRouterComponent {
1203    PdRouter,
1204}
1205
1206/// The stable schema branch for a Gateway-only frontend binding.
1207#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1208#[serde(transparent)]
1209pub struct GatewayFrontendBinding(pub [FrontendGatewayComponent; 1]);
1210
1211/// The stable schema branch for a fused Gateway and P/D Router binding.
1212#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1213#[serde(transparent)]
1214pub struct GatewayPdRouterFrontendBinding(
1215    pub (FrontendGatewayComponent, FrontendPdRouterComponent),
1216);
1217
1218/// The only two frontend bindings the adapter protocol accepts. Tuple representation
1219/// deliberately serializes as the closed ordered arrays `["gateway"]` and
1220/// `["gateway", "pd_router"]` rather than as an open component list.
1221#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1222#[serde(untagged)]
1223pub enum FrontendComponents {
1224    Gateway(GatewayFrontendBinding),
1225    GatewayPdRouter(GatewayPdRouterFrontendBinding),
1226}
1227
1228impl FrontendComponents {
1229    #[must_use]
1230    pub const fn gateway() -> Self {
1231        Self::Gateway(GatewayFrontendBinding([FrontendGatewayComponent::Gateway]))
1232    }
1233
1234    #[must_use]
1235    pub const fn gateway_pd_router() -> Self {
1236        Self::GatewayPdRouter(GatewayPdRouterFrontendBinding((
1237            FrontendGatewayComponent::Gateway,
1238            FrontendPdRouterComponent::PdRouter,
1239        )))
1240    }
1241
1242    #[must_use]
1243    pub const fn includes_pd_router(&self) -> bool {
1244        matches!(self, Self::GatewayPdRouter(_))
1245    }
1246}
1247
1248/// The target a Gateway forwards accepted public requests to.
1249#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1250#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
1251pub enum GatewayTarget {
1252    /// A routed-single Gateway targets the sole Engine role entry point.
1253    Engine { role: String },
1254    /// A P/D Gateway hands requests to its co-rendered P/D Router component.
1255    PdRouter,
1256}
1257
1258/// The public frontend component plan returned independently from Engine and
1259/// P/D Router planning.
1260#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
1261#[serde(deny_unknown_fields)]
1262pub struct GatewayPlan {
1263    pub backend: String,
1264    pub implementation: String,
1265    pub implementation_version: String,
1266    pub effective_settings: BTreeMap<String, SettingValue>,
1267    pub endpoint: EndpointDeclaration,
1268    pub readiness: ReadinessProbe,
1269    #[serde(default)]
1270    pub ports: Vec<String>,
1271    pub targets: Vec<GatewayTarget>,
1272    #[serde(default)]
1273    pub render_inputs: Vec<RenderInputDeclaration>,
1274    pub render_source: RenderSource,
1275    pub co_rendering: FrontendCoRendering,
1276    /// The discovery service this Gateway backend requires; its presence
1277    /// derives one `discovery` process ([[RFC-0003:C-SERVE-TOPOLOGY]]).
1278    #[serde(default, skip_serializing_if = "Option::is_none")]
1279    pub discovery: Option<DiscoveryRequirement>,
1280}
1281
1282/// Independent policies for choosing prefill and decode targets.
1283#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1284#[serde(deny_unknown_fields)]
1285pub struct PdRoutingPolicies {
1286    pub prefill: String,
1287    pub decode: String,
1288}
1289
1290/// The currently demonstrated Gateway-to-P/D Router handoff is internal to
1291/// one fused frontend process.
1292#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1293#[serde(rename_all = "snake_case")]
1294pub enum FrontendHandoff {
1295    InProcess,
1296}
1297
1298/// The P/D orchestration component plan, kept separate even when the same
1299/// process and provider also realize Gateway.
1300#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
1301#[serde(deny_unknown_fields)]
1302pub struct PdRouterPlan {
1303    pub backend: String,
1304    pub implementation: String,
1305    pub implementation_version: String,
1306    pub effective_settings: BTreeMap<String, SettingValue>,
1307    pub policies: PdRoutingPolicies,
1308    pub prefill_role: String,
1309    pub decode_role: String,
1310    pub target_scheme: TargetEndpointScheme,
1311    #[serde(default)]
1312    pub ports: Vec<String>,
1313    pub readiness: ReadinessProbe,
1314    pub handoff: FrontendHandoff,
1315    #[serde(default)]
1316    pub render_inputs: Vec<RenderInputDeclaration>,
1317    pub render_source: RenderSource,
1318    pub co_rendering: FrontendCoRendering,
1319}
1320
1321/// The application protocol used to identify serving targets in a registry.
1322#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1323#[serde(rename_all = "snake_case")]
1324pub enum TargetEndpointScheme {
1325    /// HTTP serving endpoint.
1326    Http,
1327    /// gRPC serving endpoint.
1328    Grpc,
1329    /// A framework's own TCP request plane, such as Dynamo's.
1330    Tcp,
1331}
1332
1333// Measurement-client shared base.
1334
1335/// The model identity used by measurement clients. Unlike integration
1336/// planning, a benchmark client may need a controller-visible tokenizer
1337/// locator.
1338#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1339#[serde(deny_unknown_fields)]
1340pub struct MeasurementModelInput {
1341    pub locator: String,
1342    pub served_name: String,
1343}
1344
1345/// The public workload endpoint an Eval or Bench client connects to.
1346#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1347#[serde(deny_unknown_fields)]
1348pub struct ClientEndpointInput {
1349    pub protocol: EndpointProtocol,
1350    pub host: String,
1351    pub port: u16,
1352    pub completions_path: String,
1353    pub chat_completions_path: String,
1354    #[serde(default)]
1355    pub server_metrics: Option<ServerMetricsEndpointInput>,
1356    #[serde(default)]
1357    pub prompt_cache_read_zero_representation: Option<PromptCacheReadZeroRepresentation>,
1358}
1359
1360/// The resolved server-metrics endpoint supplied to a measurement client.
1361#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1362#[serde(deny_unknown_fields)]
1363pub struct ServerMetricsEndpointInput {
1364    pub path: String,
1365    #[serde(default)]
1366    pub port_name: Option<String>,
1367    pub url: String,
1368}
1369
1370/// How a backend with cache reporting enabled represents a zero-token cache read.
1371#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1372#[serde(rename_all = "snake_case")]
1373pub enum PromptCacheReadZeroRepresentation {
1374    Explicit,
1375    Omitted,
1376}
1377
1378/// The terminal outcome a measurement client reports.
1379#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1380#[serde(rename_all = "snake_case")]
1381pub enum ClientStatus {
1382    /// The client completed its measurement successfully.
1383    Succeeded,
1384    /// The client did not complete successfully.
1385    Failed,
1386}
1387
1388/// A raw output file a client produced, retained as workload evidence.
1389#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1390#[serde(deny_unknown_fields)]
1391pub struct RawArtifact {
1392    pub name: String,
1393    pub kind: String,
1394    pub path: PathBuf,
1395}
1396
1397// Eval client.
1398
1399/// The request the Eval measurement runtime passes to its client: the endpoint
1400/// to hit, the model, the eval definition, and where to write artifacts.
1401#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
1402#[serde(deny_unknown_fields)]
1403pub struct EvalClientRequest {
1404    pub protocol_version: ProtocolVersion,
1405    pub workspace_root: PathBuf,
1406    pub workspace_source_exclusions: Vec<PathBuf>,
1407    pub endpoint: ClientEndpointInput,
1408    pub model: MeasurementModelInput,
1409    pub definition: EvalDefinitionInput,
1410    #[serde(default)]
1411    pub prepared_source: Option<EvalPreparedSourceBinding>,
1412    /// Remaining control-plane case budget when the client is released.
1413    pub case_budget_seconds: f64,
1414    pub artifact_dir: PathBuf,
1415}
1416
1417/// The measurement an Eval client runs against the workload endpoint.
1418#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
1419#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
1420pub enum EvalDefinitionInput {
1421    /// A single-prompt liveness check.
1422    #[serde(rename = "openai_smoke")]
1423    OpenAiSmoke {
1424        prompt: String,
1425        max_tokens: u32,
1426        timeout_seconds: u64,
1427        /// The effective vision-mode selection
1428        /// ([[RFC-0004:C-MEASUREMENTS]]): a vision smoke routes to chat
1429        /// completions carrying the release-owned fixed test image.
1430        #[serde(default)]
1431        vision: bool,
1432    },
1433    /// An lm-eval task run with a pass threshold on the chosen metric.
1434    LmEval {
1435        task: Box<EvalTaskSourceInput>,
1436        /// The prompt rendering authority resolution selected.
1437        prompt: EvalPromptInput,
1438        /// The authority the definition declared, absent when it was defaulted.
1439        #[serde(default)]
1440        declared_prompt: Option<EvalPromptInput>,
1441        #[serde(default)]
1442        request_body: BTreeMap<String, SettingValue>,
1443        limit: Option<u32>,
1444        few_shot: Option<u32>,
1445        /// The seed the definition declared, absent when it omitted one; a
1446        /// single trial with no declared seed does not invent one.
1447        seed: Option<u64>,
1448        /// The effective base seed of the trial schedule, resolved by the
1449        /// control plane: the declared seed, or the shared fallback when the
1450        /// definition declared none ([[RFC-0004:C-LM-EVAL]]).
1451        base_seed: u64,
1452        trials: u32,
1453        max_tokens: Option<u32>,
1454        concurrency: Option<u32>,
1455        metric: String,
1456        #[serde(default)]
1457        metric_filter: Option<String>,
1458        threshold: f64,
1459        timeout_seconds: u64,
1460    },
1461}
1462
1463/// The prompt rendering authority for a generative lm-eval definition.
1464///
1465/// The authority determines the protocol route and the release-pinned lm-eval
1466/// client; the runner rejects an authority the resolved task output type does
1467/// not permit.
1468#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1469#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
1470pub enum EvalPromptInput {
1471    /// Ordinary text on the completions path with no chat construction.
1472    Flat,
1473    /// Structured messages on the chat-completions path, rendered by the server.
1474    ServerChat,
1475}
1476
1477/// The resolved lm-eval task source consumed by the release-owned Eval runner.
1478#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1479#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
1480pub enum EvalTaskSourceInput {
1481    /// One individual task shipped by the pinned lm-eval runtime.
1482    BuiltIn { name: String },
1483    /// One task closure shipped and identity-bound by the Inferlab release.
1484    Bundled {
1485        name: String,
1486        task_identity: String,
1487        path: PathBuf,
1488        task_closure_sha256: String,
1489        task_definition_sha256: String,
1490        prompt_asset_sha256: String,
1491        dataset_asset_sha256: String,
1492        scorer_sha256: String,
1493    },
1494    /// One validated workspace-local lm-eval YAML file.
1495    WorkspaceYaml { path: PathBuf },
1496}
1497
1498/// The result an Eval client writes for the measurement runtime to consume.
1499#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
1500#[serde(deny_unknown_fields)]
1501pub struct EvalClientResult {
1502    /// Result envelope version; clients write `1`. The measurement runtime
1503    /// rejects an eval result whose version is not `1`.
1504    pub schema_version: u32,
1505    pub status: ClientStatus,
1506    pub metrics: BTreeMap<String, f64>,
1507    #[serde(default)]
1508    pub normalized_metrics: BTreeMap<String, EvalNormalizedMetric>,
1509    #[serde(default)]
1510    pub gate: Option<EvalMetricGate>,
1511    #[serde(default)]
1512    pub trial_summary: Option<EvalTrialSummary>,
1513    pub native_command: Vec<String>,
1514    #[serde(default)]
1515    pub native_exit_code: Option<i32>,
1516    #[serde(default)]
1517    pub native_timed_out: bool,
1518    pub raw_artifacts: Vec<RawArtifact>,
1519    pub failure_kind: Option<EvalFailureKind>,
1520    pub error: Option<String>,
1521}
1522
1523/// A typed Eval failure category preserved across the client boundary.
1524#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1525#[serde(rename_all = "snake_case")]
1526pub enum EvalFailureKind {
1527    TaskResolution,
1528    ProbeTokenizer,
1529    ProbeTransport,
1530    ProbeHttp,
1531    ProbeMalformedResponse,
1532    ProbeGeneratedOnlyLogprobs,
1533    ProbeTokenizerAlignment,
1534    MetricNormalization,
1535}
1536
1537/// The threshold comparison selected from lm-eval's scoring direction.
1538#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1539#[serde(rename_all = "snake_case")]
1540pub enum EvalMetricComparison {
1541    AtLeast,
1542    AtMost,
1543}
1544
1545/// The terminal conclusion of an lm-eval metric threshold gate.
1546#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1547#[serde(rename_all = "snake_case")]
1548pub enum EvalMetricGateConclusion {
1549    Passed,
1550    Failed,
1551}
1552
1553/// One finite lm-eval metric with its exact native provenance.
1554#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
1555#[serde(deny_unknown_fields)]
1556pub struct EvalNormalizedMetric {
1557    pub source_identity: String,
1558    pub metric: String,
1559    pub filter: Option<String>,
1560    pub native_metric_key: String,
1561    pub value: f64,
1562    pub higher_is_better: bool,
1563    /// The prompt rendering authority that produced this value. The same task
1564    /// scored under another authority is a different measurement, so the value
1565    /// never travels without it.
1566    pub prompt_authority: EvalPromptInput,
1567}
1568
1569/// The effective threshold comparison for the configured primary metric.
1570#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
1571#[serde(deny_unknown_fields)]
1572pub struct EvalMetricGate {
1573    pub metric: EvalNormalizedMetric,
1574    pub threshold: f64,
1575    pub comparison: EvalMetricComparison,
1576    pub conclusion: EvalMetricGateConclusion,
1577}
1578
1579/// Reconstructible aggregate counts for fixed outer Eval trials.
1580#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
1581#[serde(deny_unknown_fields)]
1582pub struct EvalTrialSummary {
1583    pub requested_trials: u32,
1584    pub issued_trials: u32,
1585    pub unissued_trials: u32,
1586    pub completed_trials: u32,
1587    pub request_failure_trials: u32,
1588    pub passed_trials: u32,
1589    pub pass_rate: Option<f64>,
1590    pub per_trial_metric: String,
1591    pub per_trial_filter: Option<String>,
1592    pub higher_is_better: bool,
1593}
1594
1595// Bench client.
1596
1597/// The request the Bench measurement runtime passes to its client: the
1598/// endpoint, model, bench definition, the load case to run, and the artifact
1599/// directory.
1600#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
1601#[serde(deny_unknown_fields)]
1602pub struct BenchClientRequest {
1603    pub protocol_version: ProtocolVersion,
1604    pub endpoint: ClientEndpointInput,
1605    pub model: MeasurementModelInput,
1606    pub definition: BenchDefinitionInput,
1607    #[serde(default)]
1608    pub population: Option<BenchPopulationInput>,
1609    pub case: BenchCaseInput,
1610    /// Remaining control-plane case budget when the client is released.
1611    pub case_budget_seconds: f64,
1612    pub artifact_dir: PathBuf,
1613}
1614
1615/// The workload shape a Bench client drives, shared across its load cases.
1616#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
1617#[serde(deny_unknown_fields)]
1618pub struct BenchDefinitionInput {
1619    #[serde(default)]
1620    pub request_source: Option<BenchRequestSourceInput>,
1621    #[serde(default)]
1622    pub session_source: Option<BenchSessionSourceInput>,
1623    #[serde(default)]
1624    pub agentic_source: Option<BenchAgenticSourceInput>,
1625    pub prompt: BenchPromptInput,
1626    #[serde(default)]
1627    pub server_metrics: bool,
1628    #[serde(default)]
1629    pub artifact_level: BenchArtifactLevelInput,
1630    pub seed: u64,
1631    #[serde(default)]
1632    pub request_body: BTreeMap<String, SettingValue>,
1633    #[serde(default)]
1634    pub request_slo: Option<BenchRequestSloInput>,
1635    pub timeout_seconds: u64,
1636    pub cache_start: BenchCacheStartInput,
1637}
1638
1639/// The artifact level a Bench case requests from the measurement runtime.
1640/// Omission resolves to `diagnostic`, which retains the full raw
1641/// request/response export; `performance` keeps only normalized per-request
1642/// records and the summary export ([[RFC-0004:C-BENCH-ARTIFACT-LEVEL]]).
1643#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1644#[serde(rename_all = "snake_case")]
1645pub enum BenchArtifactLevelInput {
1646    Performance,
1647    #[default]
1648    Diagnostic,
1649}
1650
1651#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1652#[serde(rename_all = "snake_case")]
1653pub enum BenchCacheStartInput {
1654    Uncontrolled,
1655    Cold,
1656    Primed,
1657}
1658
1659/// The frozen prompt representation, route, and rendering authority for a
1660/// serving Bench population.
1661#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
1662#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
1663pub enum BenchPromptInput {
1664    Flat {
1665        request_representation: BenchRequestRepresentationInput,
1666        route: BenchPromptRouteInput,
1667        rendering_authority: BenchRenderingAuthorityInput,
1668    },
1669    RenderedChat {
1670        #[serde(default)]
1671        chat_template: Option<String>,
1672        #[serde(default)]
1673        chat_template_kwargs: BTreeMap<String, SettingValue>,
1674        request_representation: BenchRequestRepresentationInput,
1675        route: BenchPromptRouteInput,
1676        rendering_authority: BenchRenderingAuthorityInput,
1677    },
1678    ServerChat {
1679        request_representation: BenchRequestRepresentationInput,
1680        route: BenchPromptRouteInput,
1681        rendering_authority: BenchRenderingAuthorityInput,
1682    },
1683}
1684
1685/// The request payload representation frozen by prompt resolution.
1686#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1687#[serde(rename_all = "snake_case")]
1688pub enum BenchRequestRepresentationInput {
1689    FlatPrompt,
1690    StructuredMessages,
1691}
1692
1693/// The named OpenAI-compatible endpoint family derived from prompt authority.
1694#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1695#[serde(rename_all = "snake_case")]
1696pub enum BenchPromptRouteInput {
1697    Completions,
1698    ChatCompletions,
1699}
1700
1701/// The owner that turns semantic prompt input into the final prompt tokens.
1702#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1703#[serde(rename_all = "snake_case")]
1704pub enum BenchRenderingAuthorityInput {
1705    LocalFlat,
1706    LocalTemplate,
1707    Server,
1708}
1709
1710/// One closed request origin lowered by Inferlab for the Bench runtime.
1711#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
1712#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
1713pub enum BenchRequestSourceInput {
1714    /// AIPerf generates exact token-shape prompts from the release-pinned
1715    /// synthetic generator.
1716    Random {
1717        input_tokens: BenchTokenSelectorInput,
1718        output_tokens: BenchTokenSelectorInput,
1719        #[serde(default)]
1720        prefix_sharing: Option<BenchPrefixSharingInput>,
1721        #[serde(default)]
1722        shared_system_content: Option<BenchSharedSystemContentInput>,
1723        #[serde(default)]
1724        corpus: Option<BenchCorpusInput>,
1725        /// The effective per-request image decoration
1726        /// ([[RFC-0004:C-BENCH-REQUEST-SOURCES]]); absent when the source
1727        /// declares no images.
1728        #[serde(default)]
1729        images: Option<BenchImagesInput>,
1730    },
1731    /// AIPerf samples exact token-shape pairs from one seeded categorical
1732    /// distribution.
1733    RandomMixture {
1734        shapes: Vec<BenchRandomShapeInput>,
1735        total_weight: u64,
1736        #[serde(default)]
1737        prefix_sharing: Option<BenchPrefixSharingInput>,
1738    },
1739    /// Inferlab materializes a release-catalog conversation snapshot before
1740    /// AIPerf starts.
1741    Dataset {
1742        dataset: String,
1743        #[serde(default)]
1744        profile: Option<String>,
1745        max_input_tokens: u32,
1746        output_tokens: Option<u32>,
1747        catalog: Box<BenchDatasetCatalogInput>,
1748    },
1749    /// Inferlab replays one workspace-local frozen population file unchanged;
1750    /// the file bytes are the sole population authority
1751    /// ([[RFC-0004:C-BENCH-REQUEST-SOURCES]]).
1752    Replay {
1753        /// Workspace-relative population path as declared. The preparation
1754        /// request's `source_path` carries its absolute resolution.
1755        path: String,
1756        #[serde(default)]
1757        expected_sha256: Option<String>,
1758        #[serde(default)]
1759        prefix_sharing: Option<BenchPrefixSharingInput>,
1760    },
1761}
1762
1763/// One fixed or bounded inclusive-uniform token-length selector.
1764#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1765#[serde(untagged)]
1766pub enum BenchTokenSelectorInput {
1767    Fixed(u32),
1768    InclusiveUniform(BenchInclusiveUniformInput),
1769}
1770
1771/// The one bounded distribution currently supported for a token selector.
1772#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1773#[serde(deny_unknown_fields)]
1774pub struct BenchInclusiveUniformInput {
1775    pub kind: BenchTokenDistributionKindInput,
1776    pub min: u32,
1777    pub max: u32,
1778}
1779
1780#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1781#[serde(rename_all = "snake_case")]
1782pub enum BenchTokenDistributionKindInput {
1783    InclusiveUniform,
1784}
1785
1786/// One declared exact final-prompt prefix geometry.
1787#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
1788#[serde(untagged)]
1789pub enum BenchPrefixSharingInput {
1790    Tokens { shared_prefix_tokens: u32 },
1791    Ratio { shared_prefix_ratio: f64 },
1792}
1793
1794/// One pre-template shared system-content declaration for server-rendered
1795/// synthetic chat.
1796#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
1797#[serde(untagged)]
1798pub enum BenchSharedSystemContentInput {
1799    Tokens { tokens: u32 },
1800    Ratio { ratio: f64 },
1801}
1802
1803/// One operator-supplied text corpus binding for the random request source;
1804/// entry content is drawn as exact token-length slices of the corpus token
1805/// stream ([[RFC-0004:C-BENCH-REQUEST-SOURCES]]).
1806#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1807#[serde(deny_unknown_fields)]
1808pub struct BenchCorpusInput {
1809    /// Workspace-relative corpus path as declared. The preparation request's
1810    /// `source_path` carries its absolute resolution.
1811    pub path: String,
1812    #[serde(default)]
1813    pub expected_sha256: Option<String>,
1814}
1815
1816/// The effective per-request image decoration of a random request source
1817/// ([[RFC-0004:C-BENCH-REQUEST-SOURCES]]): fixed pixel dimensions and the
1818/// per-request image count. Images attach at measurement-run time on the
1819/// chat-completions route and never enter the frozen population.
1820#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1821#[serde(deny_unknown_fields)]
1822pub struct BenchImagesInput {
1823    /// The fixed image width in pixels.
1824    pub width: u32,
1825    /// The fixed image height in pixels.
1826    pub height: u32,
1827    /// The effective number of images attached to each request.
1828    pub count: u32,
1829    /// The image supply; absent selects the measurement runtime's synthetic
1830    /// noise image supply.
1831    #[serde(default)]
1832    pub source: Option<BenchImageSourceInput>,
1833}
1834
1835/// One operator-supplied image directory binding for the random request
1836/// source's image decoration ([[RFC-0004:C-BENCH-REQUEST-SOURCES]]).
1837#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1838#[serde(deny_unknown_fields)]
1839pub struct BenchImageSourceInput {
1840    /// Workspace-relative directory path as declared.
1841    pub path: String,
1842    /// Absolute resolution of `path` against the workspace root.
1843    pub resolved_path: PathBuf,
1844    /// The declared digest binding over the release-owned recursive
1845    /// enumeration digest of the directory.
1846    #[serde(default)]
1847    pub expected_sha256: Option<String>,
1848    /// The effective source-image sampling policy.
1849    pub sampling: BenchImageSamplingInput,
1850}
1851
1852/// The closed source-image sampling vocabulary
1853/// ([[RFC-0004:C-BENCH-REQUEST-SOURCES]]).
1854#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1855#[serde(rename_all = "kebab-case")]
1856pub enum BenchImageSamplingInput {
1857    /// Draw each source image independently; repeats may occur immediately.
1858    RandomWithReplacement,
1859    /// Draw every source image once per shuffled cycle, reshuffling after
1860    /// exhaustion.
1861    ShuffleCycle,
1862    /// Walk source images in sorted load order and wrap after exhaustion.
1863    SequentialCycle,
1864}
1865
1866/// One exact ISL/OSL pair and its relative categorical sampling weight.
1867#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1868#[serde(deny_unknown_fields)]
1869pub struct BenchRandomShapeInput {
1870    pub input_tokens: u32,
1871    pub output_tokens: u32,
1872    pub weight: u32,
1873}
1874
1875/// Immutable release-catalog facts resolved by the Rust control plane.
1876#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1877#[serde(deny_unknown_fields)]
1878pub struct BenchDatasetCatalogInput {
1879    pub dataset: String,
1880    #[serde(default)]
1881    pub profile: Option<String>,
1882    pub source: String,
1883    pub upstream_identity: String,
1884    pub url: String,
1885    pub sha256: String,
1886    pub source_format: String,
1887    pub aiperf_format: String,
1888    #[serde(default)]
1889    pub configuration: Option<String>,
1890    #[serde(default)]
1891    pub split: Option<String>,
1892    #[serde(default)]
1893    pub filter: Option<BenchDatasetFilterInput>,
1894    pub license: String,
1895    pub cache_path: PathBuf,
1896    pub cache_state: BenchDatasetCacheState,
1897    pub materialization_identity: String,
1898    pub provides_output_targets: bool,
1899}
1900
1901#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1902#[serde(deny_unknown_fields)]
1903pub struct BenchDatasetFilterInput {
1904    pub field: String,
1905    pub value: String,
1906}
1907
1908/// Read-only cache state observed while resolving the Bench plan.
1909#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1910#[serde(rename_all = "snake_case")]
1911pub enum BenchDatasetCacheState {
1912    Missing,
1913    Present,
1914}
1915
1916/// One release-qualified population of dependent linear session templates.
1917#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
1918#[serde(deny_unknown_fields)]
1919pub struct BenchSessionSourceInput {
1920    pub dataset: String,
1921    #[serde(default)]
1922    pub profile: Option<String>,
1923    pub max_input_tokens: u32,
1924    pub output_tokens: Option<u32>,
1925    pub inter_turn_delay_scale: f64,
1926    pub max_inter_turn_delay_seconds: Option<f64>,
1927    pub catalog: Box<BenchSessionDatasetCatalogInput>,
1928}
1929
1930/// Immutable release-catalog facts for a linear-session materializer. AIPerf
1931/// loader names are deliberately absent from this source boundary.
1932#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1933#[serde(deny_unknown_fields)]
1934pub struct BenchSessionDatasetCatalogInput {
1935    pub dataset: String,
1936    #[serde(default)]
1937    pub profile: Option<String>,
1938    pub source: String,
1939    pub upstream_identity: String,
1940    pub url: String,
1941    pub sha256: String,
1942    pub source_format: String,
1943    #[serde(default)]
1944    pub configuration: Option<String>,
1945    #[serde(default)]
1946    pub split: Option<String>,
1947    #[serde(default)]
1948    pub filter: Option<BenchDatasetFilterInput>,
1949    pub license: String,
1950    pub cache_path: PathBuf,
1951    pub cache_state: BenchDatasetCacheState,
1952    pub materialization_identity: String,
1953    pub provides_output_targets: bool,
1954}
1955
1956/// One release-qualified agentic trace source and its complete effective policy.
1957#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
1958#[serde(deny_unknown_fields)]
1959pub struct BenchAgenticSourceInput {
1960    pub dataset: String,
1961    pub profile: String,
1962    pub catalog: Box<BenchAgenticCatalogInput>,
1963}
1964
1965#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
1966#[serde(deny_unknown_fields)]
1967pub struct BenchAgenticCatalogInput {
1968    pub repository: String,
1969    pub revision: String,
1970    pub filename: String,
1971    pub sha256: String,
1972    #[serde(default)]
1973    pub cache_path: Option<PathBuf>,
1974    #[serde(default)]
1975    pub cache_state: Option<BenchDatasetCacheState>,
1976    pub trace_count: u32,
1977    pub approximate_bytes: u64,
1978    pub license: String,
1979    pub source_format: String,
1980    pub aiperf_loader: String,
1981    pub materialization_identity: String,
1982    pub scenario: String,
1983    pub concurrency_semantics: String,
1984    pub replay_semantics: String,
1985    pub cache_bust: String,
1986    pub trajectory_start_min: f64,
1987    pub trajectory_start_max: f64,
1988    pub global_idle_gap_cap_seconds: f64,
1989    pub trace_idle_gap_cap_seconds: f64,
1990    pub cache_warmup_requests_per_lane: u64,
1991    pub warmup_grace_seconds: u64,
1992    pub dataset_configuration_timeout_seconds: u64,
1993    pub service_profile_configuration_timeout_seconds: u64,
1994    pub default_duration_seconds: u64,
1995    pub minimum_duration_seconds: u64,
1996    pub failure_threshold: f64,
1997    pub dataset_entries: u32,
1998    pub streaming: bool,
1999    pub ignore_eos: bool,
2000    pub use_server_token_count: bool,
2001    pub gpu_telemetry: bool,
2002    pub server_metric_slice_seconds: u64,
2003    pub required_artifacts: Vec<String>,
2004    pub unavailable_dimensions: Vec<String>,
2005    pub inferencex_repository: String,
2006    pub inferencex_revision: String,
2007    pub inferencex_reference: String,
2008    pub aiperf_revision: String,
2009    pub aiperf_version: String,
2010}
2011
2012/// A single Bench case: its load shape and the number of requests to send.
2013#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
2014#[serde(deny_unknown_fields)]
2015pub struct BenchCaseInput {
2016    pub load_shape: BenchLoadInput,
2017    pub request_count: u32,
2018    #[serde(default)]
2019    pub warmup_request_count: u32,
2020    #[serde(default)]
2021    pub duration_seconds: Option<u64>,
2022    #[serde(default)]
2023    pub session_count: Option<u32>,
2024    #[serde(default)]
2025    pub warmup_session_count: Option<u32>,
2026}
2027
2028/// How a Bench case paces its requests.
2029#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
2030#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
2031pub enum BenchLoadInput {
2032    /// A fixed number of in-flight requests.
2033    ConcurrencyLimited { concurrency: u32 },
2034    /// A target arrival rate, optionally shaped by a burstiness factor.
2035    RequestRateLimited {
2036        request_rate: f64,
2037        burstiness: Option<f64>,
2038    },
2039    /// All requests issued as fast as possible.
2040    UnboundedRequestRate,
2041}
2042
2043/// Per-request latency bounds lowered to the release-owned Bench runner.
2044#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
2045#[serde(deny_unknown_fields)]
2046pub struct BenchRequestSloInput {
2047    #[serde(default)]
2048    pub request_latency_ms: Option<f64>,
2049    #[serde(default)]
2050    pub ttft_ms: Option<f64>,
2051    #[serde(default)]
2052    pub tpot_ms: Option<f64>,
2053    pub minimum_good_request_ratio: f64,
2054}
2055
2056/// One frozen dataset population consumed sequentially by every Bench case.
2057#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
2058#[serde(deny_unknown_fields)]
2059pub struct BenchPopulationInput {
2060    pub path: PathBuf,
2061    pub evidence_path: PathBuf,
2062    pub sha256: String,
2063    pub entries: u32,
2064    pub tpot_applicable: bool,
2065    #[serde(default)]
2066    pub session_templates: Vec<BenchSessionTemplateInput>,
2067}
2068
2069/// One frozen linear-template summary needed to assign case slices without
2070/// duplicating the content-bearing population evidence artifact.
2071#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
2072#[serde(deny_unknown_fields)]
2073pub struct BenchSessionTemplateInput {
2074    pub template_identity: String,
2075    pub turn_count: u32,
2076}
2077
2078/// The bounded tokenizer-backed operation that freezes one request population
2079/// before any Bench case starts.
2080#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
2081#[serde(deny_unknown_fields)]
2082pub struct BenchPopulationPreparationRequest {
2083    pub protocol_version: ProtocolVersion,
2084    pub model: MeasurementModelInput,
2085    pub tokenizer_backend: String,
2086    pub transformers_version: String,
2087    #[serde(default)]
2088    pub request_source: Option<BenchRequestSourceInput>,
2089    #[serde(default)]
2090    pub session_source: Option<BenchSessionSourceInput>,
2091    pub prompt: BenchPromptInput,
2092    pub cache_start: BenchCacheStartInput,
2093    #[serde(default)]
2094    pub source_path: Option<PathBuf>,
2095    pub required_entries: u32,
2096    pub seed: u64,
2097    #[serde(default)]
2098    pub request_body: BTreeMap<String, SettingValue>,
2099    pub artifact_dir: PathBuf,
2100}
2101
2102/// Summary of the realized token counts. Exact per-entry values remain in the
2103/// population evidence artifact.
2104#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
2105#[serde(deny_unknown_fields)]
2106pub struct BenchTokenCountSummary {
2107    pub minimum: u32,
2108    pub maximum: u32,
2109    pub mean: f64,
2110}
2111
2112/// Where the concrete template used for local prompt-length projection came
2113/// from. The model server remains authoritative for transport rendering.
2114#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
2115#[serde(rename_all = "snake_case")]
2116pub enum BenchPromptTemplateSource {
2117    RequestBody,
2118    PromptTable,
2119    TokenizerDefault,
2120}
2121
2122/// The concrete template used only for local complete-prompt projection.
2123#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
2124#[serde(deny_unknown_fields)]
2125pub struct BenchPromptTemplateProjection {
2126    pub source: BenchPromptTemplateSource,
2127    pub content: String,
2128    pub sha256: String,
2129}
2130
2131/// Summary of best-effort complete-prompt targeting for a synthetic population.
2132#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
2133#[serde(deny_unknown_fields)]
2134pub struct BenchPromptTokenTargetingSummary {
2135    pub selected_prompt_tokens: BenchTokenCountSummary,
2136    pub pre_template_content_tokens: BenchTokenCountSummary,
2137    #[serde(default)]
2138    pub projection_template: Option<BenchPromptTemplateProjection>,
2139    pub exact_entries: u32,
2140    pub fallback_entries: u32,
2141    #[serde(default)]
2142    pub fallback_reasons: BTreeMap<String, u32>,
2143}
2144
2145/// Resolved exact final-prompt prefix geometry across one frozen population.
2146#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
2147#[serde(deny_unknown_fields)]
2148pub struct BenchPrefixGeometrySummary {
2149    pub shared_prefix_tokens: BenchTokenCountSummary,
2150    pub unique_suffix_tokens: BenchTokenCountSummary,
2151    pub maximum_shared_prefix_tokens: u32,
2152    pub canonical_prefix_sha256: String,
2153    pub full_prompt_entries: u32,
2154}
2155
2156/// File-bound exact canonical prefix used to condition a primed cache start.
2157#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
2158#[serde(deny_unknown_fields)]
2159pub struct BenchPrefixConditioningInput {
2160    pub path: PathBuf,
2161    pub sha256: String,
2162    pub prompt_tokens: u32,
2163}
2164
2165/// Resolved pre-template shared system-content shape across one frozen
2166/// server-chat population.
2167#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
2168#[serde(deny_unknown_fields)]
2169pub struct BenchSharedSystemContentSummary {
2170    pub system_content_tokens: BenchTokenCountSummary,
2171    pub user_content_tokens: BenchTokenCountSummary,
2172    pub canonical_system_content_sha256: String,
2173}
2174
2175/// Terminal result of request-population materialization.
2176#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
2177#[serde(deny_unknown_fields)]
2178pub struct BenchPopulationPreparationResult {
2179    pub schema_version: u32,
2180    pub status: ClientStatus,
2181    pub materialization_identity: String,
2182    pub requested_entries: u32,
2183    pub candidate_entries: u64,
2184    pub admitted_entries: u64,
2185    pub ineligible_entries: u64,
2186    #[serde(default)]
2187    pub ineligible_reasons: BTreeMap<String, u64>,
2188    pub population: Option<BenchPopulationInput>,
2189    pub input_tokens: Option<BenchTokenCountSummary>,
2190    pub output_tokens: Option<BenchTokenCountSummary>,
2191    #[serde(default)]
2192    pub prompt_token_targeting: Option<BenchPromptTokenTargetingSummary>,
2193    #[serde(default)]
2194    pub prefix_geometry: Option<BenchPrefixGeometrySummary>,
2195    #[serde(default)]
2196    pub prefix_conditioning: Option<BenchPrefixConditioningInput>,
2197    #[serde(default)]
2198    pub shared_system_content: Option<BenchSharedSystemContentSummary>,
2199    pub evidence_path: Option<PathBuf>,
2200    pub error: Option<String>,
2201}
2202
2203/// The result a Bench client writes for the measurement runtime to consume.
2204#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
2205#[serde(deny_unknown_fields)]
2206pub struct BenchClientResult {
2207    /// Result envelope version; clients write `1`. The measurement runtime
2208    /// rejects a bench result whose version is not `1`.
2209    pub schema_version: u32,
2210    pub status: ClientStatus,
2211    pub completed_requests: u64,
2212    pub failed_requests: u64,
2213    pub normalization_schema: String,
2214    pub metrics: BTreeMap<String, f64>,
2215    #[serde(default)]
2216    pub request_slo: Option<BenchRequestSloResult>,
2217    #[serde(default)]
2218    pub session_evidence: Option<BenchSessionResultEvidence>,
2219    #[serde(default)]
2220    pub agentic_evidence: Option<Box<BenchAgenticResultEvidence>>,
2221    #[serde(default)]
2222    pub prompt_token_reconciliation: Vec<BenchPromptTokenReconciliation>,
2223    #[serde(default)]
2224    pub prompt_cache_observations: Vec<BenchPromptCacheObservation>,
2225    pub native_command: Vec<String>,
2226    pub native_exit_code: Option<i32>,
2227    #[serde(default)]
2228    pub report_invocations: Vec<BenchNativeInvocation>,
2229    pub raw_artifacts: Vec<RawArtifact>,
2230    pub error: Option<String>,
2231}
2232
2233/// Backend-reported prompt/cache token accounting for one completed profiling request.
2234#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
2235#[serde(deny_unknown_fields)]
2236pub struct BenchPromptCacheObservation {
2237    pub request_id: u64,
2238    pub prompt_tokens: u64,
2239    pub cache_read_tokens: u64,
2240    pub uncached_prompt_tokens: u64,
2241    pub cache_read_ratio: f64,
2242}
2243
2244#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
2245#[serde(deny_unknown_fields)]
2246pub struct BenchAgenticSourceVerification {
2247    pub repository: String,
2248    pub expected_revision: String,
2249    #[serde(default)]
2250    pub observed_revision: Option<String>,
2251    pub filename: String,
2252    pub expected_sha256: String,
2253    #[serde(default)]
2254    pub observed_sha256: Option<String>,
2255    #[serde(default)]
2256    pub cache_path: Option<PathBuf>,
2257    #[serde(default)]
2258    pub cache_state_before: Option<BenchDatasetCacheState>,
2259    #[serde(default)]
2260    pub acquisition_outcome: Option<BenchAgenticAcquisitionOutcome>,
2261}
2262
2263#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
2264#[serde(rename_all = "snake_case")]
2265pub enum BenchAgenticAcquisitionOutcome {
2266    Reused,
2267    Downloaded,
2268}
2269
2270#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
2271#[serde(deny_unknown_fields)]
2272pub struct BenchAgenticBranchStats {
2273    pub children_spawned: u64,
2274    pub children_completed: u64,
2275    pub children_errored: u64,
2276    pub children_truncated: u64,
2277    pub children_delayed: u64,
2278    pub parents_suspended: u64,
2279    pub parents_resumed: u64,
2280    pub parents_failed_due_to_child_error: u64,
2281    pub joins_suppressed: u64,
2282}
2283
2284#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
2285#[serde(deny_unknown_fields)]
2286pub struct BenchAgenticResultEvidence {
2287    pub source: BenchAgenticSourceVerification,
2288    #[serde(default)]
2289    pub run: Option<Box<BenchAgenticRunEvidence>>,
2290}
2291
2292#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
2293#[serde(deny_unknown_fields)]
2294pub struct BenchAgenticRunEvidence {
2295    pub native_run_id: String,
2296    pub scenario: String,
2297    pub submission_valid: bool,
2298    #[serde(default)]
2299    pub submission_invalid_reasons: Vec<String>,
2300    pub warmup_records: u64,
2301    pub warmup_error_records: u64,
2302    /// Whether the native run crossed the warmup phase and entered profiling
2303    /// (`profiling_records > 0`); snapshot-warmup failure aborts natively
2304    /// before profiling and therefore surfaces as an invalid submission.
2305    pub warmup_succeeded: bool,
2306    pub profiling_records: u64,
2307    pub distinct_runtime_conversations: u64,
2308    pub distinct_transport_requests: u64,
2309    pub context_overflow_count: u64,
2310    pub ordinary_failure_count: u64,
2311    pub branch_stats: BenchAgenticBranchStats,
2312    pub aggregate_artifact: PathBuf,
2313    /// The raw request/response artifact; absent at the `performance`
2314    /// artifact level, where raw export is not requested.
2315    #[serde(default)]
2316    pub raw_records_artifact: Option<PathBuf>,
2317    #[serde(default)]
2318    pub unavailable_dimensions: Vec<String>,
2319    /// Warmup records whose raw-derived source coordinates were observed;
2320    /// absent when the artifact level makes that mapping unavailable.
2321    #[serde(default)]
2322    pub warmup_source_coordinate_records: Option<u64>,
2323    /// Profiling records whose raw-derived source coordinates were observed;
2324    /// absent when the artifact level makes that mapping unavailable.
2325    #[serde(default)]
2326    pub source_coordinate_records: Option<u64>,
2327    /// Distinct source traces identified through the raw-derived coordinate
2328    /// mapping; absent when the artifact level makes it unavailable.
2329    #[serde(default)]
2330    pub distinct_source_traces: Option<u64>,
2331    /// Profiling records carrying a raw cache-bust marker observation;
2332    /// absent when the artifact level makes that observation unavailable.
2333    #[serde(default)]
2334    pub cache_bust_records: Option<u64>,
2335}
2336
2337/// One synthetic profiling request's planned-to-observed prompt-token check.
2338#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
2339#[serde(deny_unknown_fields)]
2340pub struct BenchPromptTokenReconciliation {
2341    pub population_index: u32,
2342    pub native_session_num: u64,
2343    pub planned_prompt_tokens: u32,
2344    #[serde(default)]
2345    pub observed_prompt_tokens: Option<u32>,
2346    pub reconciled: bool,
2347}
2348
2349/// Reconciled native evidence for one session-bounded Bench phase.
2350#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
2351#[serde(deny_unknown_fields)]
2352pub struct BenchSessionPhaseSummary {
2353    pub planned_sessions: u32,
2354    pub started_sessions: u32,
2355    pub succeeded_sessions: u32,
2356    pub failed_sessions: u32,
2357    pub planned_requests: u32,
2358    pub attempted_requests: u32,
2359    pub completed_requests: u32,
2360    pub failed_requests: u32,
2361    pub reconciled: bool,
2362}
2363
2364/// Terminal evidence for one admitted runtime session.
2365#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
2366#[serde(deny_unknown_fields)]
2367pub struct BenchRuntimeSessionResult {
2368    pub phase: String,
2369    pub runtime_session_id: String,
2370    pub template_identity: String,
2371    pub planned_turns: u32,
2372    pub attempted_turns: u32,
2373    pub status: ClientStatus,
2374    #[serde(default)]
2375    pub failure_classification: Option<String>,
2376    #[serde(default)]
2377    pub diagnostic: Option<String>,
2378    #[serde(default)]
2379    pub failing_turn: Option<u32>,
2380    pub suppressed_later_turns: u32,
2381}
2382
2383/// One native transport request reconciled to a linear-session turn.
2384#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
2385#[serde(deny_unknown_fields)]
2386pub struct BenchSessionTurnResult {
2387    pub phase: String,
2388    pub runtime_session_id: String,
2389    pub turn_index: u32,
2390    /// Measured from the raw request payload; absent when the artifact level
2391    /// does not produce the raw request/response artifact.
2392    #[serde(default)]
2393    pub pre_template_content_tokens: Option<u32>,
2394    #[serde(default)]
2395    pub observed_prompt_tokens: Option<u32>,
2396    pub native_session_num: u64,
2397    #[serde(default)]
2398    pub preceding_native_session_num: Option<u64>,
2399    #[serde(default)]
2400    pub preceding_terminal_response_receipt_ns: Option<u64>,
2401    #[serde(default)]
2402    pub effective_inter_turn_delay_seconds: Option<f64>,
2403    pub request_start_ns: u64,
2404    #[serde(default)]
2405    pub inter_turn_delay_reconciled: Option<bool>,
2406    #[serde(default)]
2407    pub post_failure_continuation: bool,
2408    pub native_artifact_name: String,
2409}
2410
2411/// Session and transport reconciliation returned by the Bench client.
2412#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
2413#[serde(deny_unknown_fields)]
2414pub struct BenchSessionResultEvidence {
2415    pub warmup: BenchSessionPhaseSummary,
2416    pub profiling: BenchSessionPhaseSummary,
2417    pub sessions: Vec<BenchRuntimeSessionResult>,
2418    pub turns: Vec<BenchSessionTurnResult>,
2419    pub population_slice_reconciled: bool,
2420    pub sessions_reconciled: bool,
2421    pub turn_order_reconciled: bool,
2422    pub inter_turn_delays_reconciled: bool,
2423    /// Whether raw requests reconcile to normalized metric records; absent at
2424    /// the `performance` artifact level, where no raw artifact exists.
2425    #[serde(default)]
2426    pub native_requests_reconciled: Option<bool>,
2427    pub counts_reconciled: bool,
2428    /// Evidence dimensions recorded as unavailable due to the effective
2429    /// artifact level ([[RFC-0004:C-BENCH-ARTIFACT-LEVEL]]).
2430    #[serde(default)]
2431    pub unavailable_dimensions: Vec<String>,
2432}
2433
2434/// One bounded native post-processing command and its terminal outcome.
2435#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
2436#[serde(deny_unknown_fields)]
2437pub struct BenchNativeInvocation {
2438    pub purpose: String,
2439    pub command: Vec<String>,
2440    pub exit_code: Option<i32>,
2441    pub interrupted: bool,
2442    pub timed_out: bool,
2443}
2444
2445/// File-bound request-SLO evidence derived from AIPerf profiling records.
2446#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
2447#[serde(deny_unknown_fields)]
2448pub struct BenchRequestSloResult {
2449    pub good_requests: u64,
2450    pub good_request_ratio: f64,
2451    pub goodput: f64,
2452    pub profiling_duration_seconds: f64,
2453    pub profiling_duration_source: String,
2454    pub request_count_reconciled: bool,
2455    #[serde(default)]
2456    pub native_aggregate_good_request_count: Option<u64>,
2457    #[serde(default)]
2458    pub native_aggregate_good_request_count_consistent: Option<bool>,
2459}
2460
2461// Schema roots.
2462
2463/// The schema root for the independently released framework adapter SDK.
2464#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
2465#[serde(deny_unknown_fields)]
2466pub struct AdapterProtocol {
2467    pub request: AdapterRequest,
2468    pub response: AdapterResponse,
2469}
2470
2471/// The schema root aggregating the request and result surfaces used by the
2472/// product-owned Eval and Bench measurement clients. Its optional fields are
2473/// code-generation anchors and are never all populated in one message.
2474#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
2475#[serde(deny_unknown_fields)]
2476pub struct MeasurementProtocol {
2477    #[serde(default, skip_serializing_if = "Option::is_none")]
2478    pub eval_client_request: Option<EvalClientRequest>,
2479    #[serde(default, skip_serializing_if = "Option::is_none")]
2480    pub eval_client_result: Option<EvalClientResult>,
2481    #[serde(default, skip_serializing_if = "Option::is_none")]
2482    pub bench_client_request: Option<BenchClientRequest>,
2483    #[serde(default, skip_serializing_if = "Option::is_none")]
2484    pub bench_client_result: Option<BenchClientResult>,
2485    #[serde(default, skip_serializing_if = "Option::is_none")]
2486    pub bench_population_preparation_request: Option<BenchPopulationPreparationRequest>,
2487    #[serde(default, skip_serializing_if = "Option::is_none")]
2488    pub bench_population_preparation_result: Option<BenchPopulationPreparationResult>,
2489    #[serde(default, skip_serializing_if = "Option::is_none")]
2490    pub data_asset_preparation_request: Option<MeasurementDataAssetPreparationRequest>,
2491    #[serde(default, skip_serializing_if = "Option::is_none")]
2492    pub data_asset_preparation_result: Option<MeasurementDataAssetPreparationResult>,
2493}