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 `12` (serialized as the
20/// string `"12"`); a mismatch is rejected before lowering.
21#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
22pub enum ProtocolVersion {
23    /// Protocol version 12.
24    #[serde(rename = "12")]
25    V12,
26}
27
28impl ProtocolVersion {
29    /// The current adapter protocol version.
30    pub const CURRENT: Self = Self::V12;
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::V12 => "12",
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 integration-owned HTTP registry contract for target-aware readiness.
856#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
857#[serde(deny_unknown_fields)]
858pub struct HttpTargetRegistryReadiness {
859    pub target_scheme: TargetEndpointScheme,
860    pub readiness_path: String,
861    pub registry_path: String,
862    pub targets_field: String,
863    pub target_url_field: String,
864    pub target_role_field: String,
865    pub target_healthy_field: String,
866    pub target_bootstrap_port_field: String,
867    pub prefill_role_value: String,
868    pub decode_role_value: String,
869    pub prefill_bootstrap_port: String,
870}
871
872/// An HTTP action invoked against a logical serving endpoint.
873#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
874#[serde(deny_unknown_fields)]
875pub struct HttpActionSpec {
876    pub method: HttpMethod,
877    pub path: String,
878    /// When present, a 2xx response succeeds only if its JSON body holds the
879    /// expected value at the pointer ([[RFC-0006:C-INTEGRATIONS]]).
880    #[serde(default, skip_serializing_if = "Option::is_none")]
881    pub success: Option<JsonValueMatch>,
882}
883
884// Capture window control ([[RFC-0004:C-WORKLOAD-PROFILING]]).
885
886/// Marks a replica as a profiling capture target and carries its window
887/// control ([[RFC-0004:C-WORKLOAD-PROFILING]]).
888#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
889#[serde(deny_unknown_fields)]
890pub struct CaptureTargetRequirement {
891    /// The capture mechanism this target declares; it must equal the
892    /// effective mechanism requested on the plan.
893    pub mechanism: CaptureMechanism,
894    pub window_control: CaptureWindowControlRequirement,
895}
896
897/// The logical workload endpoint and typed actions that open and close a
898/// capture window.
899#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
900#[serde(deny_unknown_fields)]
901pub struct CaptureWindowControlRequirement {
902    pub endpoint: CaptureWindowControlEndpoint,
903    pub start: CaptureWindowHttpActionSpec,
904    pub stop: CaptureWindowHttpActionSpec,
905}
906
907/// The logical workload endpoint exposing a capture target's window-control
908/// actions.
909#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
910#[serde(rename_all = "snake_case")]
911pub enum CaptureWindowControlEndpoint {
912    /// The entry process of the capture target's Engine replica.
913    ReplicaEntry,
914    /// The separately planned Gateway process.
915    Gateway,
916}
917
918/// A capture-window HTTP action invoked against a logical serving endpoint.
919/// The integration owns any framework-specific JSON body; the control plane
920/// owns execution and evidence.
921#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
922#[serde(deny_unknown_fields)]
923pub struct CaptureWindowHttpActionSpec {
924    pub method: HttpMethod,
925    pub path: String,
926    #[serde(default, skip_serializing_if = "Option::is_none")]
927    pub body: Option<BTreeMap<String, SettingValue>>,
928    /// When present, a 2xx response succeeds only if its JSON body holds the
929    /// expected value at the pointer ([[RFC-0006:C-INTEGRATIONS]]).
930    #[serde(default, skip_serializing_if = "Option::is_none")]
931    pub success: Option<JsonValueMatch>,
932}
933
934/// One concrete process allocation supplied to `RenderServe`. Model-rank and
935/// process-only frontend identities are distinct so a frontend cannot acquire
936/// model coordinates or a model locator by construction.
937#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
938#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
939pub enum ServeProcessAllocation {
940    ModelRank {
941        process: String,
942        role: String,
943        role_kind: ServeRoleKind,
944        replica: u32,
945        rank: u32,
946        rank_count: u32,
947        machine: String,
948        devices: Vec<u32>,
949        model_locator: String,
950        /// The machine-resolved auxiliary weight locators for each declared
951        /// kind ([[RFC-0003:C-SERVE-AUXILIARY-MODELS]]).
952        #[serde(default, skip_serializing_if = "Vec::is_empty")]
953        auxiliary_model_locators: Vec<AuxiliaryModelLocator>,
954        #[serde(default, skip_serializing_if = "Option::is_none")]
955        endpoint: Option<EndpointAssignment>,
956        ports: BTreeMap<String, EndpointAssignment>,
957        cache: String,
958        /// The control-plane-assigned persistent trace directory; present
959        /// only when this rank belongs to an engine-trace capture target
960        /// ([[RFC-0004:C-WORKLOAD-PROFILING]]).
961        #[serde(default, skip_serializing_if = "Option::is_none")]
962        capture_storage: Option<String>,
963        launch: AllocationLaunch,
964        effective_settings: BTreeMap<String, SettingValue>,
965        effective_parallelism: Parallelism,
966        #[serde(default)]
967        links: Vec<ServeRoleLink>,
968        #[serde(default)]
969        dependencies: Vec<String>,
970        /// The discovery process this process registers with, when the
971        /// topology has one ([[RFC-0003:C-SERVE-TOPOLOGY]]).
972        #[serde(default, skip_serializing_if = "Option::is_none")]
973        discovery: Option<String>,
974        #[serde(default)]
975        render_inputs: Vec<SuppliedRenderInput>,
976    },
977    Frontend {
978        process: String,
979        process_role: FrontendProcessRole,
980        components: FrontendComponents,
981        machine: String,
982        devices: Vec<u32>,
983        endpoint: EndpointAssignment,
984        ports: BTreeMap<String, EndpointAssignment>,
985        cache: String,
986        launch: AllocationLaunch,
987        gateway: Box<GatewayPlan>,
988        #[serde(default, skip_serializing_if = "Option::is_none")]
989        pd_router: Option<Box<PdRouterPlan>>,
990        #[serde(default)]
991        links: Vec<ServeRoleLink>,
992        #[serde(default)]
993        dependencies: Vec<String>,
994        /// The discovery process this process registers with, when the
995        /// topology has one ([[RFC-0003:C-SERVE-TOPOLOGY]]).
996        #[serde(default, skip_serializing_if = "Option::is_none")]
997        discovery: Option<String>,
998        #[serde(default)]
999        render_inputs: Vec<SuppliedRenderInput>,
1000    },
1001    /// The discovery service a Gateway result requires
1002    /// ([[RFC-0003:C-SERVE-TOPOLOGY]]): process-only, zero devices, placed on
1003    /// the Gateway's machine, with an internal (not public) endpoint.
1004    Discovery {
1005        process: String,
1006        process_role: DiscoveryProcessRole,
1007        components: DiscoveryBinding,
1008        machine: String,
1009        devices: Vec<u32>,
1010        endpoint: EndpointAssignment,
1011        ports: BTreeMap<String, EndpointAssignment>,
1012        cache: String,
1013        /// The control-plane-assigned empty data directory under the
1014        /// machine's runtime cache.
1015        data_directory: String,
1016        launch: AllocationLaunch,
1017        discovery: Box<DiscoveryRequirement>,
1018        #[serde(default)]
1019        render_inputs: Vec<SuppliedRenderInput>,
1020    },
1021}
1022
1023/// The machine-local launch channel selected by the control plane.
1024#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1025#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
1026pub enum AllocationLaunch {
1027    Local,
1028    Ssh { target: String },
1029}
1030
1031/// The final process invocations returned by a `RenderServe`, one per supplied
1032/// allocation.
1033#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1034#[serde(deny_unknown_fields)]
1035pub struct RenderServeResult {
1036    pub integration: IntegrationIdentity,
1037    pub processes: Vec<RenderedServeProcess>,
1038}
1039
1040/// A rendered process bound to the model-rank or frontend allocation identity
1041/// it was produced for.
1042#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1043#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
1044pub enum RenderedServeProcess {
1045    ModelRank {
1046        process: String,
1047        role: String,
1048        replica: u32,
1049        rank: u32,
1050        rank_count: u32,
1051        launch_files: Vec<LaunchFileDeclaration>,
1052        command: ProcessSpec,
1053    },
1054    Frontend {
1055        process: String,
1056        process_role: FrontendProcessRole,
1057        components: FrontendComponents,
1058        launch_files: Vec<LaunchFileDeclaration>,
1059        command: ProcessSpec,
1060    },
1061    Discovery {
1062        process: String,
1063        process_role: DiscoveryProcessRole,
1064        components: DiscoveryBinding,
1065        launch_files: Vec<LaunchFileDeclaration>,
1066        command: ProcessSpec,
1067    },
1068}
1069
1070/// One immutable text input a rendered process requires before it can launch.
1071#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1072#[serde(deny_unknown_fields)]
1073pub struct LaunchFileDeclaration {
1074    pub relative_path: String,
1075    pub text: String,
1076    pub sha256: String,
1077}
1078
1079/// A launchable process: its argument vector and environment.
1080#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1081#[serde(deny_unknown_fields)]
1082pub struct ProcessSpec {
1083    pub argv: Vec<String>,
1084    pub env: BTreeMap<String, String>,
1085}
1086
1087// Frontend components.
1088
1089/// Which bounded implementation renders an accepted concrete frontend
1090/// allocation. This is a lowering boundary only; the control plane always
1091/// owns placement, lifecycle, cleanup, endpoints, and records.
1092#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1093#[serde(rename_all = "snake_case")]
1094pub enum RenderSource {
1095    ControlPlane,
1096    Integration,
1097}
1098
1099/// The one canonical process role available to a frontend.
1100#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1101#[serde(rename_all = "snake_case")]
1102pub enum FrontendProcessRole {
1103    Gateway,
1104}
1105
1106/// The canonical process role of the discovery process.
1107#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1108#[serde(rename_all = "snake_case")]
1109pub enum DiscoveryProcessRole {
1110    Discovery,
1111}
1112
1113/// The only member of the closed discovery component binding.
1114#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1115#[serde(rename_all = "snake_case")]
1116pub enum DiscoveryComponent {
1117    Discovery,
1118}
1119
1120/// The closed `["discovery"]` component binding.
1121#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1122#[serde(transparent)]
1123pub struct DiscoveryBinding(pub [DiscoveryComponent; 1]);
1124
1125impl DiscoveryBinding {
1126    #[must_use]
1127    pub const fn discovery() -> Self {
1128        Self([DiscoveryComponent::Discovery])
1129    }
1130}
1131
1132/// A discovery service a Gateway backend requires: its logical ports beyond
1133/// its endpoint, readiness, and render inputs ([[RFC-0006:C-INTEGRATIONS]]).
1134#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1135#[serde(deny_unknown_fields)]
1136pub struct DiscoveryRequirement {
1137    #[serde(default)]
1138    pub ports: Vec<String>,
1139    pub readiness: ReadinessProbe,
1140    #[serde(default)]
1141    pub render_inputs: Vec<RenderInputDeclaration>,
1142}
1143
1144/// The fixed co-rendering requirement shared by compatible frontend plans.
1145#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1146#[serde(deny_unknown_fields)]
1147pub struct FrontendCoRendering {
1148    pub process_role: FrontendProcessRole,
1149}
1150
1151/// The literal first member of every closed frontend component binding.
1152#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1153#[serde(rename_all = "snake_case")]
1154pub enum FrontendGatewayComponent {
1155    Gateway,
1156}
1157
1158/// The literal second member of the fused P/D frontend component binding.
1159#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1160#[serde(rename_all = "snake_case")]
1161pub enum FrontendPdRouterComponent {
1162    PdRouter,
1163}
1164
1165/// The stable schema branch for a Gateway-only frontend binding.
1166#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1167#[serde(transparent)]
1168pub struct GatewayFrontendBinding(pub [FrontendGatewayComponent; 1]);
1169
1170/// The stable schema branch for a fused Gateway and P/D Router binding.
1171#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1172#[serde(transparent)]
1173pub struct GatewayPdRouterFrontendBinding(
1174    pub (FrontendGatewayComponent, FrontendPdRouterComponent),
1175);
1176
1177/// The only two frontend bindings the adapter protocol accepts. Tuple representation
1178/// deliberately serializes as the closed ordered arrays `["gateway"]` and
1179/// `["gateway", "pd_router"]` rather than as an open component list.
1180#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1181#[serde(untagged)]
1182pub enum FrontendComponents {
1183    Gateway(GatewayFrontendBinding),
1184    GatewayPdRouter(GatewayPdRouterFrontendBinding),
1185}
1186
1187impl FrontendComponents {
1188    #[must_use]
1189    pub const fn gateway() -> Self {
1190        Self::Gateway(GatewayFrontendBinding([FrontendGatewayComponent::Gateway]))
1191    }
1192
1193    #[must_use]
1194    pub const fn gateway_pd_router() -> Self {
1195        Self::GatewayPdRouter(GatewayPdRouterFrontendBinding((
1196            FrontendGatewayComponent::Gateway,
1197            FrontendPdRouterComponent::PdRouter,
1198        )))
1199    }
1200
1201    #[must_use]
1202    pub const fn includes_pd_router(&self) -> bool {
1203        matches!(self, Self::GatewayPdRouter(_))
1204    }
1205}
1206
1207/// The target a Gateway forwards accepted public requests to.
1208#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1209#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
1210pub enum GatewayTarget {
1211    /// A routed-single Gateway targets the sole Engine role entry point.
1212    Engine { role: String },
1213    /// A P/D Gateway hands requests to its co-rendered P/D Router component.
1214    PdRouter,
1215}
1216
1217/// The public frontend component plan returned independently from Engine and
1218/// P/D Router planning.
1219#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
1220#[serde(deny_unknown_fields)]
1221pub struct GatewayPlan {
1222    pub backend: String,
1223    pub implementation: String,
1224    pub implementation_version: String,
1225    pub effective_settings: BTreeMap<String, SettingValue>,
1226    pub endpoint: EndpointDeclaration,
1227    pub readiness: ReadinessProbe,
1228    #[serde(default)]
1229    pub ports: Vec<String>,
1230    pub targets: Vec<GatewayTarget>,
1231    #[serde(default)]
1232    pub render_inputs: Vec<RenderInputDeclaration>,
1233    pub render_source: RenderSource,
1234    pub co_rendering: FrontendCoRendering,
1235    /// The discovery service this Gateway backend requires; its presence
1236    /// derives one `discovery` process ([[RFC-0003:C-SERVE-TOPOLOGY]]).
1237    #[serde(default, skip_serializing_if = "Option::is_none")]
1238    pub discovery: Option<DiscoveryRequirement>,
1239}
1240
1241/// Independent policies for choosing prefill and decode targets.
1242#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1243#[serde(deny_unknown_fields)]
1244pub struct PdRoutingPolicies {
1245    pub prefill: String,
1246    pub decode: String,
1247}
1248
1249/// The currently demonstrated Gateway-to-P/D Router handoff is internal to
1250/// one fused frontend process.
1251#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1252#[serde(rename_all = "snake_case")]
1253pub enum FrontendHandoff {
1254    InProcess,
1255}
1256
1257/// The P/D orchestration component plan, kept separate even when the same
1258/// process and provider also realize Gateway.
1259#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
1260#[serde(deny_unknown_fields)]
1261pub struct PdRouterPlan {
1262    pub backend: String,
1263    pub implementation: String,
1264    pub implementation_version: String,
1265    pub effective_settings: BTreeMap<String, SettingValue>,
1266    pub policies: PdRoutingPolicies,
1267    pub prefill_role: String,
1268    pub decode_role: String,
1269    pub target_scheme: TargetEndpointScheme,
1270    #[serde(default)]
1271    pub ports: Vec<String>,
1272    pub readiness: ReadinessProbe,
1273    pub handoff: FrontendHandoff,
1274    #[serde(default)]
1275    pub render_inputs: Vec<RenderInputDeclaration>,
1276    pub render_source: RenderSource,
1277    pub co_rendering: FrontendCoRendering,
1278}
1279
1280/// The application protocol used to identify serving targets in a registry.
1281#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1282#[serde(rename_all = "snake_case")]
1283pub enum TargetEndpointScheme {
1284    /// HTTP serving endpoint.
1285    Http,
1286    /// gRPC serving endpoint.
1287    Grpc,
1288    /// A framework's own TCP request plane, such as Dynamo's.
1289    Tcp,
1290}
1291
1292// Measurement-client shared base.
1293
1294/// The model identity used by measurement clients. Unlike integration
1295/// planning, a benchmark client may need a controller-visible tokenizer
1296/// locator.
1297#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1298#[serde(deny_unknown_fields)]
1299pub struct MeasurementModelInput {
1300    pub locator: String,
1301    pub served_name: String,
1302}
1303
1304/// The public workload endpoint an Eval or Bench client connects to.
1305#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1306#[serde(deny_unknown_fields)]
1307pub struct ClientEndpointInput {
1308    pub protocol: EndpointProtocol,
1309    pub host: String,
1310    pub port: u16,
1311    pub completions_path: String,
1312    pub chat_completions_path: String,
1313    #[serde(default)]
1314    pub server_metrics: Option<ServerMetricsEndpointInput>,
1315    #[serde(default)]
1316    pub prompt_cache_read_zero_representation: Option<PromptCacheReadZeroRepresentation>,
1317}
1318
1319/// The resolved server-metrics endpoint supplied to a measurement client.
1320#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1321#[serde(deny_unknown_fields)]
1322pub struct ServerMetricsEndpointInput {
1323    pub path: String,
1324    #[serde(default)]
1325    pub port_name: Option<String>,
1326    pub url: String,
1327}
1328
1329/// How a backend with cache reporting enabled represents a zero-token cache read.
1330#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1331#[serde(rename_all = "snake_case")]
1332pub enum PromptCacheReadZeroRepresentation {
1333    Explicit,
1334    Omitted,
1335}
1336
1337/// The terminal outcome a measurement client reports.
1338#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1339#[serde(rename_all = "snake_case")]
1340pub enum ClientStatus {
1341    /// The client completed its measurement successfully.
1342    Succeeded,
1343    /// The client did not complete successfully.
1344    Failed,
1345}
1346
1347/// A raw output file a client produced, retained as workload evidence.
1348#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1349#[serde(deny_unknown_fields)]
1350pub struct RawArtifact {
1351    pub name: String,
1352    pub kind: String,
1353    pub path: PathBuf,
1354}
1355
1356// Eval client.
1357
1358/// The request the Eval measurement runtime passes to its client: the endpoint
1359/// to hit, the model, the eval definition, and where to write artifacts.
1360#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
1361#[serde(deny_unknown_fields)]
1362pub struct EvalClientRequest {
1363    pub protocol_version: ProtocolVersion,
1364    pub workspace_root: PathBuf,
1365    pub workspace_source_exclusions: Vec<PathBuf>,
1366    pub endpoint: ClientEndpointInput,
1367    pub model: MeasurementModelInput,
1368    pub definition: EvalDefinitionInput,
1369    #[serde(default)]
1370    pub prepared_source: Option<EvalPreparedSourceBinding>,
1371    /// Remaining control-plane case budget when the client is released.
1372    pub case_budget_seconds: f64,
1373    pub artifact_dir: PathBuf,
1374}
1375
1376/// The measurement an Eval client runs against the workload endpoint.
1377#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
1378#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
1379pub enum EvalDefinitionInput {
1380    /// A single-prompt liveness check.
1381    #[serde(rename = "openai_smoke")]
1382    OpenAiSmoke {
1383        prompt: String,
1384        max_tokens: u32,
1385        timeout_seconds: u64,
1386        /// The effective vision-mode selection
1387        /// ([[RFC-0004:C-MEASUREMENTS]]): a vision smoke routes to chat
1388        /// completions carrying the release-owned fixed test image.
1389        #[serde(default)]
1390        vision: bool,
1391    },
1392    /// An lm-eval task run with a pass threshold on the chosen metric.
1393    LmEval {
1394        task: Box<EvalTaskSourceInput>,
1395        /// The prompt rendering authority resolution selected.
1396        prompt: EvalPromptInput,
1397        /// The authority the definition declared, absent when it was defaulted.
1398        #[serde(default)]
1399        declared_prompt: Option<EvalPromptInput>,
1400        #[serde(default)]
1401        request_body: BTreeMap<String, SettingValue>,
1402        limit: Option<u32>,
1403        few_shot: Option<u32>,
1404        /// The seed the definition declared, absent when it omitted one; a
1405        /// single trial with no declared seed does not invent one.
1406        seed: Option<u64>,
1407        /// The effective base seed of the trial schedule, resolved by the
1408        /// control plane: the declared seed, or the shared fallback when the
1409        /// definition declared none ([[RFC-0004:C-LM-EVAL]]).
1410        base_seed: u64,
1411        trials: u32,
1412        max_tokens: Option<u32>,
1413        concurrency: Option<u32>,
1414        metric: String,
1415        #[serde(default)]
1416        metric_filter: Option<String>,
1417        threshold: f64,
1418        timeout_seconds: u64,
1419    },
1420}
1421
1422/// The prompt rendering authority for a generative lm-eval definition.
1423///
1424/// The authority determines the protocol route and the release-pinned lm-eval
1425/// client; the runner rejects an authority the resolved task output type does
1426/// not permit.
1427#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1428#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
1429pub enum EvalPromptInput {
1430    /// Ordinary text on the completions path with no chat construction.
1431    Flat,
1432    /// Structured messages on the chat-completions path, rendered by the server.
1433    ServerChat,
1434}
1435
1436/// The resolved lm-eval task source consumed by the release-owned Eval runner.
1437#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1438#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
1439pub enum EvalTaskSourceInput {
1440    /// One individual task shipped by the pinned lm-eval runtime.
1441    BuiltIn { name: String },
1442    /// One task closure shipped and identity-bound by the Inferlab release.
1443    Bundled {
1444        name: String,
1445        task_identity: String,
1446        path: PathBuf,
1447        task_closure_sha256: String,
1448        task_definition_sha256: String,
1449        prompt_asset_sha256: String,
1450        dataset_asset_sha256: String,
1451        scorer_sha256: String,
1452    },
1453    /// One validated workspace-local lm-eval YAML file.
1454    WorkspaceYaml { path: PathBuf },
1455}
1456
1457/// The result an Eval client writes for the measurement runtime to consume.
1458#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
1459#[serde(deny_unknown_fields)]
1460pub struct EvalClientResult {
1461    /// Result envelope version; clients write `1`. The measurement runtime
1462    /// rejects an eval result whose version is not `1`.
1463    pub schema_version: u32,
1464    pub status: ClientStatus,
1465    pub metrics: BTreeMap<String, f64>,
1466    #[serde(default)]
1467    pub normalized_metrics: BTreeMap<String, EvalNormalizedMetric>,
1468    #[serde(default)]
1469    pub gate: Option<EvalMetricGate>,
1470    #[serde(default)]
1471    pub trial_summary: Option<EvalTrialSummary>,
1472    pub native_command: Vec<String>,
1473    #[serde(default)]
1474    pub native_exit_code: Option<i32>,
1475    #[serde(default)]
1476    pub native_timed_out: bool,
1477    pub raw_artifacts: Vec<RawArtifact>,
1478    pub failure_kind: Option<EvalFailureKind>,
1479    pub error: Option<String>,
1480}
1481
1482/// A typed Eval failure category preserved across the client boundary.
1483#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1484#[serde(rename_all = "snake_case")]
1485pub enum EvalFailureKind {
1486    TaskResolution,
1487    ProbeTokenizer,
1488    ProbeTransport,
1489    ProbeHttp,
1490    ProbeMalformedResponse,
1491    ProbeGeneratedOnlyLogprobs,
1492    ProbeTokenizerAlignment,
1493    MetricNormalization,
1494}
1495
1496/// The threshold comparison selected from lm-eval's scoring direction.
1497#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1498#[serde(rename_all = "snake_case")]
1499pub enum EvalMetricComparison {
1500    AtLeast,
1501    AtMost,
1502}
1503
1504/// The terminal conclusion of an lm-eval metric threshold gate.
1505#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1506#[serde(rename_all = "snake_case")]
1507pub enum EvalMetricGateConclusion {
1508    Passed,
1509    Failed,
1510}
1511
1512/// One finite lm-eval metric with its exact native provenance.
1513#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
1514#[serde(deny_unknown_fields)]
1515pub struct EvalNormalizedMetric {
1516    pub source_identity: String,
1517    pub metric: String,
1518    pub filter: Option<String>,
1519    pub native_metric_key: String,
1520    pub value: f64,
1521    pub higher_is_better: bool,
1522    /// The prompt rendering authority that produced this value. The same task
1523    /// scored under another authority is a different measurement, so the value
1524    /// never travels without it.
1525    pub prompt_authority: EvalPromptInput,
1526}
1527
1528/// The effective threshold comparison for the configured primary metric.
1529#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
1530#[serde(deny_unknown_fields)]
1531pub struct EvalMetricGate {
1532    pub metric: EvalNormalizedMetric,
1533    pub threshold: f64,
1534    pub comparison: EvalMetricComparison,
1535    pub conclusion: EvalMetricGateConclusion,
1536}
1537
1538/// Reconstructible aggregate counts for fixed outer Eval trials.
1539#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
1540#[serde(deny_unknown_fields)]
1541pub struct EvalTrialSummary {
1542    pub requested_trials: u32,
1543    pub issued_trials: u32,
1544    pub unissued_trials: u32,
1545    pub completed_trials: u32,
1546    pub request_failure_trials: u32,
1547    pub passed_trials: u32,
1548    pub pass_rate: Option<f64>,
1549    pub per_trial_metric: String,
1550    pub per_trial_filter: Option<String>,
1551    pub higher_is_better: bool,
1552}
1553
1554// Bench client.
1555
1556/// The request the Bench measurement runtime passes to its client: the
1557/// endpoint, model, bench definition, the load case to run, and the artifact
1558/// directory.
1559#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
1560#[serde(deny_unknown_fields)]
1561pub struct BenchClientRequest {
1562    pub protocol_version: ProtocolVersion,
1563    pub endpoint: ClientEndpointInput,
1564    pub model: MeasurementModelInput,
1565    pub definition: BenchDefinitionInput,
1566    #[serde(default)]
1567    pub population: Option<BenchPopulationInput>,
1568    pub case: BenchCaseInput,
1569    /// Remaining control-plane case budget when the client is released.
1570    pub case_budget_seconds: f64,
1571    pub artifact_dir: PathBuf,
1572}
1573
1574/// The workload shape a Bench client drives, shared across its load cases.
1575#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
1576#[serde(deny_unknown_fields)]
1577pub struct BenchDefinitionInput {
1578    #[serde(default)]
1579    pub request_source: Option<BenchRequestSourceInput>,
1580    #[serde(default)]
1581    pub session_source: Option<BenchSessionSourceInput>,
1582    #[serde(default)]
1583    pub agentic_source: Option<BenchAgenticSourceInput>,
1584    pub prompt: BenchPromptInput,
1585    #[serde(default)]
1586    pub server_metrics: bool,
1587    #[serde(default)]
1588    pub artifact_level: BenchArtifactLevelInput,
1589    pub seed: u64,
1590    #[serde(default)]
1591    pub request_body: BTreeMap<String, SettingValue>,
1592    #[serde(default)]
1593    pub request_slo: Option<BenchRequestSloInput>,
1594    pub timeout_seconds: u64,
1595    pub cache_start: BenchCacheStartInput,
1596}
1597
1598/// The artifact level a Bench case requests from the measurement runtime.
1599/// Omission resolves to `diagnostic`, which retains the full raw
1600/// request/response export; `performance` keeps only normalized per-request
1601/// records and the summary export ([[RFC-0004:C-BENCH-ARTIFACT-LEVEL]]).
1602#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1603#[serde(rename_all = "snake_case")]
1604pub enum BenchArtifactLevelInput {
1605    Performance,
1606    #[default]
1607    Diagnostic,
1608}
1609
1610#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1611#[serde(rename_all = "snake_case")]
1612pub enum BenchCacheStartInput {
1613    Uncontrolled,
1614    Cold,
1615    Primed,
1616}
1617
1618/// The frozen prompt representation, route, and rendering authority for a
1619/// serving Bench population.
1620#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
1621#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
1622pub enum BenchPromptInput {
1623    Flat {
1624        request_representation: BenchRequestRepresentationInput,
1625        route: BenchPromptRouteInput,
1626        rendering_authority: BenchRenderingAuthorityInput,
1627    },
1628    RenderedChat {
1629        #[serde(default)]
1630        chat_template: Option<String>,
1631        #[serde(default)]
1632        chat_template_kwargs: BTreeMap<String, SettingValue>,
1633        request_representation: BenchRequestRepresentationInput,
1634        route: BenchPromptRouteInput,
1635        rendering_authority: BenchRenderingAuthorityInput,
1636    },
1637    ServerChat {
1638        request_representation: BenchRequestRepresentationInput,
1639        route: BenchPromptRouteInput,
1640        rendering_authority: BenchRenderingAuthorityInput,
1641    },
1642}
1643
1644/// The request payload representation frozen by prompt resolution.
1645#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1646#[serde(rename_all = "snake_case")]
1647pub enum BenchRequestRepresentationInput {
1648    FlatPrompt,
1649    StructuredMessages,
1650}
1651
1652/// The named OpenAI-compatible endpoint family derived from prompt authority.
1653#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1654#[serde(rename_all = "snake_case")]
1655pub enum BenchPromptRouteInput {
1656    Completions,
1657    ChatCompletions,
1658}
1659
1660/// The owner that turns semantic prompt input into the final prompt tokens.
1661#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1662#[serde(rename_all = "snake_case")]
1663pub enum BenchRenderingAuthorityInput {
1664    LocalFlat,
1665    LocalTemplate,
1666    Server,
1667}
1668
1669/// One closed request origin lowered by Inferlab for the Bench runtime.
1670#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
1671#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
1672pub enum BenchRequestSourceInput {
1673    /// AIPerf generates exact token-shape prompts from the release-pinned
1674    /// synthetic generator.
1675    Random {
1676        input_tokens: BenchTokenSelectorInput,
1677        output_tokens: BenchTokenSelectorInput,
1678        #[serde(default)]
1679        prefix_sharing: Option<BenchPrefixSharingInput>,
1680        #[serde(default)]
1681        shared_system_content: Option<BenchSharedSystemContentInput>,
1682        #[serde(default)]
1683        corpus: Option<BenchCorpusInput>,
1684        /// The effective per-request image decoration
1685        /// ([[RFC-0004:C-BENCH-REQUEST-SOURCES]]); absent when the source
1686        /// declares no images.
1687        #[serde(default)]
1688        images: Option<BenchImagesInput>,
1689    },
1690    /// AIPerf samples exact token-shape pairs from one seeded categorical
1691    /// distribution.
1692    RandomMixture {
1693        shapes: Vec<BenchRandomShapeInput>,
1694        total_weight: u64,
1695        #[serde(default)]
1696        prefix_sharing: Option<BenchPrefixSharingInput>,
1697    },
1698    /// Inferlab materializes a release-catalog conversation snapshot before
1699    /// AIPerf starts.
1700    Dataset {
1701        dataset: String,
1702        #[serde(default)]
1703        profile: Option<String>,
1704        max_input_tokens: u32,
1705        output_tokens: Option<u32>,
1706        catalog: Box<BenchDatasetCatalogInput>,
1707    },
1708    /// Inferlab replays one workspace-local frozen population file unchanged;
1709    /// the file bytes are the sole population authority
1710    /// ([[RFC-0004:C-BENCH-REQUEST-SOURCES]]).
1711    Replay {
1712        /// Workspace-relative population path as declared. The preparation
1713        /// request's `source_path` carries its absolute resolution.
1714        path: String,
1715        #[serde(default)]
1716        expected_sha256: Option<String>,
1717        #[serde(default)]
1718        prefix_sharing: Option<BenchPrefixSharingInput>,
1719    },
1720}
1721
1722/// One fixed or bounded inclusive-uniform token-length selector.
1723#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1724#[serde(untagged)]
1725pub enum BenchTokenSelectorInput {
1726    Fixed(u32),
1727    InclusiveUniform(BenchInclusiveUniformInput),
1728}
1729
1730/// The one bounded distribution currently supported for a token selector.
1731#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1732#[serde(deny_unknown_fields)]
1733pub struct BenchInclusiveUniformInput {
1734    pub kind: BenchTokenDistributionKindInput,
1735    pub min: u32,
1736    pub max: u32,
1737}
1738
1739#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1740#[serde(rename_all = "snake_case")]
1741pub enum BenchTokenDistributionKindInput {
1742    InclusiveUniform,
1743}
1744
1745/// One declared exact final-prompt prefix geometry.
1746#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
1747#[serde(untagged)]
1748pub enum BenchPrefixSharingInput {
1749    Tokens { shared_prefix_tokens: u32 },
1750    Ratio { shared_prefix_ratio: f64 },
1751}
1752
1753/// One pre-template shared system-content declaration for server-rendered
1754/// synthetic chat.
1755#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
1756#[serde(untagged)]
1757pub enum BenchSharedSystemContentInput {
1758    Tokens { tokens: u32 },
1759    Ratio { ratio: f64 },
1760}
1761
1762/// One operator-supplied text corpus binding for the random request source;
1763/// entry content is drawn as exact token-length slices of the corpus token
1764/// stream ([[RFC-0004:C-BENCH-REQUEST-SOURCES]]).
1765#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1766#[serde(deny_unknown_fields)]
1767pub struct BenchCorpusInput {
1768    /// Workspace-relative corpus path as declared. The preparation request's
1769    /// `source_path` carries its absolute resolution.
1770    pub path: String,
1771    #[serde(default)]
1772    pub expected_sha256: Option<String>,
1773}
1774
1775/// The effective per-request image decoration of a random request source
1776/// ([[RFC-0004:C-BENCH-REQUEST-SOURCES]]): fixed pixel dimensions and the
1777/// per-request image count. Images attach at measurement-run time on the
1778/// chat-completions route and never enter the frozen population.
1779#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1780#[serde(deny_unknown_fields)]
1781pub struct BenchImagesInput {
1782    /// The fixed image width in pixels.
1783    pub width: u32,
1784    /// The fixed image height in pixels.
1785    pub height: u32,
1786    /// The effective number of images attached to each request.
1787    pub count: u32,
1788    /// The image supply; absent selects the measurement runtime's synthetic
1789    /// noise image supply.
1790    #[serde(default)]
1791    pub source: Option<BenchImageSourceInput>,
1792}
1793
1794/// One operator-supplied image directory binding for the random request
1795/// source's image decoration ([[RFC-0004:C-BENCH-REQUEST-SOURCES]]).
1796#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1797#[serde(deny_unknown_fields)]
1798pub struct BenchImageSourceInput {
1799    /// Workspace-relative directory path as declared.
1800    pub path: String,
1801    /// Absolute resolution of `path` against the workspace root.
1802    pub resolved_path: PathBuf,
1803    /// The declared digest binding over the release-owned recursive
1804    /// enumeration digest of the directory.
1805    #[serde(default)]
1806    pub expected_sha256: Option<String>,
1807    /// The effective source-image sampling policy.
1808    pub sampling: BenchImageSamplingInput,
1809}
1810
1811/// The closed source-image sampling vocabulary
1812/// ([[RFC-0004:C-BENCH-REQUEST-SOURCES]]).
1813#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1814#[serde(rename_all = "kebab-case")]
1815pub enum BenchImageSamplingInput {
1816    /// Draw each source image independently; repeats may occur immediately.
1817    RandomWithReplacement,
1818    /// Draw every source image once per shuffled cycle, reshuffling after
1819    /// exhaustion.
1820    ShuffleCycle,
1821    /// Walk source images in sorted load order and wrap after exhaustion.
1822    SequentialCycle,
1823}
1824
1825/// One exact ISL/OSL pair and its relative categorical sampling weight.
1826#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1827#[serde(deny_unknown_fields)]
1828pub struct BenchRandomShapeInput {
1829    pub input_tokens: u32,
1830    pub output_tokens: u32,
1831    pub weight: u32,
1832}
1833
1834/// Immutable release-catalog facts resolved by the Rust control plane.
1835#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1836#[serde(deny_unknown_fields)]
1837pub struct BenchDatasetCatalogInput {
1838    pub dataset: String,
1839    #[serde(default)]
1840    pub profile: Option<String>,
1841    pub source: String,
1842    pub upstream_identity: String,
1843    pub url: String,
1844    pub sha256: String,
1845    pub source_format: String,
1846    pub aiperf_format: String,
1847    #[serde(default)]
1848    pub configuration: Option<String>,
1849    #[serde(default)]
1850    pub split: Option<String>,
1851    #[serde(default)]
1852    pub filter: Option<BenchDatasetFilterInput>,
1853    pub license: String,
1854    pub cache_path: PathBuf,
1855    pub cache_state: BenchDatasetCacheState,
1856    pub materialization_identity: String,
1857    pub provides_output_targets: bool,
1858}
1859
1860#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1861#[serde(deny_unknown_fields)]
1862pub struct BenchDatasetFilterInput {
1863    pub field: String,
1864    pub value: String,
1865}
1866
1867/// Read-only cache state observed while resolving the Bench plan.
1868#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1869#[serde(rename_all = "snake_case")]
1870pub enum BenchDatasetCacheState {
1871    Missing,
1872    Present,
1873}
1874
1875/// One release-qualified population of dependent linear session templates.
1876#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
1877#[serde(deny_unknown_fields)]
1878pub struct BenchSessionSourceInput {
1879    pub dataset: String,
1880    #[serde(default)]
1881    pub profile: Option<String>,
1882    pub max_input_tokens: u32,
1883    pub output_tokens: Option<u32>,
1884    pub inter_turn_delay_scale: f64,
1885    pub max_inter_turn_delay_seconds: Option<f64>,
1886    pub catalog: Box<BenchSessionDatasetCatalogInput>,
1887}
1888
1889/// Immutable release-catalog facts for a linear-session materializer. AIPerf
1890/// loader names are deliberately absent from this source boundary.
1891#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
1892#[serde(deny_unknown_fields)]
1893pub struct BenchSessionDatasetCatalogInput {
1894    pub dataset: String,
1895    #[serde(default)]
1896    pub profile: Option<String>,
1897    pub source: String,
1898    pub upstream_identity: String,
1899    pub url: String,
1900    pub sha256: String,
1901    pub source_format: String,
1902    #[serde(default)]
1903    pub configuration: Option<String>,
1904    #[serde(default)]
1905    pub split: Option<String>,
1906    #[serde(default)]
1907    pub filter: Option<BenchDatasetFilterInput>,
1908    pub license: String,
1909    pub cache_path: PathBuf,
1910    pub cache_state: BenchDatasetCacheState,
1911    pub materialization_identity: String,
1912    pub provides_output_targets: bool,
1913}
1914
1915/// One release-qualified agentic trace source and its complete effective policy.
1916#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
1917#[serde(deny_unknown_fields)]
1918pub struct BenchAgenticSourceInput {
1919    pub dataset: String,
1920    pub profile: String,
1921    pub catalog: Box<BenchAgenticCatalogInput>,
1922}
1923
1924#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
1925#[serde(deny_unknown_fields)]
1926pub struct BenchAgenticCatalogInput {
1927    pub repository: String,
1928    pub revision: String,
1929    pub filename: String,
1930    pub sha256: String,
1931    #[serde(default)]
1932    pub cache_path: Option<PathBuf>,
1933    #[serde(default)]
1934    pub cache_state: Option<BenchDatasetCacheState>,
1935    pub trace_count: u32,
1936    pub approximate_bytes: u64,
1937    pub license: String,
1938    pub source_format: String,
1939    pub aiperf_loader: String,
1940    pub materialization_identity: String,
1941    pub scenario: String,
1942    pub concurrency_semantics: String,
1943    pub replay_semantics: String,
1944    pub cache_bust: String,
1945    pub trajectory_start_min: f64,
1946    pub trajectory_start_max: f64,
1947    pub global_idle_gap_cap_seconds: f64,
1948    pub trace_idle_gap_cap_seconds: f64,
1949    pub cache_warmup_requests_per_lane: u64,
1950    pub warmup_grace_seconds: u64,
1951    pub dataset_configuration_timeout_seconds: u64,
1952    pub service_profile_configuration_timeout_seconds: u64,
1953    pub default_duration_seconds: u64,
1954    pub minimum_duration_seconds: u64,
1955    pub failure_threshold: f64,
1956    pub dataset_entries: u32,
1957    pub streaming: bool,
1958    pub ignore_eos: bool,
1959    pub use_server_token_count: bool,
1960    pub gpu_telemetry: bool,
1961    pub server_metric_slice_seconds: u64,
1962    pub required_artifacts: Vec<String>,
1963    pub unavailable_dimensions: Vec<String>,
1964    pub inferencex_repository: String,
1965    pub inferencex_revision: String,
1966    pub inferencex_reference: String,
1967    pub aiperf_revision: String,
1968    pub aiperf_version: String,
1969}
1970
1971/// A single Bench case: its load shape and the number of requests to send.
1972#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
1973#[serde(deny_unknown_fields)]
1974pub struct BenchCaseInput {
1975    pub load_shape: BenchLoadInput,
1976    pub request_count: u32,
1977    #[serde(default)]
1978    pub warmup_request_count: u32,
1979    #[serde(default)]
1980    pub duration_seconds: Option<u64>,
1981    #[serde(default)]
1982    pub session_count: Option<u32>,
1983    #[serde(default)]
1984    pub warmup_session_count: Option<u32>,
1985}
1986
1987/// How a Bench case paces its requests.
1988#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
1989#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
1990pub enum BenchLoadInput {
1991    /// A fixed number of in-flight requests.
1992    ConcurrencyLimited { concurrency: u32 },
1993    /// A target arrival rate, optionally shaped by a burstiness factor.
1994    RequestRateLimited {
1995        request_rate: f64,
1996        burstiness: Option<f64>,
1997    },
1998    /// All requests issued as fast as possible.
1999    UnboundedRequestRate,
2000}
2001
2002/// Per-request latency bounds lowered to the release-owned Bench runner.
2003#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
2004#[serde(deny_unknown_fields)]
2005pub struct BenchRequestSloInput {
2006    #[serde(default)]
2007    pub request_latency_ms: Option<f64>,
2008    #[serde(default)]
2009    pub ttft_ms: Option<f64>,
2010    #[serde(default)]
2011    pub tpot_ms: Option<f64>,
2012    pub minimum_good_request_ratio: f64,
2013}
2014
2015/// One frozen dataset population consumed sequentially by every Bench case.
2016#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
2017#[serde(deny_unknown_fields)]
2018pub struct BenchPopulationInput {
2019    pub path: PathBuf,
2020    pub evidence_path: PathBuf,
2021    pub sha256: String,
2022    pub entries: u32,
2023    pub tpot_applicable: bool,
2024    #[serde(default)]
2025    pub session_templates: Vec<BenchSessionTemplateInput>,
2026}
2027
2028/// One frozen linear-template summary needed to assign case slices without
2029/// duplicating the content-bearing population evidence artifact.
2030#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
2031#[serde(deny_unknown_fields)]
2032pub struct BenchSessionTemplateInput {
2033    pub template_identity: String,
2034    pub turn_count: u32,
2035}
2036
2037/// The bounded tokenizer-backed operation that freezes one request population
2038/// before any Bench case starts.
2039#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
2040#[serde(deny_unknown_fields)]
2041pub struct BenchPopulationPreparationRequest {
2042    pub protocol_version: ProtocolVersion,
2043    pub model: MeasurementModelInput,
2044    pub tokenizer_backend: String,
2045    pub transformers_version: String,
2046    #[serde(default)]
2047    pub request_source: Option<BenchRequestSourceInput>,
2048    #[serde(default)]
2049    pub session_source: Option<BenchSessionSourceInput>,
2050    pub prompt: BenchPromptInput,
2051    pub cache_start: BenchCacheStartInput,
2052    #[serde(default)]
2053    pub source_path: Option<PathBuf>,
2054    pub required_entries: u32,
2055    pub seed: u64,
2056    #[serde(default)]
2057    pub request_body: BTreeMap<String, SettingValue>,
2058    pub artifact_dir: PathBuf,
2059}
2060
2061/// Summary of the realized token counts. Exact per-entry values remain in the
2062/// population evidence artifact.
2063#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
2064#[serde(deny_unknown_fields)]
2065pub struct BenchTokenCountSummary {
2066    pub minimum: u32,
2067    pub maximum: u32,
2068    pub mean: f64,
2069}
2070
2071/// Where the concrete template used for local prompt-length projection came
2072/// from. The model server remains authoritative for transport rendering.
2073#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
2074#[serde(rename_all = "snake_case")]
2075pub enum BenchPromptTemplateSource {
2076    RequestBody,
2077    PromptTable,
2078    TokenizerDefault,
2079}
2080
2081/// The concrete template used only for local complete-prompt projection.
2082#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
2083#[serde(deny_unknown_fields)]
2084pub struct BenchPromptTemplateProjection {
2085    pub source: BenchPromptTemplateSource,
2086    pub content: String,
2087    pub sha256: String,
2088}
2089
2090/// Summary of best-effort complete-prompt targeting for a synthetic population.
2091#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
2092#[serde(deny_unknown_fields)]
2093pub struct BenchPromptTokenTargetingSummary {
2094    pub selected_prompt_tokens: BenchTokenCountSummary,
2095    pub pre_template_content_tokens: BenchTokenCountSummary,
2096    #[serde(default)]
2097    pub projection_template: Option<BenchPromptTemplateProjection>,
2098    pub exact_entries: u32,
2099    pub fallback_entries: u32,
2100    #[serde(default)]
2101    pub fallback_reasons: BTreeMap<String, u32>,
2102}
2103
2104/// Resolved exact final-prompt prefix geometry across one frozen population.
2105#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
2106#[serde(deny_unknown_fields)]
2107pub struct BenchPrefixGeometrySummary {
2108    pub shared_prefix_tokens: BenchTokenCountSummary,
2109    pub unique_suffix_tokens: BenchTokenCountSummary,
2110    pub maximum_shared_prefix_tokens: u32,
2111    pub canonical_prefix_sha256: String,
2112    pub full_prompt_entries: u32,
2113}
2114
2115/// File-bound exact canonical prefix used to condition a primed cache start.
2116#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
2117#[serde(deny_unknown_fields)]
2118pub struct BenchPrefixConditioningInput {
2119    pub path: PathBuf,
2120    pub sha256: String,
2121    pub prompt_tokens: u32,
2122}
2123
2124/// Resolved pre-template shared system-content shape across one frozen
2125/// server-chat population.
2126#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
2127#[serde(deny_unknown_fields)]
2128pub struct BenchSharedSystemContentSummary {
2129    pub system_content_tokens: BenchTokenCountSummary,
2130    pub user_content_tokens: BenchTokenCountSummary,
2131    pub canonical_system_content_sha256: String,
2132}
2133
2134/// Terminal result of request-population materialization.
2135#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
2136#[serde(deny_unknown_fields)]
2137pub struct BenchPopulationPreparationResult {
2138    pub schema_version: u32,
2139    pub status: ClientStatus,
2140    pub materialization_identity: String,
2141    pub requested_entries: u32,
2142    pub candidate_entries: u64,
2143    pub admitted_entries: u64,
2144    pub ineligible_entries: u64,
2145    #[serde(default)]
2146    pub ineligible_reasons: BTreeMap<String, u64>,
2147    pub population: Option<BenchPopulationInput>,
2148    pub input_tokens: Option<BenchTokenCountSummary>,
2149    pub output_tokens: Option<BenchTokenCountSummary>,
2150    #[serde(default)]
2151    pub prompt_token_targeting: Option<BenchPromptTokenTargetingSummary>,
2152    #[serde(default)]
2153    pub prefix_geometry: Option<BenchPrefixGeometrySummary>,
2154    #[serde(default)]
2155    pub prefix_conditioning: Option<BenchPrefixConditioningInput>,
2156    #[serde(default)]
2157    pub shared_system_content: Option<BenchSharedSystemContentSummary>,
2158    pub evidence_path: Option<PathBuf>,
2159    pub error: Option<String>,
2160}
2161
2162/// The result a Bench client writes for the measurement runtime to consume.
2163#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
2164#[serde(deny_unknown_fields)]
2165pub struct BenchClientResult {
2166    /// Result envelope version; clients write `1`. The measurement runtime
2167    /// rejects a bench result whose version is not `1`.
2168    pub schema_version: u32,
2169    pub status: ClientStatus,
2170    pub completed_requests: u64,
2171    pub failed_requests: u64,
2172    pub normalization_schema: String,
2173    pub metrics: BTreeMap<String, f64>,
2174    #[serde(default)]
2175    pub request_slo: Option<BenchRequestSloResult>,
2176    #[serde(default)]
2177    pub session_evidence: Option<BenchSessionResultEvidence>,
2178    #[serde(default)]
2179    pub agentic_evidence: Option<Box<BenchAgenticResultEvidence>>,
2180    #[serde(default)]
2181    pub prompt_token_reconciliation: Vec<BenchPromptTokenReconciliation>,
2182    #[serde(default)]
2183    pub prompt_cache_observations: Vec<BenchPromptCacheObservation>,
2184    pub native_command: Vec<String>,
2185    pub native_exit_code: Option<i32>,
2186    #[serde(default)]
2187    pub report_invocations: Vec<BenchNativeInvocation>,
2188    pub raw_artifacts: Vec<RawArtifact>,
2189    pub error: Option<String>,
2190}
2191
2192/// Backend-reported prompt/cache token accounting for one completed profiling request.
2193#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
2194#[serde(deny_unknown_fields)]
2195pub struct BenchPromptCacheObservation {
2196    pub request_id: u64,
2197    pub prompt_tokens: u64,
2198    pub cache_read_tokens: u64,
2199    pub uncached_prompt_tokens: u64,
2200    pub cache_read_ratio: f64,
2201}
2202
2203#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
2204#[serde(deny_unknown_fields)]
2205pub struct BenchAgenticSourceVerification {
2206    pub repository: String,
2207    pub expected_revision: String,
2208    #[serde(default)]
2209    pub observed_revision: Option<String>,
2210    pub filename: String,
2211    pub expected_sha256: String,
2212    #[serde(default)]
2213    pub observed_sha256: Option<String>,
2214    #[serde(default)]
2215    pub cache_path: Option<PathBuf>,
2216    #[serde(default)]
2217    pub cache_state_before: Option<BenchDatasetCacheState>,
2218    #[serde(default)]
2219    pub acquisition_outcome: Option<BenchAgenticAcquisitionOutcome>,
2220}
2221
2222#[derive(Clone, Copy, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
2223#[serde(rename_all = "snake_case")]
2224pub enum BenchAgenticAcquisitionOutcome {
2225    Reused,
2226    Downloaded,
2227}
2228
2229#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
2230#[serde(deny_unknown_fields)]
2231pub struct BenchAgenticBranchStats {
2232    pub children_spawned: u64,
2233    pub children_completed: u64,
2234    pub children_errored: u64,
2235    pub children_truncated: u64,
2236    pub children_delayed: u64,
2237    pub parents_suspended: u64,
2238    pub parents_resumed: u64,
2239    pub parents_failed_due_to_child_error: u64,
2240    pub joins_suppressed: u64,
2241}
2242
2243#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
2244#[serde(deny_unknown_fields)]
2245pub struct BenchAgenticResultEvidence {
2246    pub source: BenchAgenticSourceVerification,
2247    #[serde(default)]
2248    pub run: Option<Box<BenchAgenticRunEvidence>>,
2249}
2250
2251#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
2252#[serde(deny_unknown_fields)]
2253pub struct BenchAgenticRunEvidence {
2254    pub native_run_id: String,
2255    pub scenario: String,
2256    pub submission_valid: bool,
2257    #[serde(default)]
2258    pub submission_invalid_reasons: Vec<String>,
2259    pub warmup_records: u64,
2260    pub warmup_error_records: u64,
2261    /// Whether the native run crossed the warmup phase and entered profiling
2262    /// (`profiling_records > 0`); snapshot-warmup failure aborts natively
2263    /// before profiling and therefore surfaces as an invalid submission.
2264    pub warmup_succeeded: bool,
2265    pub profiling_records: u64,
2266    pub distinct_runtime_conversations: u64,
2267    pub distinct_transport_requests: u64,
2268    pub context_overflow_count: u64,
2269    pub ordinary_failure_count: u64,
2270    pub branch_stats: BenchAgenticBranchStats,
2271    pub aggregate_artifact: PathBuf,
2272    /// The raw request/response artifact; absent at the `performance`
2273    /// artifact level, where raw export is not requested.
2274    #[serde(default)]
2275    pub raw_records_artifact: Option<PathBuf>,
2276    #[serde(default)]
2277    pub unavailable_dimensions: Vec<String>,
2278    /// Warmup records whose raw-derived source coordinates were observed;
2279    /// absent when the artifact level makes that mapping unavailable.
2280    #[serde(default)]
2281    pub warmup_source_coordinate_records: Option<u64>,
2282    /// Profiling records whose raw-derived source coordinates were observed;
2283    /// absent when the artifact level makes that mapping unavailable.
2284    #[serde(default)]
2285    pub source_coordinate_records: Option<u64>,
2286    /// Distinct source traces identified through the raw-derived coordinate
2287    /// mapping; absent when the artifact level makes it unavailable.
2288    #[serde(default)]
2289    pub distinct_source_traces: Option<u64>,
2290    /// Profiling records carrying a raw cache-bust marker observation;
2291    /// absent when the artifact level makes that observation unavailable.
2292    #[serde(default)]
2293    pub cache_bust_records: Option<u64>,
2294}
2295
2296/// One synthetic profiling request's planned-to-observed prompt-token check.
2297#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
2298#[serde(deny_unknown_fields)]
2299pub struct BenchPromptTokenReconciliation {
2300    pub population_index: u32,
2301    pub native_session_num: u64,
2302    pub planned_prompt_tokens: u32,
2303    #[serde(default)]
2304    pub observed_prompt_tokens: Option<u32>,
2305    pub reconciled: bool,
2306}
2307
2308/// Reconciled native evidence for one session-bounded Bench phase.
2309#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
2310#[serde(deny_unknown_fields)]
2311pub struct BenchSessionPhaseSummary {
2312    pub planned_sessions: u32,
2313    pub started_sessions: u32,
2314    pub succeeded_sessions: u32,
2315    pub failed_sessions: u32,
2316    pub planned_requests: u32,
2317    pub attempted_requests: u32,
2318    pub completed_requests: u32,
2319    pub failed_requests: u32,
2320    pub reconciled: bool,
2321}
2322
2323/// Terminal evidence for one admitted runtime session.
2324#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
2325#[serde(deny_unknown_fields)]
2326pub struct BenchRuntimeSessionResult {
2327    pub phase: String,
2328    pub runtime_session_id: String,
2329    pub template_identity: String,
2330    pub planned_turns: u32,
2331    pub attempted_turns: u32,
2332    pub status: ClientStatus,
2333    #[serde(default)]
2334    pub failure_classification: Option<String>,
2335    #[serde(default)]
2336    pub diagnostic: Option<String>,
2337    #[serde(default)]
2338    pub failing_turn: Option<u32>,
2339    pub suppressed_later_turns: u32,
2340}
2341
2342/// One native transport request reconciled to a linear-session turn.
2343#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
2344#[serde(deny_unknown_fields)]
2345pub struct BenchSessionTurnResult {
2346    pub phase: String,
2347    pub runtime_session_id: String,
2348    pub turn_index: u32,
2349    /// Measured from the raw request payload; absent when the artifact level
2350    /// does not produce the raw request/response artifact.
2351    #[serde(default)]
2352    pub pre_template_content_tokens: Option<u32>,
2353    #[serde(default)]
2354    pub observed_prompt_tokens: Option<u32>,
2355    pub native_session_num: u64,
2356    #[serde(default)]
2357    pub preceding_native_session_num: Option<u64>,
2358    #[serde(default)]
2359    pub preceding_terminal_response_receipt_ns: Option<u64>,
2360    #[serde(default)]
2361    pub effective_inter_turn_delay_seconds: Option<f64>,
2362    pub request_start_ns: u64,
2363    #[serde(default)]
2364    pub inter_turn_delay_reconciled: Option<bool>,
2365    #[serde(default)]
2366    pub post_failure_continuation: bool,
2367    pub native_artifact_name: String,
2368}
2369
2370/// Session and transport reconciliation returned by the Bench client.
2371#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
2372#[serde(deny_unknown_fields)]
2373pub struct BenchSessionResultEvidence {
2374    pub warmup: BenchSessionPhaseSummary,
2375    pub profiling: BenchSessionPhaseSummary,
2376    pub sessions: Vec<BenchRuntimeSessionResult>,
2377    pub turns: Vec<BenchSessionTurnResult>,
2378    pub population_slice_reconciled: bool,
2379    pub sessions_reconciled: bool,
2380    pub turn_order_reconciled: bool,
2381    pub inter_turn_delays_reconciled: bool,
2382    /// Whether raw requests reconcile to normalized metric records; absent at
2383    /// the `performance` artifact level, where no raw artifact exists.
2384    #[serde(default)]
2385    pub native_requests_reconciled: Option<bool>,
2386    pub counts_reconciled: bool,
2387    /// Evidence dimensions recorded as unavailable due to the effective
2388    /// artifact level ([[RFC-0004:C-BENCH-ARTIFACT-LEVEL]]).
2389    #[serde(default)]
2390    pub unavailable_dimensions: Vec<String>,
2391}
2392
2393/// One bounded native post-processing command and its terminal outcome.
2394#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
2395#[serde(deny_unknown_fields)]
2396pub struct BenchNativeInvocation {
2397    pub purpose: String,
2398    pub command: Vec<String>,
2399    pub exit_code: Option<i32>,
2400    pub interrupted: bool,
2401    pub timed_out: bool,
2402}
2403
2404/// File-bound request-SLO evidence derived from AIPerf profiling records.
2405#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
2406#[serde(deny_unknown_fields)]
2407pub struct BenchRequestSloResult {
2408    pub good_requests: u64,
2409    pub good_request_ratio: f64,
2410    pub goodput: f64,
2411    pub profiling_duration_seconds: f64,
2412    pub profiling_duration_source: String,
2413    pub request_count_reconciled: bool,
2414    #[serde(default)]
2415    pub native_aggregate_good_request_count: Option<u64>,
2416    #[serde(default)]
2417    pub native_aggregate_good_request_count_consistent: Option<bool>,
2418}
2419
2420// Schema roots.
2421
2422/// The schema root for the independently released framework adapter SDK.
2423#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
2424#[serde(deny_unknown_fields)]
2425pub struct AdapterProtocol {
2426    pub request: AdapterRequest,
2427    pub response: AdapterResponse,
2428}
2429
2430/// The schema root aggregating the request and result surfaces used by the
2431/// product-owned Eval and Bench measurement clients. Its optional fields are
2432/// code-generation anchors and are never all populated in one message.
2433#[derive(Clone, Debug, Deserialize, JsonSchema, PartialEq, Serialize)]
2434#[serde(deny_unknown_fields)]
2435pub struct MeasurementProtocol {
2436    #[serde(default, skip_serializing_if = "Option::is_none")]
2437    pub eval_client_request: Option<EvalClientRequest>,
2438    #[serde(default, skip_serializing_if = "Option::is_none")]
2439    pub eval_client_result: Option<EvalClientResult>,
2440    #[serde(default, skip_serializing_if = "Option::is_none")]
2441    pub bench_client_request: Option<BenchClientRequest>,
2442    #[serde(default, skip_serializing_if = "Option::is_none")]
2443    pub bench_client_result: Option<BenchClientResult>,
2444    #[serde(default, skip_serializing_if = "Option::is_none")]
2445    pub bench_population_preparation_request: Option<BenchPopulationPreparationRequest>,
2446    #[serde(default, skip_serializing_if = "Option::is_none")]
2447    pub bench_population_preparation_result: Option<BenchPopulationPreparationResult>,
2448    #[serde(default, skip_serializing_if = "Option::is_none")]
2449    pub data_asset_preparation_request: Option<MeasurementDataAssetPreparationRequest>,
2450    #[serde(default, skip_serializing_if = "Option::is_none")]
2451    pub data_asset_preparation_result: Option<MeasurementDataAssetPreparationResult>,
2452}