Skip to main content

jugar_probar/perf_gate/
drain.rs

1//! PP-LLAMA-001 v3.0 §5.1 / §7.4 — band derivation: boundary effects, drain,
2//! the four request counters that must never be conflated, the correctness and
3//! streaming witnesses, and the status the band ends up with.
4//!
5//! # Why this exists
6//!
7//! `scripts/perf_gate.sh:42` fails any receipt whose `drain_ms` is absent. On
8//! `62d23d8d1`, `grep -rn "drain_ms" --include="*.rs" crates` returned **zero
9//! lines**: nothing in the workspace could produce the field, so Arm C rejected
10//! every receipt that could ever be measured. A gate that can only fail is the
11//! mirror of one that can only pass.
12//!
13//! # What `drain_ms` MEANS (PP-10), stated before it is computed
14//!
15//! The measurement window opens at offset `0` and **closes at `T`**. No new
16//! request is issued at or after `T` (PP-10). Every request issued before `T` is
17//! then *drained* — allowed to run on past `T` to completion or timeout.
18//!
19//! > `drain_ms` = (last settlement of any pre-`T` request) − `T`, clamped at 0.
20//!
21//! It is the length of the **drain phase**, not a property of any one request,
22//! and it is `0` when nothing was still in flight at `T`. The `SUSPECT` rule
23//! reads it exactly that way: `drain_ms > 0.5 × window` means one request
24//! dominated the window and the band must be re-run longer.
25//!
26//! # The conflation this module refuses to make
27//!
28//! "A request that timed out during drain increments `timeouts`; one
29//! **abandoned at drain deadline** increments `truncated`."
30//!
31//! `truncated` therefore means *the drain deadline arrived while this request
32//! was still running*. It does **not** mean `finish_reason == "length"`.
33//! W1 (§5.1) generates with `n_predict = 128` and **EOS ignored**, so every
34//! single healthy W1 request ends with `finish_reason == "length"`. `agg`'s
35//! numerator is over "completed, **non-truncated**" requests — so reading
36//! `truncated` in the finish-reason sense empties the numerator and reports
37//! `0 tok/s` for a perfectly healthy server. The two senses are named apart
38//! here on purpose: [`Outcome::AbandonedAtDrain`] is the drain sense and is the
39//! only thing that increments `truncated`.
40//!
41//! # Timeouts are their own counter, and it is checked, not just named
42//!
43//! §3 fixes a hard **120 s per request**. [`Outcome::Timeout`] and
44//! [`Outcome::Failed`] are distinct counters (PP-5 makes `timeouts > 0` fatal to
45//! a band's ratio, while a transport error is a different fault), and
46//! [`BandInput::derive`] *verifies* the label against the request's own
47//! duration: a `Timeout` that did not reach the timeout, or a `Failed` that
48//! exceeded it, is refused rather than counted.
49//!
50//! # v3: three witnesses a band must carry, and what happens without them
51//!
52//! | witness | rule | absent or failing |
53//! |---|---|---|
54//! | [`BatchInvarianceWitness`] (PP-26) | the tokens were right | `INVALID-CORRECTNESS` at `c > 1`; **no** `agg`/`dec`/`prefill` written |
55//! | [`StreamMode`] + [`StreamWitness`] (PP-27) | the stream was live | `dec`/`ttft`/`itl` move to `unproduced`; `NONCONFORMANT-VALID` |
56//! | `n_predict` (PP-28) | every retained sample ran to length | `short_of_n_predict > 0`; `NONCONFORMANT-VALID` |
57//!
58//! A failing witness does **not** make [`BandInput::derive`] return `Err`. The
59//! band still renders, with the numbers it is entitled to and a status that
60//! says what it lacks — because the evidence of a bad run is the point of
61//! keeping it (Appendix C's `validity_by_band`). Only a band that contradicts
62//! its own clock (a request issued after `T`, a mislabelled timeout) is refused
63//! outright: that is not a bad measurement, it is not a measurement.
64//!
65//! # Nothing here is defaulted
66//!
67//! Every number below is derived from per-request timestamps supplied by the
68//! caller. There is no constructor that accepts a `drain_ms` scalar, because a
69//! caller-supplied `drain_ms` is indistinguishable from a fabricated one — the
70//! same rule `scripts/lib/bench_receipt.py` already applies to ratios ("a stated
71//! ratio that its own samples do not produce is a fabricated measurement").
72
73use serde::{Deserialize, Serialize};
74
75use super::bootstrap::{median_decode_tok_s, paired_ratio_lcb};
76use super::join::{BandRatios, JoinKey, Ratio, RatioMethod};
77use super::metrics::RequestSample;
78use super::protocol::{stream_live_ttft_over_e2e_max, INTERLEAVED, REPLICATES};
79use super::receipt::RunId;
80use super::replicate::MIN_REPLICATES;
81use super::samples::SamplesFile;
82use super::witness::BatchInvarianceWitness;
83
84/// §3 — the hard per-request timeout, in milliseconds.
85pub const REQUEST_TIMEOUT_MS: f64 = 120_000.0;
86
87/// PP-10 — `drain_ms > DRAIN_SUSPECT_FRACTION × window_ms` is annotated `SUSPECT`.
88pub const DRAIN_SUSPECT_FRACTION: f64 = 0.5;
89
90/// The receipt schema version this producer writes. `3` is PP-LLAMA-001 v3.0;
91/// a receipt without the key is version 2 and is historical (PP-4).
92pub const SCHEMA_VERSION: u32 = 3;
93
94/// P-5 — the one-sided confidence the verdict is taken at.
95pub const VERDICT_CONFIDENCE: f64 = 0.95;
96
97/// How one sampled request ended. The four variants are mutually exclusive, so
98/// `requested == completed + timeouts + truncated + errors` holds by
99/// construction rather than by convention.
100#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
101#[serde(rename_all = "snake_case")]
102pub enum Outcome {
103    /// Returned a usable response, inside the window or during the drain.
104    Completed,
105    /// Reached the §3 hard 120 s timeout.
106    Timeout,
107    /// Still running when the drain deadline arrived (PP-10). Increments
108    /// `truncated`. **Not** `finish_reason == "length"` — see the module docs.
109    AbandonedAtDrain,
110    /// Any other fault: transport, non-2xx, unparseable body. Counted apart
111    /// from [`Outcome::Timeout`] because they are different defects.
112    Failed,
113}
114
115/// PP-27 — what the **server** declared on the first SSE chunk.
116#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
117#[serde(rename_all = "snake_case")]
118pub enum StreamMode {
119    /// Tokens were emitted as they were produced.
120    Live,
121    /// The answer was produced first and replayed as a stream. Every
122    /// client-side latency metric is then a property of the replay, not of the
123    /// server.
124    Replayed,
125}
126
127/// PP-27 — the client's own verdict, independent of what the server declared.
128#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
129#[serde(rename_all = "snake_case")]
130pub enum StreamVerdict {
131    /// The stream is live: either the server declared `live` and
132    /// `median(ttft / e2e)` agrees, or the server declared nothing and the
133    /// client's own ratio establishes it.
134    Live,
135    /// Server said `replayed`, or said `live` and the ratio contradicts it.
136    Replayed,
137    /// The server declared nothing **and** the client's ratio does not
138    /// establish liveness either. Not the same as `replayed` — nothing said
139    /// the answer was pre-computed — and not a pass: no half of the dual
140    /// witness supports a latency metric.
141    Undeclared,
142}
143
144/// PP-27 — which half of the dual witness the verdict rests on.
145///
146/// Upstream `llama-server` declares no `stream_mode` on its SSE chunks and is
147/// not going to start. Reading "undeclared" as "not live" made **every**
148/// comparator band `NONCONFORMANT-VALID`, so no baseline could ever be
149/// conformant and the parity arm could never reach a verdict — a rule about a
150/// field the oracle does not emit, dressed as a finding about the oracle.
151/// The client's `median(ttft / e2e)` is a measurement of the same fact, so it
152/// stands on its own; this field records that it had to.
153#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
154#[serde(rename_all = "snake_case")]
155pub enum StreamWitnessSource {
156    /// The server declared a mode and the client did not contradict it.
157    Server,
158    /// The server declared nothing, or declared `live` and the client's ratio
159    /// overruled it. Either way the verdict is the client's measurement.
160    Client,
161}
162
163/// PP-27 — the client-side half of the dual witness.
164///
165/// On a live stream the first token arrives long before the last, so
166/// `ttft / e2e` is small. On a replayed one the whole answer lands at once and
167/// the ratio approaches 1. The threshold is
168/// `stream.live_ttft_over_e2e_max` in `perf-matrix.yaml` (PP-33).
169#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
170#[serde(deny_unknown_fields)]
171pub struct StreamWitness {
172    /// Median over completed requests of `ttft_ms / (settled_ms − issued_ms)`.
173    pub client_ttft_over_e2e_median: f64,
174    /// The verdict the two halves reach together.
175    pub verdict: StreamVerdict,
176    /// Which half the verdict rests on.
177    pub source: StreamWitnessSource,
178}
179
180/// §7.4 — the band status vocabulary. Six tokens, no more.
181///
182/// `Skip` is not a status. `SUSPECT_DISPATCH` is not a status; a dispatch
183/// anomaly is a finding with a mechanism, and this band carries `suspect[]`
184/// annotations for it instead.
185#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
186#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
187pub enum BandStatus {
188    /// Conformant receipt, fresh pin, PP-26 passed, a baseline present.
189    Measured,
190    /// Temporary; in the denominator. Needs an owner.
191    Unmeasured,
192    /// Permanent; out of the denominator. Needs `decided_by`.
193    Na,
194    /// PP-26 absent or failed on this band. Can never be a baseline.
195    InvalidCorrectness,
196    /// Historical record; cited, never a baseline.
197    NonconformantValid,
198    /// PP-20 tripped: the comparator pin expired before the run started.
199    ComparatorStale,
200}
201
202impl BandStatus {
203    /// The §7.4 wire token. Spelled out rather than derived from the variant
204    /// name because two of them are not the variant name
205    /// (`NA`, `INVALID-CORRECTNESS`), and a reader of the receipt matches these
206    /// strings exactly.
207    #[must_use]
208    pub fn wire_token(self) -> &'static str {
209        match self {
210            Self::Measured => "MEASURED",
211            Self::Unmeasured => "UNMEASURED",
212            Self::Na => "NA",
213            Self::InvalidCorrectness => "INVALID-CORRECTNESS",
214            Self::NonconformantValid => "NONCONFORMANT-VALID",
215            Self::ComparatorStale => "COMPARATOR_STALE",
216        }
217    }
218
219    /// The §7.4 vocabulary, in table order. The single source for the
220    /// vocabulary test and for any validator that needs the closed set.
221    #[must_use]
222    pub fn vocabulary() -> [Self; 6] {
223        [
224            Self::Measured,
225            Self::Unmeasured,
226            Self::Na,
227            Self::InvalidCorrectness,
228            Self::NonconformantValid,
229            Self::ComparatorStale,
230        ]
231    }
232
233    /// P-4 / §7.4 — may a band with this status be a comparator baseline?
234    #[must_use]
235    pub fn baseline_eligible(self) -> bool {
236        self == Self::Measured
237    }
238
239    /// §7.4 — where this status sits in the precedence order. **Lower wins.**
240    ///
241    /// `INVALID-CORRECTNESS > COMPARATOR_STALE > NA > NONCONFORMANT-VALID >
242    /// UNMEASURED > MEASURED`, defined **once** here.
243    ///
244    /// The order is not arbitrary and each step is a different question:
245    ///
246    /// - `INVALID-CORRECTNESS` first, because §7.0 asks "were the tokens
247    ///   right?" before "how fast?". A `c > 1` band with no passing witness has
248    ///   no throughput at all, and a fresher comparator pin does not give it
249    ///   one — which is exactly the inversion this rank fixes: the render pass
250    ///   used to stamp `COMPARATOR_STALE` over it, and a band that reported no
251    ///   numbers came out labelled as if its only problem were an expired pin.
252    /// - `COMPARATOR_STALE` next: PP-20 blocks a band that would OTHERWISE have
253    ///   been `MEASURED`/`UNMEASURED`, and says so rather than hiding it under
254    ///   the weaker tokens below.
255    /// - `NA` above `NONCONFORMANT-VALID`: an `NA` band is permanently out of
256    ///   the denominator — usually because it never ran — and "the band that
257    ///   did not run did not interleave" is not a finding about the run.
258    #[must_use]
259    pub fn rank(self) -> u8 {
260        match self {
261            Self::InvalidCorrectness => 0,
262            Self::ComparatorStale => 1,
263            Self::Na => 2,
264            Self::NonconformantValid => 3,
265            Self::Unmeasured => 4,
266            Self::Measured => 5,
267        }
268    }
269
270    /// The stronger of two statuses under [`Self::rank`].
271    ///
272    /// Every place that has to combine two verdicts goes through this, so the
273    /// precedence cannot be spelled twice and drift.
274    #[must_use]
275    pub fn stronger_of(self, other: Self) -> Self {
276        if other.rank() < self.rank() {
277            other
278        } else {
279            self
280        }
281    }
282}
283
284/// PP-24 — which lane capped admission.
285#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
286#[serde(rename_all = "snake_case")]
287pub enum Lane {
288    /// The subject, `apr serve`.
289    Apr,
290    /// The comparator, `llama-server`.
291    Llama,
292}
293
294impl Lane {
295    /// The wire token.
296    #[must_use]
297    pub fn wire_token(self) -> &'static str {
298        match self {
299            Self::Apr => "apr",
300            Self::Llama => "llama",
301        }
302    }
303}
304
305/// PP-24 — a band that could not run because a lane admitted fewer slots than
306/// the band's `c`. Server-reported; a harness-computed cap is schema-fatal
307/// (PP-13).
308#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
309#[serde(deny_unknown_fields)]
310pub struct AdmissionCap {
311    /// Which lane capped.
312    pub lane: Lane,
313    /// The slot count that lane reported.
314    pub cap: u32,
315}
316
317/// §4.7.1 / §7.4 — a band's comparator posture.
318///
319/// [`Self::Measured`] is the only variant carrying numbers, and it has no
320/// public constructor: [`BandInput::join_status`] is the only way to make one,
321/// and it refuses a cross-run baseline (PP-3), a join-key mismatch (PP-22) and
322/// a timed-out lane (PP-5). A bare `agg_ratio` scalar is therefore
323/// unrepresentable rather than merely discouraged.
324#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
325#[serde(deny_unknown_fields)]
326pub enum ComparatorStatus {
327    /// Permanent exclusion. Needs the decision recorded.
328    NotApplicable {
329        /// Who decided, e.g. `perf-matrix.yaml`.
330        decided_by: String,
331        /// Why the comparator cannot exist for this cell.
332        reason: String,
333        /// PP-24 — the server-reported ceiling that put this band out of the
334        /// ladder, when that is the reason.
335        budget: Option<String>,
336    },
337    /// Temporary. Counted against the denominator; needs an owner.
338    Unmeasured {
339        /// Who owes the measurement.
340        owner: String,
341        /// Why it has not been measured yet.
342        reason: String,
343        /// PP-24 — set when the band was not run because a lane admitted fewer
344        /// slots than `c`.
345        admission_capped: Option<AdmissionCap>,
346    },
347    /// PP-3 — a same-run comparator lane and the ratios formed from it.
348    ///
349    /// The payload is a [`MeasuredJoin`], whose fields are **private** and
350    /// whose constructor is crate-private: outside this crate the variant can
351    /// be matched and read but not built, so PP-3, PP-22 and PP-5 cannot be
352    /// stepped around with a struct literal.
353    Measured(MeasuredJoin),
354}
355
356/// PP-3 / PP-22 / P-5 — a comparator lane that passed the join, and the ratios
357/// it produced.
358///
359/// # Why the fields are private
360///
361/// While `ComparatorStatus::Measured` was a struct variant with public fields,
362/// **any** caller could write
363///
364/// ```text
365/// ComparatorStatus::Measured { baseline: Box::new(band), ratios }
366/// ```
367///
368/// and attach a baseline from another run, another band, or a lane that timed
369/// out — the three things [`BandInput::join_status_in`] exists to refuse. The
370/// refusals lived in a function nothing forced anyone to call. Wrapping the
371/// payload in a type whose only constructor is `pub(crate)` and which is
372/// reachable only from that function makes the refusals structural: outside
373/// `aprender-test-lib` there is no expression that produces one.
374///
375/// Reading is unrestricted — [`Self::baseline`] and [`Self::ratios`] — because
376/// a receipt renderer must be able to see what it is rendering.
377#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
378#[serde(deny_unknown_fields)]
379pub struct MeasuredJoin {
380    /// The comparator lane's band, derived under the same rules.
381    baseline: Box<DerivedBand>,
382    /// P-5 — the ratios, each with the bound its verdict is taken on.
383    ratios: BandRatios,
384}
385
386impl MeasuredJoin {
387    /// The only constructor, crate-private and called from exactly one place:
388    /// [`BandInput::join_status_in`], after PP-3, PP-22 and PP-5 have all been
389    /// checked.
390    pub(crate) fn sealed(baseline: DerivedBand, ratios: BandRatios) -> Self {
391        Self {
392            baseline: Box::new(baseline),
393            ratios,
394        }
395    }
396
397    /// The comparator lane's band.
398    #[must_use]
399    pub fn baseline(&self) -> &DerivedBand {
400        &self.baseline
401    }
402
403    /// P-5 — the ratios formed against that band.
404    #[must_use]
405    pub fn ratios(&self) -> &BandRatios {
406        &self.ratios
407    }
408}
409
410impl ComparatorStatus {
411    /// A temporary posture with no admission cap — the common case.
412    #[must_use]
413    pub fn unmeasured(owner: impl Into<String>, reason: impl Into<String>) -> Self {
414        Self::Unmeasured {
415            owner: owner.into(),
416            reason: reason.into(),
417            admission_capped: None,
418        }
419    }
420
421    /// A permanent exclusion with no reported budget.
422    #[must_use]
423    pub fn not_applicable(decided_by: impl Into<String>, reason: impl Into<String>) -> Self {
424        Self::NotApplicable {
425            decided_by: decided_by.into(),
426            reason: reason.into(),
427            budget: None,
428        }
429    }
430
431    /// The wire token `perf_gate.sh` reads from `band.comparator_status`.
432    ///
433    /// The legacy spelling `NOT_APPLICABLE` is kept here deliberately: §7.4's
434    /// `NA` lives in the new per-band `status` field, and changing this token
435    /// would break every existing reader for no gain.
436    #[must_use]
437    pub fn wire_token(&self) -> &'static str {
438        match self {
439            Self::NotApplicable { .. } => "NOT_APPLICABLE",
440            Self::Unmeasured { .. } => "UNMEASURED",
441            Self::Measured(_) => "MEASURED",
442        }
443    }
444}
445
446/// The comparator lane's configuration, as it enters the PP-22 join key.
447///
448/// Every field is `Option` because a lane that did not report one has not
449/// reported one — and `None` does not match `Some(x)` in
450/// [`JoinKey::refuse_mismatch`], so an unreported field refuses the join
451/// rather than acting as a wildcard.
452#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
453#[serde(deny_unknown_fields)]
454pub struct LaneConfig {
455    /// `-c` per slot on the comparator; `props.n_ctx / props.total_slots`.
456    pub n_ctx_slot: Option<u32>,
457    /// KV cache type, e.g. `f16`.
458    pub kv_type: Option<String>,
459    /// Flash attention.
460    pub fa: Option<bool>,
461    /// `-b`. `Some(1)` is refused by the join key (§5.3).
462    pub n_batch: Option<u32>,
463}
464
465/// One sampled request's terminal record. All offsets are milliseconds from the
466/// window opening at `0`.
467#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
468#[serde(deny_unknown_fields)]
469pub struct RequestOutcome {
470    /// When the client issued the request.
471    pub issued_ms: f64,
472    /// When the request reached its terminal state.
473    pub settled_ms: f64,
474    /// How it ended.
475    pub outcome: Outcome,
476    /// Generated (completion) tokens. Must be non-zero for a completion:
477    /// a zero-token response is a failure, not a fast request.
478    pub generated_tokens: u32,
479    /// Prompt tokens the server reported (§3: all token counts are the
480    /// server's `usage`).
481    pub prompt_tokens: u32,
482    /// PP-28 — the `n_predict` this request was issued with, when it differs
483    /// from the band's (W2's ragged mixture). `None` means "the band's".
484    pub expected_tokens: Option<u32>,
485    /// Time to first token, when the transport streamed. `None` for a
486    /// non-streaming client, which genuinely cannot observe it.
487    pub ttft_ms: Option<f64>,
488    /// Server-reported prefill duration for this request
489    /// (`timings.prompt_ms` on llama.cpp, the `apr` equivalent per PP-2).
490    /// `None` when the server reported none — never a client-side estimate
491    /// (PP-13).
492    pub prefill_ms: Option<f64>,
493    /// Concurrent requests in flight at the instant this one was issued. The
494    /// direct per-request evidence that the client was actually concurrent
495    /// (PP-8).
496    pub in_flight_at_start: u32,
497    /// Absolute arrival offsets of each generated token, when the transport
498    /// streamed. Empty for a non-streaming client.
499    pub token_times_ms: Vec<f64>,
500}
501
502impl RequestOutcome {
503    /// A terminal record with nothing observed beyond the clock and the count.
504    ///
505    /// The streaming, server-timing and concurrency facts are added with the
506    /// builders below, so a caller that never observed one cannot accidentally
507    /// supply a plausible value for it.
508    #[must_use]
509    pub fn new(issued_ms: f64, settled_ms: f64, outcome: Outcome, generated_tokens: u32) -> Self {
510        Self {
511            issued_ms,
512            settled_ms,
513            outcome,
514            generated_tokens,
515            prompt_tokens: 0,
516            expected_tokens: None,
517            ttft_ms: None,
518            prefill_ms: None,
519            in_flight_at_start: 0,
520            token_times_ms: Vec::new(),
521        }
522    }
523
524    /// A completed request. The common case.
525    #[must_use]
526    pub fn completed(issued_ms: f64, settled_ms: f64, generated_tokens: u32) -> Self {
527        Self::new(issued_ms, settled_ms, Outcome::Completed, generated_tokens)
528    }
529
530    /// Record what the transport streamed (PP-27).
531    #[must_use]
532    pub fn streamed(mut self, ttft_ms: f64, token_times_ms: Vec<f64>) -> Self {
533        self.ttft_ms = Some(ttft_ms);
534        self.token_times_ms = token_times_ms;
535        self
536    }
537
538    /// Record the server's prompt-token count and prefill duration (PP-2).
539    #[must_use]
540    pub fn server_prefill(mut self, prompt_tokens: u32, prefill_ms: f64) -> Self {
541        self.prompt_tokens = prompt_tokens;
542        self.prefill_ms = Some(prefill_ms);
543        self
544    }
545
546    /// Record the server's prompt-token count without a prefill duration.
547    #[must_use]
548    pub fn with_prompt_tokens(mut self, prompt_tokens: u32) -> Self {
549        self.prompt_tokens = prompt_tokens;
550        self
551    }
552
553    /// Record the `n_predict` this request was issued with (PP-28).
554    #[must_use]
555    pub fn expecting(mut self, expected_tokens: u32) -> Self {
556        self.expected_tokens = Some(expected_tokens);
557        self
558    }
559
560    /// Record the client's in-flight count at issue (PP-8).
561    #[must_use]
562    pub fn in_flight(mut self, in_flight_at_start: u32) -> Self {
563        self.in_flight_at_start = in_flight_at_start;
564        self
565    }
566
567    /// Wall-clock duration of the request.
568    #[must_use]
569    pub fn duration_ms(&self) -> f64 {
570        self.settled_ms - self.issued_ms
571    }
572
573    /// §3 — per-request `dec = (tokens − 1) / (last − first)`.
574    /// `None` unless the transport streamed at least two tokens.
575    #[must_use]
576    pub fn decode_tok_per_sec(&self) -> Option<f64> {
577        let (first, last) = (self.token_times_ms.first()?, self.token_times_ms.last()?);
578        let span_s = (last - first) / 1000.0;
579        let n = self.token_times_ms.len();
580        if n < 2 || span_s <= 0.0 {
581            return None;
582        }
583        Some((n as f64 - 1.0) / span_s)
584    }
585
586    /// §3 — this request's inter-token gaps, in milliseconds.
587    #[must_use]
588    pub fn itl_gaps_ms(&self) -> Vec<f64> {
589        self.token_times_ms
590            .windows(2)
591            .map(|w| w[1] - w[0])
592            .collect()
593    }
594
595    /// PP-27 — this request's `ttft / e2e`. `None` without a first-token
596    /// instant or a positive duration.
597    #[must_use]
598    pub fn ttft_over_e2e(&self) -> Option<f64> {
599        let ttft = self.ttft_ms?;
600        let e2e = self.duration_ms();
601        if e2e <= 0.0 {
602            return None;
603        }
604        Some(ttft / e2e)
605    }
606
607    /// The same record in the seconds-based shape the §4.3 estimators resample.
608    #[must_use]
609    pub fn to_sample(&self, index: usize, in_flight_fallback: u32) -> RequestSample {
610        RequestSample {
611            index,
612            worker: 0,
613            start_s: self.issued_ms / 1000.0,
614            end_s: self.settled_ms / 1000.0,
615            token_times_s: self.token_times_ms.iter().map(|t| t / 1000.0).collect(),
616            generated_tokens: self.generated_tokens,
617            prompt_tokens: self.prompt_tokens,
618            outcome: self.outcome,
619            in_flight_at_start: if self.in_flight_at_start == 0 {
620                in_flight_fallback as usize
621            } else {
622                self.in_flight_at_start as usize
623            },
624            drained: false,
625        }
626    }
627
628    /// PP-7 — the row this request contributes to the receipt's per-band
629    /// `samples[]`. `token_times_ms` stays in the gzipped side file: it is the
630    /// bulk of the payload and the receipt links it by digest instead.
631    #[must_use]
632    pub fn to_row(&self, index: usize) -> SampleRow {
633        SampleRow {
634            index,
635            issued_ms: self.issued_ms,
636            settled_ms: self.settled_ms,
637            outcome: self.outcome,
638            generated_tokens: self.generated_tokens,
639            prompt_tokens: self.prompt_tokens,
640            ttft_ms: self.ttft_ms,
641            in_flight_at_start: self.in_flight_at_start,
642        }
643    }
644}
645
646/// PP-7 — one row of a band's `samples[]`, as it appears in the receipt.
647#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
648#[serde(deny_unknown_fields)]
649pub struct SampleRow {
650    /// Position in issue order.
651    pub index: usize,
652    /// Offset of the request start from the window opening.
653    pub issued_ms: f64,
654    /// Offset of the terminal state.
655    pub settled_ms: f64,
656    /// How it ended.
657    pub outcome: Outcome,
658    /// Server-reported completion tokens.
659    pub generated_tokens: u32,
660    /// Server-reported prompt tokens.
661    pub prompt_tokens: u32,
662    /// Time to first token, when the transport streamed.
663    pub ttft_ms: Option<f64>,
664    /// In-flight requests when this one was issued (PP-8).
665    pub in_flight_at_start: u32,
666}
667
668/// Receipt-level facts a band needs in order to know its own status.
669///
670/// Passed in rather than read from a global, so `derive_at(2)` can render a v2
671/// band under v2 rules and `derive` can render a v3 one under v3 rules in the
672/// same test.
673#[derive(Debug, Clone, Copy, PartialEq)]
674pub struct BandContext {
675    /// The receipt's `schema_version`. PP-26's `INVALID-CORRECTNESS` rule and
676    /// PP-4's metric-presence rule apply from 3 on; a version-2 receipt is
677    /// historical and neither is applied to it.
678    pub schema_version: u32,
679    /// Replicates the cell ran (§4.3: fewer than five is `NONCONFORMANT`).
680    pub replicates: u32,
681    /// Whether those replicates alternated.
682    pub interleaved: bool,
683    /// PP-20 — the comparator pin expired before the run started.
684    pub comparator_stale: bool,
685    /// PP-27 threshold, from `perf-matrix.yaml`.
686    pub stream_live_ttft_over_e2e_max: f64,
687}
688
689impl Default for BandContext {
690    fn default() -> Self {
691        Self {
692            schema_version: SCHEMA_VERSION,
693            replicates: REPLICATES as u32,
694            interleaved: INTERLEAVED,
695            comparator_stale: false,
696            stream_live_ttft_over_e2e_max: stream_live_ttft_over_e2e_max(),
697        }
698    }
699}
700
701impl BandContext {
702    /// The context for a receipt at `schema_version`, everything else at the
703    /// conformant value.
704    #[must_use]
705    pub fn at_schema_version(schema_version: u32) -> Self {
706        Self {
707            schema_version,
708            ..Self::default()
709        }
710    }
711}
712
713/// One band's raw input: the window it ran, and every request it sampled.
714#[derive(Debug, Clone, PartialEq)]
715pub struct BandInput {
716    /// Fixed concurrency `c`.
717    pub concurrency: u32,
718    /// `T` — the window close, in milliseconds from window open.
719    pub window_ms: f64,
720    /// Which replicate this band is, 1-based.
721    pub replicate: u32,
722    /// Every sampled request's terminal record.
723    pub requests: Vec<RequestOutcome>,
724    /// This cell's comparator posture.
725    pub comparator: ComparatorStatus,
726    /// PP-28 — the `n_predict` every retained sample was issued with. `None`
727    /// only for a workload that does not pin one.
728    pub n_predict: Option<u32>,
729    /// PP-27 — what the server declared on the first chunk.
730    pub stream_mode: Option<StreamMode>,
731    /// PP-26 — the band's correctness witness.
732    pub witness: Option<BatchInvarianceWitness>,
733    /// PP-7 — the retained gzipped samples file, when one was written.
734    pub samples_file: Option<SamplesFile>,
735    /// PP-22 — the lane configuration that enters the join key.
736    pub lane: LaneConfig,
737    /// PP-26 — which lane measured this band.
738    ///
739    /// The batch-invariance witness is a claim about the **subject**: "did
740    /// `apr serve` return the same tokens under batching as it did alone?" The
741    /// comparator is the oracle the subject is measured against, and demanding
742    /// a witness of it would make `llama-server` `INVALID-CORRECTNESS` for
743    /// failing to be witnessed against itself. So a `Llama` band is exempt
744    /// (see [`Self::invalid_correctness`]) and carries no witness at all.
745    pub role: Lane,
746    /// §4.4.2 — protocol departures the DRIVER observed while running this
747    /// band (a shrunken window, a warmup that did not complete, a sample floor
748    /// that was not met).
749    ///
750    /// Printed-only is not recorded: a violation the operator saw scroll past
751    /// and the receipt did not carry is a receipt that reads conformant. Each
752    /// entry sends the band to `NONCONFORMANT-VALID` and is named in
753    /// `unproduced_fields`.
754    pub conformance_violations: Vec<String>,
755}
756
757impl BandInput {
758    /// A band with the minimum a measurement needs: its window, its requests
759    /// and its comparator posture.
760    ///
761    /// Everything else — the correctness witness, the stream declaration, the
762    /// `n_predict` pin, the lane configuration — is recorded with the builders
763    /// below. A band that records none of them renders as
764    /// `NONCONFORMANT-VALID` and says why, which is the honest posture for a
765    /// run that observed none of them.
766    #[must_use]
767    pub fn new(
768        concurrency: u32,
769        window_ms: f64,
770        requests: Vec<RequestOutcome>,
771        comparator: ComparatorStatus,
772    ) -> Self {
773        Self {
774            concurrency,
775            window_ms,
776            replicate: 1,
777            requests,
778            comparator,
779            n_predict: None,
780            stream_mode: None,
781            witness: None,
782            samples_file: None,
783            lane: LaneConfig::default(),
784            role: Lane::Apr,
785            conformance_violations: Vec::new(),
786        }
787    }
788
789    /// PP-26 — which lane measured this band. Defaults to [`Lane::Apr`], the
790    /// subject; a comparator lane must say so, because saying nothing is what
791    /// copied the subject's witness onto the oracle.
792    #[must_use]
793    pub fn role(mut self, role: Lane) -> Self {
794        self.role = role;
795        self
796    }
797
798    /// §4.4.2 — the driver's protocol departures for this band.
799    #[must_use]
800    pub fn conformance_violations(mut self, violations: Vec<String>) -> Self {
801        self.conformance_violations = violations;
802        self
803    }
804
805    /// Which replicate this band is, 1-based (§4.3).
806    #[must_use]
807    pub fn replicate(mut self, replicate: u32) -> Self {
808        self.replicate = replicate;
809        self
810    }
811
812    /// PP-28 — the `n_predict` every retained sample was issued with.
813    #[must_use]
814    pub fn n_predict(mut self, n_predict: u32) -> Self {
815        self.n_predict = Some(n_predict);
816        self
817    }
818
819    /// PP-27 — what the server declared on the first chunk.
820    #[must_use]
821    pub fn stream_mode(mut self, stream_mode: StreamMode) -> Self {
822        self.stream_mode = Some(stream_mode);
823        self
824    }
825
826    /// PP-26 — the band's correctness witness.
827    #[must_use]
828    pub fn witness(mut self, witness: BatchInvarianceWitness) -> Self {
829        self.witness = Some(witness);
830        self
831    }
832
833    /// PP-7 — the retained gzipped samples file.
834    #[must_use]
835    pub fn samples_file(mut self, samples_file: SamplesFile) -> Self {
836        self.samples_file = Some(samples_file);
837        self
838    }
839
840    /// PP-22 — the lane configuration that enters the join key.
841    #[must_use]
842    pub fn lane(mut self, lane: LaneConfig) -> Self {
843        self.lane = lane;
844        self
845    }
846
847    /// Derive every quantity from the sampled requests, under v3 rules.
848    ///
849    /// # Errors
850    /// When the band contradicts its own clock: an empty band, a non-positive
851    /// window, a request issued at or after `T` (PP-10), a settlement before
852    /// its issue, a zero-token completion, an abandonment that did not happen
853    /// during the drain, or a `Timeout`/`Failed` label that the request's own
854    /// duration contradicts.
855    pub fn derive(&self) -> Result<DerivedBand, String> {
856        self.derive_in(&BandContext::default())
857    }
858
859    /// [`Self::derive`] under the rules of a given schema version.
860    ///
861    /// # Errors
862    /// As [`Self::derive`].
863    pub fn derive_at(&self, schema_version: u32) -> Result<DerivedBand, String> {
864        self.derive_in(&BandContext::at_schema_version(schema_version))
865    }
866
867    /// [`Self::derive`] with the receipt-level facts spelled out.
868    ///
869    /// # Errors
870    /// As [`Self::derive`].
871    pub fn derive_in(&self, ctx: &BandContext) -> Result<DerivedBand, String> {
872        self.validate()?;
873        let drain_ms = self.drain_ms();
874        let span_ms = self.span_ms();
875        let tokens_total = self.tokens_total();
876        let short_of_n_predict = self.short_of_n_predict();
877        let stream_witness = self.stream_witness(ctx.stream_live_ttft_over_e2e_max);
878        // PP-27: the verdict, not the declaration. An undeclared stream the
879        // client measured as live IS live; a declared-live stream the client
880        // measured as a replay is not.
881        let stream_live = stream_witness.is_some_and(|w| w.verdict == StreamVerdict::Live);
882        let invalid_correctness = self.invalid_correctness(ctx);
883
884        let mut unproduced = Vec::new();
885        let latency = if stream_live {
886            Latency::from(self)
887        } else {
888            unproduced.push(self.stream_reason(stream_witness.as_ref()));
889            Latency::none()
890        };
891        let mut prefill = self.prefill_tok_per_sec();
892        if prefill.is_none() {
893            unproduced.push(format!(
894                "PP-4 c={}: prefill_tok_per_sec — no request carried a server-reported \
895                 `timings.prompt_ms`, and a client-side prefill estimate is exactly the \
896                 harness-inferred field PP-13 refuses",
897                self.concurrency
898            ));
899        }
900        let mut aggregate = Some(rate_per_sec(tokens_total as f64, span_ms));
901        let mut latency = latency;
902        if invalid_correctness {
903            aggregate = None;
904            prefill = None;
905            latency.decode_tok_per_sec = None;
906            unproduced.push(format!(
907                "P-4 c={}: aggregate_tok_per_sec, decode_tok_per_sec and prefill_tok_per_sec — \
908                 the band's batch-invariance witness (PP-26) is {} , so its throughput is not \
909                 reported, not gated and never a baseline",
910                self.concurrency,
911                self.witness.as_ref().map_or_else(
912                    || "absent".to_string(),
913                    |w| format!("{:?}", w.batch_invariance)
914                )
915            ));
916        }
917        if short_of_n_predict > 0 {
918            unproduced.push(format!(
919                "PP-28 c={}: {short_of_n_predict} of {} completed requests did not reach \
920                 n_predict — the sampler pin was not honoured, so this band is a record and not \
921                 a baseline",
922                self.concurrency,
923                self.count(Outcome::Completed)
924            ));
925        }
926        for violation in &self.conformance_violations {
927            unproduced.push(format!(
928                "§4.4.2 c={}: protocol violation observed by the driver — {violation}. The band \
929                 is NONCONFORMANT-VALID: a record, cited, never a baseline.",
930                self.concurrency
931            ));
932        }
933        // PP-4: a band that reports numbers reports all three of them.
934        let metrics_complete =
935            aggregate.is_some() && latency.decode_tok_per_sec.is_some() && prefill.is_some();
936        let status = self.status(
937            ctx,
938            invalid_correctness,
939            stream_live,
940            short_of_n_predict,
941            metrics_complete,
942        );
943        if status == BandStatus::NonconformantValid {
944            unproduced.push(format!(
945                "§7.4 c={}: this band is NONCONFORMANT-VALID — a historical record, cited, never \
946                 a baseline",
947                self.concurrency
948            ));
949        }
950
951        Ok(DerivedBand {
952            concurrency: self.concurrency,
953            replicate: self.replicate,
954            window_ms: self.window_ms,
955            drain_ms,
956            suspect: self.suspect(drain_ms),
957            requested: self.requests.len(),
958            completed: self.count(Outcome::Completed),
959            timeouts: self.count(Outcome::Timeout),
960            truncated: self.count(Outcome::AbandonedAtDrain),
961            errors: self.count(Outcome::Failed),
962            short_of_n_predict,
963            tokens_total,
964            span_ms,
965            aggregate_tok_per_sec: aggregate,
966            decode_tok_per_sec: latency.decode_tok_per_sec,
967            prefill_tok_per_sec: prefill,
968            ttft_p50_ms: latency.ttft_p50_ms,
969            ttft_p95_ms: latency.ttft_p95_ms,
970            itl_p50_ms: latency.itl_p50_ms,
971            itl_p95_ms: latency.itl_p95_ms,
972            latencies_ms: self.latencies_ms(),
973            samples: self.sample_rows(),
974            samples_file: self.samples_file.clone(),
975            stream_mode: self.stream_mode,
976            stream_witness,
977            witness: self.witness.clone(),
978            status,
979            join_key: None,
980            run_id: None,
981            unproduced,
982            comparator: self.comparator.clone(),
983        })
984    }
985
986    /// PP-3 / PP-22 / PP-5 — form the comparator posture for a subject band
987    /// against a same-run comparator lane.
988    ///
989    /// This is the **only** constructor of [`ComparatorStatus::Measured`], and
990    /// therefore the only way a [`Ratio`] enters a receipt.
991    ///
992    /// # Errors
993    /// When the two lanes come from different runs (PP-3), when the join keys
994    /// differ or either is a `-b 1` cripple (PP-22), when either lane timed out
995    /// (PP-5), or when either lane fails to derive.
996    pub fn join_status(
997        subject: &Self,
998        comparator: &Self,
999        subject_key: &JoinKey,
1000        comparator_key: &JoinKey,
1001        run_ids: (&RunId, &RunId),
1002    ) -> Result<ComparatorStatus, String> {
1003        Self::join_status_in(
1004            subject,
1005            comparator,
1006            subject_key,
1007            comparator_key,
1008            run_ids,
1009            &BandContext::default(),
1010        )
1011    }
1012
1013    /// [`Self::join_status`] with the receipt-level facts spelled out.
1014    ///
1015    /// # Errors
1016    /// As [`Self::join_status`].
1017    pub fn join_status_in(
1018        subject: &Self,
1019        comparator: &Self,
1020        subject_key: &JoinKey,
1021        comparator_key: &JoinKey,
1022        run_ids: (&RunId, &RunId),
1023        ctx: &BandContext,
1024    ) -> Result<ComparatorStatus, String> {
1025        let (subject_run, comparator_run) = run_ids;
1026        if subject_run != comparator_run {
1027            return Err(format!(
1028                "PP-3: the comparator lane is run_id {} and the subject is run_id {} — a ratio is \
1029                 representable only against a baseline from the SAME run; two runs saw two \
1030                 thermal states, two free-VRAM figures and two schedulers",
1031                comparator_run.as_str(),
1032                subject_run.as_str()
1033            ));
1034        }
1035        subject_key.refuse_mismatch(comparator_key)?;
1036        let subject_band = subject.derive_in(ctx)?;
1037        let comparator_band = comparator.derive_in(ctx)?;
1038        for (lane, band) in [("subject", &subject_band), ("comparator", &comparator_band)] {
1039            if band.timeouts > 0 {
1040                return Err(format!(
1041                    "PP-5: the {lane} lane at c={} recorded {} timeouts — a timed-out band cannot \
1042                     carry a ratio, because the requests that did not return are exactly the ones \
1043                     the ratio would have to account for",
1044                    band.concurrency, band.timeouts
1045                ));
1046            }
1047        }
1048        let ratios = ratios_of(subject, comparator, &subject_band, &comparator_band)?;
1049        Ok(ComparatorStatus::Measured(MeasuredJoin::sealed(
1050            comparator_band
1051                .with_run_id(comparator_run.clone())
1052                .with_join_key(comparator_key.clone()),
1053            ratios,
1054        )))
1055    }
1056
1057    /// [`Self::join_status`], applied: the subject's derived band carrying the
1058    /// joined comparator.
1059    ///
1060    /// # Errors
1061    /// As [`Self::join_status`].
1062    pub fn join(
1063        subject: &Self,
1064        comparator: &Self,
1065        subject_key: &JoinKey,
1066        comparator_key: &JoinKey,
1067        run_ids: (&RunId, &RunId),
1068    ) -> Result<DerivedBand, String> {
1069        let status = Self::join_status(subject, comparator, subject_key, comparator_key, run_ids)?;
1070        let joined = Self {
1071            comparator: status,
1072            ..subject.clone()
1073        };
1074        Ok(joined
1075            .derive()?
1076            .with_run_id(run_ids.0.clone())
1077            .with_join_key(subject_key.clone()))
1078    }
1079
1080    fn completed_iter(&self) -> impl Iterator<Item = &RequestOutcome> {
1081        self.requests
1082            .iter()
1083            .filter(|r| r.outcome == Outcome::Completed)
1084    }
1085
1086    fn count(&self, outcome: Outcome) -> usize {
1087        self.requests
1088            .iter()
1089            .filter(|r| r.outcome == outcome)
1090            .count()
1091    }
1092
1093    fn tokens_total(&self) -> u64 {
1094        self.completed_iter()
1095            .map(|r| u64::from(r.generated_tokens))
1096            .sum()
1097    }
1098
1099    /// PP-28 — completed requests whose token count missed the pin.
1100    ///
1101    /// The per-request `expected_tokens` wins over the band's `n_predict` so a
1102    /// ragged workload (W2) is not counted short for being ragged. When neither
1103    /// is declared nothing is counted: a band that never pinned `n_predict`
1104    /// cannot be short of it, and it says so through its status instead.
1105    fn short_of_n_predict(&self) -> usize {
1106        self.completed_iter()
1107            .filter(|r| {
1108                r.expected_tokens
1109                    .or(self.n_predict)
1110                    .is_some_and(|want| r.generated_tokens != want)
1111            })
1112            .count()
1113    }
1114
1115    /// PP-27 — the two halves of the streaming witness, resolved.
1116    ///
1117    /// | server declared | client ratio | verdict | source | latency metrics |
1118    /// |---|---|---|---|---|
1119    /// | `replayed` | anything | `Replayed` | server | withheld |
1120    /// | `live` | `<= live_max` | `Live` | server | produced |
1121    /// | `live` | `> live_max` | `Replayed` | client | withheld (disagreement) |
1122    /// | nothing | `<= live_max` | `Live` | client | produced |
1123    /// | nothing | `> live_max` | `Undeclared` | client | withheld |
1124    ///
1125    /// The fourth row is the one that changed: upstream `llama-server` never
1126    /// declares a mode, and treating that as "not live" made every comparator
1127    /// band `NONCONFORMANT-VALID` — no baseline could ever be conformant. The
1128    /// client's ratio measures the same fact and carries the verdict alone.
1129    ///
1130    /// The fifth row is deliberately NOT `Replayed`: nothing said the answer
1131    /// was pre-computed. It is "no evidence of a live stream", and it is not a
1132    /// pass either.
1133    fn stream_witness(&self, live_max: f64) -> Option<StreamWitness> {
1134        let ratios: Vec<f64> = self
1135            .completed_iter()
1136            .filter_map(RequestOutcome::ttft_over_e2e)
1137            .collect();
1138        let median = percentile(&sorted(ratios), 0.50)?;
1139        let client_live = median <= live_max;
1140        let (verdict, source) = match (self.stream_mode, client_live) {
1141            (Some(StreamMode::Replayed), _) => {
1142                (StreamVerdict::Replayed, StreamWitnessSource::Server)
1143            }
1144            (Some(StreamMode::Live), true) => (StreamVerdict::Live, StreamWitnessSource::Server),
1145            (Some(StreamMode::Live), false) => {
1146                (StreamVerdict::Replayed, StreamWitnessSource::Client)
1147            }
1148            (None, true) => (StreamVerdict::Live, StreamWitnessSource::Client),
1149            (None, false) => (StreamVerdict::Undeclared, StreamWitnessSource::Client),
1150        };
1151        Some(StreamWitness {
1152            client_ttft_over_e2e_median: median,
1153            verdict,
1154            source,
1155        })
1156    }
1157
1158    fn stream_reason(&self, witness: Option<&StreamWitness>) -> String {
1159        let verdict = witness.map_or(StreamVerdict::Undeclared, |w| w.verdict);
1160        let observed = witness.map_or_else(
1161            || "no completed request reported a first-token instant".to_string(),
1162            |w| format!("median(ttft/e2e)={:.3}", w.client_ttft_over_e2e_median),
1163        );
1164        format!(
1165            "PP-27 c={}: decode_tok_per_sec, ttft_ms p50/p95 and itl_ms p50/p95 — stream verdict \
1166             {verdict:?} ({observed}); a latency computed off a replayed or undeclared stream is a \
1167             property of the replay, not of the server",
1168            self.concurrency
1169        )
1170    }
1171
1172    /// P-4 / PP-26 — `c > 1` on the **subject** lane needs a passing
1173    /// batch-invariance witness. `c = 1` forms no batch and needs none, and the
1174    /// comparator lane is not the thing being witnessed.
1175    ///
1176    /// PP-26 asks whether `apr serve` returns the same tokens under batching as
1177    /// it does alone. The comparator is the ORACLE that question is asked
1178    /// against; requiring a witness of it would mark `llama-server`
1179    /// `INVALID-CORRECTNESS` for not being witnessed against itself, and — as
1180    /// the producer copied the subject's witness onto the comparator band — a
1181    /// subject-side PASS would have silently vouched for the oracle too.
1182    fn invalid_correctness(&self, ctx: &BandContext) -> bool {
1183        self.role == Lane::Apr
1184            && ctx.schema_version >= SCHEMA_VERSION
1185            && self.concurrency > 1
1186            && !self
1187                .witness
1188                .as_ref()
1189                .is_some_and(BatchInvarianceWitness::passed)
1190    }
1191
1192    /// §7.4 — every applicable verdict, folded through
1193    /// [`BandStatus::stronger_of`].
1194    ///
1195    /// Each rule contributes a candidate and the precedence lives in one place
1196    /// ([`BandStatus::rank`]) rather than in the order of early returns, which
1197    /// is how `COMPARATOR_STALE` came to be stamped over `INVALID-CORRECTNESS`
1198    /// at render time.
1199    fn status(
1200        &self,
1201        ctx: &BandContext,
1202        invalid_correctness: bool,
1203        stream_live: bool,
1204        short_of_n_predict: usize,
1205        metrics_complete: bool,
1206    ) -> BandStatus {
1207        // PP-5 predates v3 and applies to every receipt; PP-27, PP-28, PP-4
1208        // and §4.3's replicate floor are v3 rules and are NOT applied
1209        // retroactively to a v2-dated receipt, which is historical either way
1210        // (`baseline_eligible` is false for anything but MEASURED).
1211        let v3 = ctx.schema_version >= SCHEMA_VERSION;
1212        let nonconformant = self.count(Outcome::Timeout) > 0
1213            || !self.conformance_violations.is_empty()
1214            || (v3
1215                && (!ctx.interleaved
1216                    || (ctx.replicates as usize) < MIN_REPLICATES
1217                    || !stream_live
1218                    || short_of_n_predict > 0
1219                    || !metrics_complete));
1220        let mut status = match self.comparator {
1221            ComparatorStatus::Measured(_) => BandStatus::Measured,
1222            ComparatorStatus::NotApplicable { .. } => BandStatus::Na,
1223            ComparatorStatus::Unmeasured { .. } => BandStatus::Unmeasured,
1224        };
1225        if nonconformant {
1226            status = status.stronger_of(BandStatus::NonconformantValid);
1227        }
1228        if ctx.comparator_stale {
1229            status = status.stronger_of(BandStatus::ComparatorStale);
1230        }
1231        if invalid_correctness {
1232            status = status.stronger_of(BandStatus::InvalidCorrectness);
1233        }
1234        status
1235    }
1236
1237    /// PP-10 — last settlement of any request, minus `T`, clamped at 0.
1238    fn drain_ms(&self) -> f64 {
1239        let last = self
1240            .requests
1241            .iter()
1242            .map(|r| r.settled_ms)
1243            .fold(f64::NEG_INFINITY, f64::max);
1244        (last - self.window_ms).max(0.0)
1245    }
1246
1247    /// §3 — last completion minus first request start.
1248    fn span_ms(&self) -> f64 {
1249        let first = self
1250            .requests
1251            .iter()
1252            .map(|r| r.issued_ms)
1253            .fold(f64::INFINITY, f64::min);
1254        let last = self
1255            .completed_iter()
1256            .map(|r| r.settled_ms)
1257            .fold(f64::NEG_INFINITY, f64::max);
1258        (last - first).max(0.0)
1259    }
1260
1261    fn suspect(&self, drain_ms: f64) -> Vec<String> {
1262        if self.window_ms > 0.0 && drain_ms > DRAIN_SUSPECT_FRACTION * self.window_ms {
1263            return vec![format!(
1264                "SUSPECT PP-10 c={}: drain_ms={drain_ms:.1} > 0.5 x window_ms={:.1} — one \
1265                 request dominated the window; re-run this band with a longer window",
1266                self.concurrency, self.window_ms
1267            )];
1268        }
1269        Vec::new()
1270    }
1271
1272    fn latencies_ms(&self) -> Vec<f64> {
1273        self.completed_iter()
1274            .map(RequestOutcome::duration_ms)
1275            .collect()
1276    }
1277
1278    fn sample_rows(&self) -> Vec<SampleRow> {
1279        self.requests
1280            .iter()
1281            .enumerate()
1282            .map(|(i, r)| r.to_row(i))
1283            .collect()
1284    }
1285
1286    /// The §4.3 request-unit shape of this band's completed requests.
1287    fn request_samples(&self) -> Vec<RequestSample> {
1288        self.requests
1289            .iter()
1290            .enumerate()
1291            .map(|(i, r)| r.to_sample(i, self.concurrency))
1292            .collect()
1293    }
1294
1295    fn decode_median(&self) -> Option<f64> {
1296        let rates: Vec<f64> = self
1297            .completed_iter()
1298            .filter_map(RequestOutcome::decode_tok_per_sec)
1299            .collect();
1300        percentile(&sorted(rates), 0.50)
1301    }
1302
1303    fn ttft_percentile(&self, p: f64) -> Option<f64> {
1304        let v: Vec<f64> = self.completed_iter().filter_map(|r| r.ttft_ms).collect();
1305        percentile(&sorted(v), p)
1306    }
1307
1308    fn itl_percentile(&self, p: f64) -> Option<f64> {
1309        let v: Vec<f64> = self
1310            .completed_iter()
1311            .flat_map(RequestOutcome::itl_gaps_ms)
1312            .collect();
1313        percentile(&sorted(v), p)
1314    }
1315
1316    /// §3 `prefill` — `Σ prompt_tokens / Σ prefill_ms` over the completed
1317    /// requests that carry a **server-reported** prefill duration.
1318    ///
1319    /// `None` when none of them do. There is no client-side fallback: PP-13
1320    /// makes a harness-computed value schema-fatal, and the whole point of
1321    /// naming `prefill_source: "server"` beside the number is that the reader
1322    /// can tell the difference.
1323    fn prefill_tok_per_sec(&self) -> Option<f64> {
1324        let mut tokens = 0_u64;
1325        let mut ms = 0.0_f64;
1326        for r in self.completed_iter() {
1327            if let Some(p) = r.prefill_ms {
1328                if p > 0.0 {
1329                    tokens += u64::from(r.prompt_tokens);
1330                    ms += p;
1331                }
1332            }
1333        }
1334        if ms <= 0.0 || tokens == 0 {
1335            return None;
1336        }
1337        Some(tokens as f64 / (ms / 1000.0))
1338    }
1339
1340    fn validate(&self) -> Result<(), String> {
1341        if self.requests.is_empty() {
1342            return Err(format!(
1343                "band c={}: no sampled requests — a band over zero requests is a vacuous pass, \
1344                 not a measurement",
1345                self.concurrency
1346            ));
1347        }
1348        // NaN is caught explicitly: a negated `>` would let it through.
1349        if self.window_ms.is_nan() || self.window_ms <= 0.0 {
1350            return Err(format!(
1351                "band c={}: window_ms={} — the window must have positive length or `drain_ms` \
1352                 and the SUSPECT fraction are both undefined",
1353                self.concurrency, self.window_ms
1354            ));
1355        }
1356        for (i, r) in self.requests.iter().enumerate() {
1357            validate_request(self.concurrency, i, r, self.window_ms)?;
1358        }
1359        Ok(())
1360    }
1361}
1362
1363/// The five streaming-only figures, produced or withheld together.
1364struct Latency {
1365    decode_tok_per_sec: Option<f64>,
1366    ttft_p50_ms: Option<f64>,
1367    ttft_p95_ms: Option<f64>,
1368    itl_p50_ms: Option<f64>,
1369    itl_p95_ms: Option<f64>,
1370}
1371
1372impl Latency {
1373    fn from(band: &BandInput) -> Self {
1374        Self {
1375            decode_tok_per_sec: band.decode_median(),
1376            ttft_p50_ms: band.ttft_percentile(0.50),
1377            ttft_p95_ms: band.ttft_percentile(0.95),
1378            itl_p50_ms: band.itl_percentile(0.50),
1379            itl_p95_ms: band.itl_percentile(0.95),
1380        }
1381    }
1382
1383    fn none() -> Self {
1384        Self {
1385            decode_tok_per_sec: None,
1386            ttft_p50_ms: None,
1387            ttft_p95_ms: None,
1388            itl_p50_ms: None,
1389            itl_p95_ms: None,
1390        }
1391    }
1392}
1393
1394/// §4.3 — the three ratios, each by its own estimator.
1395fn ratios_of(
1396    subject: &BandInput,
1397    comparator: &BandInput,
1398    subject_band: &DerivedBand,
1399    comparator_band: &DerivedBand,
1400) -> Result<BandRatios, String> {
1401    let agg = window_ratio(
1402        subject_band.aggregate_tok_per_sec,
1403        comparator_band.aggregate_tok_per_sec,
1404    )
1405    .ok_or_else(|| {
1406        format!(
1407            "P-5: neither lane at c={} produced an aggregate throughput, so there is no agg ratio \
1408             to form",
1409            subject_band.concurrency
1410        )
1411    })?;
1412    // A ratio of two suppressed numbers is not a ratio. `dec` is a paired
1413    // bootstrap over the raw request samples, which exist whatever the band
1414    // decided about them — so without this guard a lane whose decode was
1415    // withheld as unreliable (a replayed stream, an unwitnessed batch) still
1416    // contributed a `dec` ratio computed from exactly the samples the band
1417    // refused to report. `prefill` and `agg` divide the DERIVED figures and are
1418    // already `None` when either lane withheld one; `dec` has to be told.
1419    let dec = if subject_band.decode_tok_per_sec.is_some()
1420        && comparator_band.decode_tok_per_sec.is_some()
1421    {
1422        paired_ratio_lcb(
1423            &subject.request_samples(),
1424            &comparator.request_samples(),
1425            median_decode_tok_s,
1426            VERDICT_CONFIDENCE,
1427        )
1428    } else {
1429        None
1430    };
1431    let prefill = window_ratio(
1432        subject_band.prefill_tok_per_sec,
1433        comparator_band.prefill_tok_per_sec,
1434    );
1435    Ok(BandRatios { agg, dec, prefill })
1436}
1437
1438/// §4.3 replicate unit, from a single replicate: the point estimate with an
1439/// explicitly absent bound. One replicate bounds no variance.
1440fn window_ratio(subject: Option<f64>, comparator: Option<f64>) -> Option<Ratio> {
1441    let (s, c) = (subject?, comparator?);
1442    if c <= 0.0 {
1443        return None;
1444    }
1445    Some(Ratio::reporting_only(
1446        s / c,
1447        RatioMethod::ReplicateTLower,
1448        1,
1449    ))
1450}
1451
1452/// Everything §5.1, §3 and §7.4 derive from a [`BandInput`].
1453#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1454#[serde(deny_unknown_fields)]
1455pub struct DerivedBand {
1456    /// Fixed concurrency `c`.
1457    pub concurrency: u32,
1458    /// Which replicate this band is, 1-based.
1459    pub replicate: u32,
1460    /// `T`, in milliseconds.
1461    pub window_ms: f64,
1462    /// PP-10 — length of the drain phase, in milliseconds.
1463    pub drain_ms: f64,
1464    /// PP-10 `SUSPECT` annotations; empty when the band is clean.
1465    pub suspect: Vec<String>,
1466    /// Requests issued before `T`.
1467    pub requested: usize,
1468    /// Requests that returned a usable response.
1469    pub completed: usize,
1470    /// Requests that reached the 120 s hard timeout.
1471    pub timeouts: usize,
1472    /// Requests abandoned at the drain deadline (PP-10 sense).
1473    pub truncated: usize,
1474    /// Requests that failed for any other reason.
1475    pub errors: usize,
1476    /// PP-28 — completed requests that did not reach `n_predict`.
1477    pub short_of_n_predict: usize,
1478    /// Σ generated tokens over completed requests — `agg`'s numerator.
1479    pub tokens_total: u64,
1480    /// `agg`'s denominator: last completion − first request start, in ms.
1481    pub span_ms: f64,
1482    /// §3 `agg` — wall-clock aggregate throughput. `None` on an
1483    /// `INVALID-CORRECTNESS` band, which reports no throughput at all.
1484    pub aggregate_tok_per_sec: Option<f64>,
1485    /// §3 `dec` — median per-request decode rate. `None` without a live stream.
1486    pub decode_tok_per_sec: Option<f64>,
1487    /// §3 `prefill` — server-reported. `None` without server timings.
1488    pub prefill_tok_per_sec: Option<f64>,
1489    /// p50 time-to-first-token. `None` without a live stream.
1490    pub ttft_p50_ms: Option<f64>,
1491    /// p95 time-to-first-token. `None` without a live stream.
1492    pub ttft_p95_ms: Option<f64>,
1493    /// p50 of the pooled inter-token gaps. `None` without a live stream.
1494    pub itl_p50_ms: Option<f64>,
1495    /// p95 of the pooled inter-token gaps. `None` without a live stream.
1496    pub itl_p95_ms: Option<f64>,
1497    /// Per-request end-to-end latencies of completed requests (PP-7).
1498    pub latencies_ms: Vec<f64>,
1499    /// PP-7 — the per-request rows this band carries in the receipt.
1500    pub samples: Vec<SampleRow>,
1501    /// PP-7 — the gzipped side file the rows' token times went to.
1502    pub samples_file: Option<SamplesFile>,
1503    /// PP-27 — what the server declared.
1504    pub stream_mode: Option<StreamMode>,
1505    /// PP-27 — what the client independently observed.
1506    pub stream_witness: Option<StreamWitness>,
1507    /// PP-26 — the correctness witness.
1508    pub witness: Option<BatchInvarianceWitness>,
1509    /// §7.4 — the status this band ended up with.
1510    pub status: BandStatus,
1511    /// PP-22 — the key this band joins on. Set at render time.
1512    pub join_key: Option<JoinKey>,
1513    /// PP-3 — the run this band belongs to. Set at render time.
1514    pub run_id: Option<RunId>,
1515    /// Fields this client could not produce, each with its reason. Never
1516    /// silently omitted and never filled with a plausible number.
1517    pub unproduced: Vec<String>,
1518    /// This cell's comparator posture.
1519    pub comparator: ComparatorStatus,
1520}
1521
1522impl DerivedBand {
1523    /// Attach the PP-22 join key. Done at render time, where the receipt-level
1524    /// half of the key (host, model, protocol) is in scope.
1525    #[must_use]
1526    pub fn with_join_key(mut self, key: JoinKey) -> Self {
1527        self.join_key = Some(key);
1528        self
1529    }
1530
1531    /// Attach the PP-3 run id.
1532    #[must_use]
1533    pub fn with_run_id(mut self, run_id: RunId) -> Self {
1534        self.run_id = Some(run_id);
1535        self
1536    }
1537
1538    /// PP-20 — mark the band `COMPARATOR_STALE`. Applied at render time,
1539    /// because the pin expiry lives in the receipt's provenance and not in any
1540    /// band.
1541    ///
1542    /// Folded through [`BandStatus::stronger_of`], never assigned. An
1543    /// assignment here **overwrote** `INVALID-CORRECTNESS`: a `c > 1` band with
1544    /// no witness reported no throughput at all, and came out of the render
1545    /// labelled as though its only defect were an expired comparator pin. §7.4
1546    /// puts correctness first, and there is now one definition of that order.
1547    #[must_use]
1548    pub fn marked_comparator_stale(mut self, pin_expiry: &str, started_utc: &str) -> Self {
1549        self.status = self.status.stronger_of(BandStatus::ComparatorStale);
1550        self.unproduced.push(format!(
1551            "PP-20 c={}: the comparator pin expired {pin_expiry}, before this run started \
1552             {started_utc} — every ratio on this band is COMPARATOR_STALE and blocks MEASURED \
1553             until the pin is refreshed",
1554            self.concurrency
1555        ));
1556        self
1557    }
1558
1559    /// §7.4 — may this band be a comparator baseline?
1560    #[must_use]
1561    pub fn baseline_eligible(&self) -> bool {
1562        self.status.baseline_eligible()
1563    }
1564}
1565
1566/// Every per-request rule, applied to one record.
1567fn validate_request(c: u32, i: usize, r: &RequestOutcome, window_ms: f64) -> Result<(), String> {
1568    let at = format!("band c={c} request[{i}]");
1569    if r.issued_ms >= window_ms {
1570        return Err(format!(
1571            "{at}: issued_ms={} >= T={window_ms} — PP-10: no request is issued at or after the \
1572             window close, and its tokens are never counted",
1573            r.issued_ms
1574        ));
1575    }
1576    if r.settled_ms < r.issued_ms {
1577        return Err(format!(
1578            "{at}: settled_ms={} precedes issued_ms={}",
1579            r.settled_ms, r.issued_ms
1580        ));
1581    }
1582    validate_outcome(&at, r, window_ms)
1583}
1584
1585/// The label a request carries must agree with the clock.
1586fn validate_outcome(at: &str, r: &RequestOutcome, window_ms: f64) -> Result<(), String> {
1587    let d = r.duration_ms();
1588    match r.outcome {
1589        Outcome::Completed if r.generated_tokens == 0 => Err(format!(
1590            "{at}: completed with zero generated tokens — a zero-token response is a failure, not \
1591             a fast request"
1592        )),
1593        Outcome::Timeout if d < REQUEST_TIMEOUT_MS => Err(format!(
1594            "{at}: labelled Timeout but ran {d:.1} ms < the {REQUEST_TIMEOUT_MS} ms hard timeout \
1595             (§3) — that is a Failed, and the two are separate counters"
1596        )),
1597        Outcome::Failed if d >= REQUEST_TIMEOUT_MS => Err(format!(
1598            "{at}: labelled Failed but ran {d:.1} ms >= the {REQUEST_TIMEOUT_MS} ms hard timeout \
1599             — that is a Timeout, which PP-5 makes fatal to this band's ratio"
1600        )),
1601        Outcome::AbandonedAtDrain if r.settled_ms < window_ms => Err(format!(
1602            "{at}: labelled AbandonedAtDrain but settled at {}, before T={window_ms} — a request \
1603             can only be abandoned during the drain",
1604            r.settled_ms
1605        )),
1606        _ => Ok(()),
1607    }
1608}
1609
1610fn rate_per_sec(count: f64, span_ms: f64) -> f64 {
1611    if span_ms <= 0.0 {
1612        return 0.0;
1613    }
1614    count / (span_ms / 1000.0)
1615}
1616
1617fn sorted(mut v: Vec<f64>) -> Vec<f64> {
1618    v.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
1619    v
1620}
1621
1622/// Percentile by linear interpolation between order statistics.
1623///
1624/// Returns `None` for an empty slice: a percentile of nothing is undefined, and
1625/// returning `0.0` there is how an empty band reads as an instant one.
1626#[must_use]
1627pub fn percentile(sorted_ascending: &[f64], p: f64) -> Option<f64> {
1628    match sorted_ascending.len() {
1629        0 => None,
1630        1 => Some(sorted_ascending[0]),
1631        n => {
1632            let idx = (n as f64 - 1.0) * p;
1633            let lo = idx.floor() as usize;
1634            let hi = (lo + 1).min(n - 1);
1635            let frac = idx - lo as f64;
1636            Some(sorted_ascending[lo].mul_add(1.0 - frac, sorted_ascending[hi] * frac))
1637        }
1638    }
1639}
1640
1641#[cfg(test)]
1642mod tests {
1643    // The `<selftest-name>__<sentence>` spelling is load-bearing: PP-29's
1644    // `scripts/spec_conformance.sh` joins the §6 invariant table to the test
1645    // list on the prefix before the double underscore.
1646    #![allow(non_snake_case)]
1647    use super::*;
1648    use crate::perf_gate::receipt::{TokenCountingMethod, Workload};
1649    use crate::perf_gate::witness::BatchInvariance;
1650
1651    /// A completed request that generated `tokens`, issued at `issued_ms` and
1652    /// settling `dur_ms` later. Non-streaming: no ttft, no token times.
1653    fn done(issued_ms: f64, dur_ms: f64, tokens: u32) -> RequestOutcome {
1654        RequestOutcome::completed(issued_ms, issued_ms + dur_ms, tokens)
1655    }
1656
1657    /// The same, streamed live: the first token lands early and the rest are
1658    /// spread over the request, so `ttft/e2e` is far below the live threshold.
1659    fn streamed(issued_ms: f64, dur_ms: f64, tokens: u32) -> RequestOutcome {
1660        let ttft = dur_ms * 0.08;
1661        let times: Vec<f64> = (0..tokens)
1662            .map(|k| issued_ms + ttft + f64::from(k) * (dur_ms - ttft) / f64::from(tokens))
1663            .collect();
1664        done(issued_ms, dur_ms, tokens)
1665            .streamed(ttft, times)
1666            .server_prefill(512, dur_ms * 0.05)
1667    }
1668
1669    fn unmeasured() -> ComparatorStatus {
1670        ComparatorStatus::unmeasured("perf-gate", "no comparator lane on this cell yet (PP-25)")
1671    }
1672
1673    /// A band at `c = 1`. A single stream forms no batch, so PP-26's witness is
1674    /// out of scope here and these tests exercise the drain and counter rules
1675    /// on their own; the correctness rules have `conformant_band(c > 1)`.
1676    fn band(window_ms: f64, requests: Vec<RequestOutcome>) -> BandInput {
1677        BandInput::new(1, window_ms, requests, unmeasured())
1678    }
1679
1680    fn passing_witness() -> BatchInvarianceWitness {
1681        let tokens: Vec<u32> = (0..128).collect();
1682        BatchInvarianceWitness::compare(&tokens, &tokens, 64).formed_at(4, "perf041")
1683    }
1684
1685    /// A band that satisfies every v3 rule, so status derivation can be tested
1686    /// one departure at a time.
1687    fn conformant_band(concurrency: u32) -> BandInput {
1688        let requests: Vec<RequestOutcome> = (0..8)
1689            .map(|i| streamed(f64::from(i) * 100.0, 90.0 + f64::from(i), 128))
1690            .collect();
1691        BandInput::new(concurrency, 1000.0, requests, unmeasured())
1692            .n_predict(128)
1693            .stream_mode(StreamMode::Live)
1694            .witness(passing_witness())
1695    }
1696
1697    /// THE POINT OF THE TICKET, half one: nothing in flight at `T` means the
1698    /// drain phase had zero length. `0.0` here is a measurement, not a default.
1699    #[test]
1700    fn drain_ms_is_zero_when_nothing_straddles_the_window_close() {
1701        let d = band(1000.0, vec![done(0.0, 100.0, 128), done(200.0, 100.0, 128)])
1702            .derive()
1703            .expect("valid band");
1704        assert_eq!(d.drain_ms, 0.0);
1705        assert!(d.suspect.is_empty(), "{:?}", d.suspect);
1706    }
1707
1708    /// THE POINT OF THE TICKET, half two: the SAME code path over a band whose
1709    /// last request ran past `T` yields a DIFFERENT, non-zero `drain_ms`. A
1710    /// defaulted field cannot do this.
1711    #[test]
1712    fn drain_ms_varies_with_actual_drain_behaviour() {
1713        let quiet = band(1000.0, vec![done(0.0, 100.0, 128), done(900.0, 50.0, 128)])
1714            .derive()
1715            .expect("valid band");
1716        let straggler = band(1000.0, vec![done(0.0, 100.0, 128), done(900.0, 350.0, 128)])
1717            .derive()
1718            .expect("valid band");
1719        assert_eq!(quiet.drain_ms, 0.0);
1720        assert!((straggler.drain_ms - 250.0).abs() < 1e-9, "{straggler:?}");
1721        assert_ne!(quiet.drain_ms, straggler.drain_ms);
1722    }
1723
1724    /// PP-10 — `drain_ms > 0.5 x window` is annotated SUSPECT.
1725    #[test]
1726    fn a_dominating_request_is_annotated_suspect() {
1727        let d = band(1000.0, vec![done(0.0, 50.0, 128), done(900.0, 700.0, 128)])
1728            .derive()
1729            .expect("valid band");
1730        assert!((d.drain_ms - 600.0).abs() < 1e-9, "{d:?}");
1731        assert_eq!(d.suspect.len(), 1, "{:?}", d.suspect);
1732        assert!(d.suspect[0].contains("drain_ms"), "{:?}", d.suspect);
1733    }
1734
1735    /// And the annotation discriminates: just under the fraction stays clean.
1736    #[test]
1737    fn a_drain_just_under_half_the_window_is_not_suspect() {
1738        let d = band(1000.0, vec![done(0.0, 50.0, 128), done(900.0, 599.0, 128)])
1739            .derive()
1740            .expect("valid band");
1741        assert!((d.drain_ms - 499.0).abs() < 1e-9, "{d:?}");
1742        assert!(d.suspect.is_empty(), "{:?}", d.suspect);
1743    }
1744
1745    /// PP-10, the registered mutation: "issue one request after `T` and count
1746    /// its tokens". The band is refused, so the tokens can never be counted.
1747    #[test]
1748    fn a_request_issued_at_or_after_t_is_refused() {
1749        let at_t = band(1000.0, vec![done(0.0, 10.0, 8), done(1000.0, 10.0, 8)]).derive();
1750        let after_t = band(1000.0, vec![done(0.0, 10.0, 8), done(1500.0, 10.0, 8)]).derive();
1751        for (label, got) in [("at T", at_t), ("after T", after_t)] {
1752            let err = got.expect_err(label);
1753            assert!(err.contains("PP-10"), "{label}: {err}");
1754        }
1755    }
1756
1757    /// THE CONFLATION THIS TICKET EXISTS TO PREVENT. Every W1 request stops at
1758    /// `n_predict = 128` with EOS ignored, i.e. every one has
1759    /// `finish_reason == "length"`. Read in the finish-reason sense, `truncated`
1760    /// would be 8 and `agg`'s "completed, non-truncated" numerator would be
1761    /// EMPTY. In the drain sense it is 0 and the numerator is 1024 tokens.
1762    #[test]
1763    fn max_tokens_truncation_is_not_drain_truncation() {
1764        let reqs: Vec<RequestOutcome> = (0..8)
1765            .map(|i| done(f64::from(i) * 100.0, 90.0, 128))
1766            .collect();
1767        let d = band(1000.0, reqs).derive().expect("valid band");
1768        assert_eq!(
1769            d.truncated, 0,
1770            "no request was abandoned at the drain deadline"
1771        );
1772        assert_eq!(d.completed, 8);
1773        assert_eq!(d.tokens_total, 1024, "the numerator must not be emptied");
1774        assert!(
1775            d.aggregate_tok_per_sec.expect("agg") > 0.0,
1776            "{:?}",
1777            d.aggregate_tok_per_sec
1778        );
1779    }
1780
1781    /// The drain sense: a request still running at the drain deadline.
1782    #[test]
1783    fn an_abandoned_request_increments_truncated_not_completed() {
1784        let abandoned = RequestOutcome::new(900.0, 1400.0, Outcome::AbandonedAtDrain, 12);
1785        let d = band(1000.0, vec![done(0.0, 100.0, 128), abandoned])
1786            .derive()
1787            .expect("valid band");
1788        assert_eq!(d.truncated, 1);
1789        assert_eq!(d.completed, 1);
1790        assert_eq!(
1791            d.tokens_total, 128,
1792            "an abandoned request contributes no tokens"
1793        );
1794        assert!((d.drain_ms - 400.0).abs() < 1e-9, "{d:?}");
1795    }
1796
1797    /// An abandonment that happened before `T` is a contradiction, not a count.
1798    #[test]
1799    fn an_abandonment_before_the_window_close_is_refused() {
1800        let bogus = RequestOutcome::new(100.0, 200.0, Outcome::AbandonedAtDrain, 1);
1801        let err = band(1000.0, vec![done(0.0, 10.0, 8), bogus])
1802            .derive()
1803            .expect_err("settled before T");
1804        assert!(err.contains("only be abandoned during the drain"), "{err}");
1805    }
1806
1807    /// §3 — `timeouts` is its own counter, and the label is checked against
1808    /// the clock rather than trusted.
1809    #[test]
1810    fn timeouts_and_failures_are_separate_and_both_are_verified() {
1811        let timeout = RequestOutcome::new(10.0, 10.0 + REQUEST_TIMEOUT_MS, Outcome::Timeout, 0);
1812        let failure = RequestOutcome::new(20.0, 45.0, Outcome::Failed, 0);
1813        let with_both = band(
1814            1000.0,
1815            vec![done(0.0, 10.0, 8), timeout.clone(), failure.clone()],
1816        );
1817        let d = with_both.derive().expect("valid band");
1818        assert_eq!(d.timeouts, 1);
1819        assert_eq!(d.errors, 1);
1820        assert_eq!(
1821            d.requested,
1822            d.completed + d.timeouts + d.truncated + d.errors,
1823            "the four counters must partition the requests"
1824        );
1825        assert_eq!(
1826            d.status,
1827            BandStatus::NonconformantValid,
1828            "PP-5: a band that timed out is a record, at every schema version"
1829        );
1830        assert_eq!(
1831            with_both.derive_at(2).expect("renders").status,
1832            BandStatus::NonconformantValid,
1833            "…including a v2-dated one"
1834        );
1835    }
1836
1837    /// A `Timeout` that did not reach 120 s is a mislabelled failure. PP-5 makes
1838    /// `timeouts > 0` fatal to a band's ratio, so the label must be earned.
1839    #[test]
1840    fn a_short_request_cannot_be_labelled_a_timeout() {
1841        let liar = RequestOutcome::new(0.0, 50.0, Outcome::Timeout, 0);
1842        let err = band(1000.0, vec![done(500.0, 10.0, 8), liar])
1843            .derive()
1844            .expect_err("50 ms is not a timeout");
1845        assert!(err.contains("hard timeout"), "{err}");
1846    }
1847
1848    /// And the converse: a request that ran past the hard timeout is a timeout,
1849    /// not a generic error that would leave `timeouts == 0`.
1850    #[test]
1851    fn an_over_long_request_cannot_be_labelled_a_plain_failure() {
1852        let liar = RequestOutcome::new(0.0, REQUEST_TIMEOUT_MS + 1.0, Outcome::Failed, 0);
1853        let err = band(1000.0, vec![done(500.0, 10.0, 8), liar])
1854            .derive()
1855            .expect_err("past the hard timeout");
1856        assert!(err.contains("PP-5"), "{err}");
1857    }
1858
1859    /// A zero-token response is a failure, not a fast request.
1860    #[test]
1861    fn a_zero_token_completion_is_refused() {
1862        let err = band(1000.0, vec![done(0.0, 10.0, 0)])
1863            .derive()
1864            .expect_err("zero tokens");
1865        assert!(err.contains("zero-token"), "{err}");
1866    }
1867
1868    #[test]
1869    fn an_empty_band_is_refused() {
1870        let err = band(1000.0, Vec::new()).derive().expect_err("no requests");
1871        assert!(err.contains("vacuous"), "{err}");
1872    }
1873
1874    #[test]
1875    fn a_non_positive_window_is_refused() {
1876        let err = band(0.0, vec![done(-10.0, 5.0, 8)])
1877            .derive()
1878            .expect_err("zero window");
1879        assert!(err.contains("window_ms"), "{err}");
1880    }
1881
1882    /// §3 — the denominator is wall-clock (last completion − first start),
1883    /// never the mean of per-request rates.
1884    #[test]
1885    fn aggregate_is_wall_clock_over_the_whole_span() {
1886        // Two requests, 100 tokens each, spanning 0 -> 2000 ms.
1887        let d = band(
1888            2500.0,
1889            vec![done(0.0, 500.0, 100), done(1000.0, 1000.0, 100)],
1890        )
1891        .derive()
1892        .expect("valid band");
1893        assert!((d.span_ms - 2000.0).abs() < 1e-9, "{d:?}");
1894        assert!(
1895            (d.aggregate_tok_per_sec.expect("agg") - 100.0).abs() < 1e-9,
1896            "200 tokens over 2 s = 100 tok/s, got {:?}",
1897            d.aggregate_tok_per_sec
1898        );
1899    }
1900
1901    /// A non-streaming client cannot observe TTFT or ITL. It says so instead of
1902    /// emitting a plausible number — and at v3 the band is NONCONFORMANT-VALID,
1903    /// because PP-27 requires streaming and PP-4 requires `dec`.
1904    #[test]
1905    fn a_non_streaming_band_names_what_it_could_not_produce() {
1906        let d = band(1000.0, vec![done(0.0, 100.0, 128)])
1907            .derive()
1908            .expect("valid band");
1909        assert_eq!(d.ttft_p50_ms, None);
1910        assert_eq!(d.itl_p95_ms, None);
1911        assert_eq!(d.decode_tok_per_sec, None);
1912        assert_eq!(d.prefill_tok_per_sec, None);
1913        assert_eq!(d.status, BandStatus::NonconformantValid);
1914        let notes = d.unproduced.join("\n");
1915        assert!(notes.contains("PP-27"), "{notes}");
1916        assert!(notes.contains("PP-4"), "{notes}");
1917    }
1918
1919    /// A live-streaming client produces them, and then only the receipt-level
1920    /// notes remain.
1921    #[test]
1922    fn a_streaming_band_produces_ttft_itl_and_decode() {
1923        let one = RequestOutcome::completed(0.0, 500.0, 5)
1924            .streamed(100.0, vec![100.0, 200.0, 300.0, 400.0, 500.0])
1925            .server_prefill(512, 90.0);
1926        let d = BandInput::new(1, 1000.0, vec![one], unmeasured())
1927            .stream_mode(StreamMode::Live)
1928            .n_predict(5)
1929            .derive()
1930            .expect("valid band");
1931        assert_eq!(d.ttft_p50_ms, Some(100.0));
1932        assert_eq!(d.itl_p50_ms, Some(100.0));
1933        // (5 - 1) tokens over (500 - 100) ms = 10 tok/s.
1934        assert_eq!(d.decode_tok_per_sec, Some(10.0));
1935        // 512 prompt tokens over 90 ms.
1936        assert!(
1937            (d.prefill_tok_per_sec.expect("prefill") - 512.0 / 0.09).abs() < 1e-6,
1938            "{:?}",
1939            d.prefill_tok_per_sec
1940        );
1941        assert!(d.unproduced.is_empty(), "{:?}", d.unproduced);
1942        assert_eq!(d.status, BandStatus::Unmeasured, "no comparator lane");
1943    }
1944
1945    #[test]
1946    fn percentile_of_nothing_is_undefined_not_zero() {
1947        assert_eq!(percentile(&[], 0.5), None);
1948        assert_eq!(percentile(&[7.0], 0.95), Some(7.0));
1949        assert_eq!(percentile(&[0.0, 10.0], 0.5), Some(5.0));
1950    }
1951
1952    #[test]
1953    fn comparator_status_renders_the_token_the_gate_reads() {
1954        assert_eq!(unmeasured().wire_token(), "UNMEASURED");
1955        let na = ComparatorStatus::not_applicable("perf-matrix.yaml", "vLLM has no aarch64 build");
1956        assert_eq!(na.wire_token(), "NOT_APPLICABLE");
1957    }
1958
1959    /// §7.4 — the closed vocabulary, spelled exactly. Two of the six are not
1960    /// their variant name, which is why the table is written out.
1961    #[test]
1962    fn status_tokens_are_exactly_the_section_7_4_vocabulary() {
1963        let table = [
1964            (BandStatus::Measured, "MEASURED"),
1965            (BandStatus::Unmeasured, "UNMEASURED"),
1966            (BandStatus::Na, "NA"),
1967            (BandStatus::InvalidCorrectness, "INVALID-CORRECTNESS"),
1968            (BandStatus::NonconformantValid, "NONCONFORMANT-VALID"),
1969            (BandStatus::ComparatorStale, "COMPARATOR_STALE"),
1970        ];
1971        assert_eq!(table.len(), BandStatus::vocabulary().len());
1972        for (status, token) in table {
1973            assert_eq!(status.wire_token(), token);
1974            assert!(
1975                BandStatus::vocabulary().contains(&status),
1976                "{token} missing from the vocabulary"
1977            );
1978        }
1979        assert_ne!(
1980            BandStatus::Na.wire_token(),
1981            "NOT_APPLICABLE",
1982            "§7.4 spells it NA; NOT_APPLICABLE is the legacy comparator_status token"
1983        );
1984        assert!(BandStatus::Measured.baseline_eligible());
1985        for s in BandStatus::vocabulary() {
1986            if s != BandStatus::Measured {
1987                assert!(!s.baseline_eligible(), "{s:?} may not be a baseline");
1988            }
1989        }
1990    }
1991
1992    /// PP-28 must-fire: a completed sample that stopped short of `n_predict`.
1993    #[test]
1994    fn a_completed_sample_short_of_n_predict_is_counted() {
1995        let mut b = conformant_band(1);
1996        b.requests[3].generated_tokens = 67;
1997        let d = b.derive().expect("the band still renders");
1998        assert_eq!(d.short_of_n_predict, 1);
1999        assert_eq!(d.status, BandStatus::NonconformantValid);
2000        assert!(
2001            d.aggregate_tok_per_sec.is_some(),
2002            "the evidence still renders; PP-28 is not fatal to the receipt"
2003        );
2004        let notes = d.unproduced.join("\n");
2005        assert!(notes.contains("PP-28"), "{notes}");
2006    }
2007
2008    /// PP-28 must-not-fire: every retained sample at `n_predict`.
2009    #[test]
2010    fn thirty_of_thirty_at_n_predict_pass() {
2011        let requests: Vec<RequestOutcome> = (0..30)
2012            .map(|i| streamed(f64::from(i) * 30.0, 90.0 + f64::from(i), 128))
2013            .collect();
2014        let d = BandInput::new(1, 1000.0, requests, unmeasured())
2015            .n_predict(128)
2016            .stream_mode(StreamMode::Live)
2017            .derive()
2018            .expect("valid band");
2019        assert_eq!(d.short_of_n_predict, 0);
2020        assert_eq!(d.completed, 30);
2021        assert_eq!(d.status, BandStatus::Unmeasured);
2022    }
2023
2024    /// A band with short samples is a record, never a baseline.
2025    #[test]
2026    fn a_band_with_short_samples_is_nonconformant() {
2027        let mut b = conformant_band(4);
2028        for r in &mut b.requests {
2029            r.generated_tokens = 112;
2030        }
2031        let d = b.derive().expect("renders");
2032        assert_eq!(d.short_of_n_predict, 8);
2033        assert_eq!(d.status, BandStatus::NonconformantValid);
2034        assert!(!d.baseline_eligible());
2035    }
2036
2037    /// A per-request `expected_tokens` overrides the band's pin, so a ragged
2038    /// workload is not counted short for being ragged.
2039    #[test]
2040    fn a_per_request_expectation_overrides_the_band_pin() {
2041        let mut b = conformant_band(1);
2042        b.requests[0].generated_tokens = 64;
2043        assert_eq!(b.derive().expect("renders").short_of_n_predict, 1);
2044        b.requests[0] = b.requests[0].clone().expecting(64);
2045        assert_eq!(b.derive().expect("renders").short_of_n_predict, 0);
2046    }
2047
2048    /// PP-27 must-fire: a replayed stream withholds every latency metric.
2049    #[test]
2050    fn a_replayed_stream_sends_latency_to_unproduced() {
2051        let d = conformant_band(1)
2052            .stream_mode(StreamMode::Replayed)
2053            .derive()
2054            .expect("renders");
2055        assert_eq!(d.decode_tok_per_sec, None);
2056        assert_eq!(d.ttft_p95_ms, None);
2057        assert_eq!(d.itl_p95_ms, None);
2058        assert_eq!(
2059            d.stream_witness.expect("witness").verdict,
2060            StreamVerdict::Replayed
2061        );
2062        assert_eq!(d.status, BandStatus::NonconformantValid);
2063    }
2064
2065    /// And the client's half can overrule a server that SAYS live: a stream
2066    /// whose first token arrives with the last one is a replay however it is
2067    /// labelled.
2068    #[test]
2069    fn a_server_claiming_live_is_overruled_by_the_client_witness() {
2070        let late = RequestOutcome::completed(0.0, 500.0, 4)
2071            .streamed(499.0, vec![499.0, 499.5, 499.8, 500.0])
2072            .server_prefill(512, 40.0);
2073        let d = BandInput::new(1, 1000.0, vec![late], unmeasured())
2074            .stream_mode(StreamMode::Live)
2075            .n_predict(4)
2076            .derive()
2077            .expect("renders");
2078        let w = d.stream_witness.expect("witness");
2079        assert!(w.client_ttft_over_e2e_median > 0.95, "{w:?}");
2080        assert_eq!(w.verdict, StreamVerdict::Replayed);
2081        assert_eq!(d.decode_tok_per_sec, None);
2082    }
2083
2084    /// PP-27's threshold is an exclusive one: a ratio exactly at
2085    /// `stream.live_ttft_over_e2e_max` is still live, one hair above is a
2086    /// replay. Without this the `>` could be a `>=` and nothing would notice.
2087    #[test]
2088    fn the_stream_threshold_is_exclusive_at_the_declared_maximum() {
2089        let ctx = BandContext {
2090            stream_live_ttft_over_e2e_max: 0.95,
2091            ..BandContext::default()
2092        };
2093        let at_threshold = |ratio: f64| {
2094            let e2e = 1000.0;
2095            let ttft = ratio * e2e;
2096            let one = RequestOutcome::completed(0.0, e2e, 4)
2097                .streamed(ttft, vec![ttft, ttft + 10.0, ttft + 20.0, ttft + 30.0])
2098                .server_prefill(512, 40.0);
2099            BandInput::new(1, 2_000.0, vec![one], unmeasured())
2100                .stream_mode(StreamMode::Live)
2101                .n_predict(4)
2102                .derive_in(&ctx)
2103                .expect("renders")
2104        };
2105        assert_eq!(
2106            at_threshold(0.95).stream_witness.expect("witness").verdict,
2107            StreamVerdict::Live,
2108            "exactly at the maximum is still live"
2109        );
2110        assert_eq!(
2111            at_threshold(0.951).stream_witness.expect("witness").verdict,
2112            StreamVerdict::Replayed
2113        );
2114    }
2115
2116    /// PP-27, the rule as it now stands: a server that declares nothing does
2117    /// **not** thereby make its band nonconformant. Upstream `llama-server`
2118    /// declares no `stream_mode` and is not going to; reading its silence as
2119    /// "not live" made every comparator band `NONCONFORMANT-VALID`, so no
2120    /// baseline could ever be conformant and the parity arm could not reach a
2121    /// verdict — a rule about a field the oracle does not emit.
2122    ///
2123    /// The client's `median(ttft / e2e)` measures the same fact and carries the
2124    /// verdict alone: `Live`, sourced `Client`, with `stream_mode` still null on
2125    /// the wire because the server really did declare nothing.
2126    #[test]
2127    fn an_undeclared_stream_the_client_measured_as_live_is_live() {
2128        let d = BandInput::new(1, 1000.0, conformant_band(1).requests, unmeasured())
2129            .n_predict(128)
2130            .derive()
2131            .expect("renders");
2132        let w = d.stream_witness.expect("witness");
2133        assert_eq!(w.verdict, StreamVerdict::Live);
2134        assert_eq!(
2135            w.source,
2136            StreamWitnessSource::Client,
2137            "the server said nothing"
2138        );
2139        assert_eq!(d.stream_mode, None, "and the receipt still says so");
2140        assert!(d.decode_tok_per_sec.is_some(), "a live stream has a dec");
2141        assert_eq!(
2142            d.status,
2143            BandStatus::Unmeasured,
2144            "no comparator lane, but conformant"
2145        );
2146    }
2147
2148    /// The other polarity: silence plus a client ratio that does NOT establish
2149    /// liveness is `Undeclared` — not `Replayed`, because nothing said the
2150    /// answer was pre-computed, and not a pass either. Every latency metric is
2151    /// withheld.
2152    #[test]
2153    fn an_undeclared_stream_the_client_cannot_call_live_is_undeclared() {
2154        // Every token arrives with the last one: ttft/e2e ≈ 1.
2155        let requests: Vec<RequestOutcome> = (0..6)
2156            .map(|i| {
2157                let issued = f64::from(i) * 10.0;
2158                RequestOutcome::completed(issued, issued + 100.0 + f64::from(i), 128)
2159                    .streamed(99.0, vec![issued + 99.0, issued + 99.5, issued + 100.0])
2160            })
2161            .collect();
2162        let d = BandInput::new(1, 1000.0, requests, unmeasured())
2163            .n_predict(128)
2164            .derive()
2165            .expect("renders");
2166        let w = d.stream_witness.expect("witness");
2167        assert_eq!(w.verdict, StreamVerdict::Undeclared);
2168        assert_eq!(w.source, StreamWitnessSource::Client);
2169        assert_eq!(d.decode_tok_per_sec, None);
2170        assert_eq!(d.ttft_p50_ms, None);
2171        assert_eq!(d.itl_p95_ms, None);
2172        assert_eq!(d.status, BandStatus::NonconformantValid);
2173    }
2174
2175    /// PP-26 must-fire: #2753's constant-token batch. The band renders and
2176    /// reports NO throughput at all.
2177    #[test]
2178    fn a_constant_token_batch_is_invalid_correctness() {
2179        let m1: Vec<u32> = (0..128).map(|i| 1000 + i).collect();
2180        let failing = BatchInvarianceWitness::compare(&m1, &vec![474_u32; 128], 64)
2181            .formed_at(3, "scripts/perf041_batched_parity_probe.py");
2182        let d = conformant_band(4)
2183            .witness(failing)
2184            .derive()
2185            .expect("the band still renders");
2186        assert_eq!(d.status, BandStatus::InvalidCorrectness);
2187        assert_eq!(d.aggregate_tok_per_sec, None);
2188        assert_eq!(d.decode_tok_per_sec, None);
2189        assert_eq!(d.prefill_tok_per_sec, None);
2190    }
2191
2192    /// PP-26 must-not-fire.
2193    #[test]
2194    fn identical_128_token_prefixes_pass() {
2195        let d = conformant_band(4).derive().expect("renders");
2196        assert_eq!(
2197            d.witness.expect("witness").batch_invariance,
2198            BatchInvariance::Pass
2199        );
2200        assert_eq!(d.status, BandStatus::Unmeasured, "no comparator lane");
2201        assert!(d.aggregate_tok_per_sec.is_some());
2202    }
2203
2204    /// An `INVALID-CORRECTNESS` band names the three metrics it withheld, so a
2205    /// reader can tell "not measured" from "measured and wrong".
2206    #[test]
2207    fn an_invalid_correctness_band_reports_no_throughput() {
2208        let d = conformant_band(8)
2209            .witness(BatchInvarianceWitness::compare(&[1, 2, 3], &[9, 9, 9], 64))
2210            .derive()
2211            .expect("renders");
2212        assert_eq!(d.status, BandStatus::InvalidCorrectness);
2213        assert!(!d.baseline_eligible());
2214        let notes = d.unproduced.join("\n");
2215        assert!(notes.contains("aggregate_tok_per_sec"), "{notes}");
2216        assert!(notes.contains("decode_tok_per_sec"), "{notes}");
2217        assert!(notes.contains("prefill_tok_per_sec"), "{notes}");
2218    }
2219
2220    /// `c = 1` forms no batch, so it needs no witness and stays valid without
2221    /// one. Applying the rule there would make every single-stream band invalid.
2222    #[test]
2223    fn c1_needs_no_witness() {
2224        let mut b = conformant_band(1);
2225        b.witness = None;
2226        let d = b.derive().expect("renders");
2227        assert_ne!(d.status, BandStatus::InvalidCorrectness);
2228        assert!(d.aggregate_tok_per_sec.is_some());
2229
2230        // …and the same band at c=4 without one is INVALID-CORRECTNESS.
2231        let mut wider = conformant_band(4);
2232        wider.witness = None;
2233        assert_eq!(
2234            wider.derive().expect("renders").status,
2235            BandStatus::InvalidCorrectness
2236        );
2237    }
2238
2239    /// PP-4 — a v2-dated receipt is historical: the v3 rules are not applied to
2240    /// it retroactively, and it is not a baseline either.
2241    #[test]
2242    fn a_v2_receipt_is_historical_not_a_baseline() {
2243        let mut b = conformant_band(4);
2244        b.witness = None;
2245        b.stream_mode = None;
2246        let v2 = b.derive_at(2).expect("renders");
2247        assert_ne!(v2.status, BandStatus::InvalidCorrectness);
2248        assert!(
2249            v2.aggregate_tok_per_sec.is_some(),
2250            "a v2 band keeps its throughput"
2251        );
2252        assert!(!v2.baseline_eligible(), "but is never a baseline");
2253        assert_eq!(
2254            b.derive_at(3).expect("renders").status,
2255            BandStatus::InvalidCorrectness,
2256            "the same band at v3"
2257        );
2258    }
2259
2260    /// PP-4 — a band reporting numbers must report all three. `prefill` absent
2261    /// is a departure, not a silent omission.
2262    #[test]
2263    fn a_measured_band_without_prefill_is_nonconformant() {
2264        let mut b = conformant_band(1);
2265        for r in &mut b.requests {
2266            r.prefill_ms = None;
2267        }
2268        let d = b.derive().expect("renders");
2269        assert_eq!(d.prefill_tok_per_sec, None);
2270        assert_eq!(d.status, BandStatus::NonconformantValid);
2271        assert!(d.unproduced.join("\n").contains("PP-13"));
2272    }
2273
2274    /// §3 — `prefill` is `Σ prompt_tokens / Σ prefill_ms`, over the requests
2275    /// that carry a server-reported duration and no others.
2276    #[test]
2277    fn prefill_is_prompt_tokens_over_server_prefill_ms() {
2278        let a = RequestOutcome::completed(0.0, 500.0, 8)
2279            .streamed(
2280                40.0,
2281                vec![40.0, 100.0, 200.0, 300.0, 350.0, 400.0, 450.0, 500.0],
2282            )
2283            .server_prefill(500, 100.0);
2284        let b = RequestOutcome::completed(10.0, 520.0, 8)
2285            .streamed(
2286                40.0,
2287                vec![50.0, 110.0, 210.0, 310.0, 360.0, 410.0, 460.0, 520.0],
2288            )
2289            .server_prefill(300, 100.0);
2290        // A third request the server gave no timing for contributes nothing.
2291        let c = RequestOutcome::completed(20.0, 530.0, 8)
2292            .streamed(
2293                40.0,
2294                vec![60.0, 120.0, 220.0, 320.0, 370.0, 420.0, 470.0, 530.0],
2295            )
2296            .with_prompt_tokens(9_999);
2297        // And a fourth whose server reported a ZERO prefill duration: a
2298        // zero-length prefill is not a measurement, and admitting it would make
2299        // the sum's denominator smaller and the rate larger.
2300        let zero = RequestOutcome::completed(30.0, 540.0, 8)
2301            .streamed(
2302                40.0,
2303                vec![70.0, 130.0, 230.0, 330.0, 380.0, 430.0, 480.0, 540.0],
2304            )
2305            .server_prefill(7_777, 0.0);
2306        let d = BandInput::new(1, 1000.0, vec![a, b, c, zero], unmeasured())
2307            .stream_mode(StreamMode::Live)
2308            .n_predict(8)
2309            .derive()
2310            .expect("renders");
2311        // 800 prompt tokens over 200 ms = 4000 tok/s.
2312        assert!(
2313            (d.prefill_tok_per_sec.expect("prefill") - 4_000.0).abs() < 1e-9,
2314            "{:?}",
2315            d.prefill_tok_per_sec
2316        );
2317    }
2318
2319    /// §4.3 — five interleaved replicates is the floor; a receipt that ran
2320    /// three is a record.
2321    #[test]
2322    fn fewer_than_five_replicates_makes_the_band_nonconformant() {
2323        let b = conformant_band(1);
2324        let five = BandContext {
2325            replicates: 5,
2326            ..BandContext::default()
2327        };
2328        let three = BandContext {
2329            replicates: 3,
2330            ..BandContext::default()
2331        };
2332        assert_eq!(
2333            b.derive_in(&five).expect("renders").status,
2334            BandStatus::Unmeasured
2335        );
2336        assert_eq!(
2337            b.derive_in(&three).expect("renders").status,
2338            BandStatus::NonconformantValid
2339        );
2340    }
2341
2342    /// §4.3 — and replicates that did not alternate are a record too.
2343    #[test]
2344    fn a_non_interleaved_receipt_makes_the_band_nonconformant() {
2345        let ctx = BandContext {
2346            interleaved: false,
2347            ..BandContext::default()
2348        };
2349        assert_eq!(
2350            conformant_band(1).derive_in(&ctx).expect("renders").status,
2351            BandStatus::NonconformantValid
2352        );
2353    }
2354
2355    /// PP-20 — a stale pin is its own status, ahead of NONCONFORMANT.
2356    #[test]
2357    fn a_stale_pin_renders_comparator_stale() {
2358        let ctx = BandContext {
2359            comparator_stale: true,
2360            ..BandContext::default()
2361        };
2362        let d = conformant_band(1).derive_in(&ctx).expect("renders");
2363        assert_eq!(d.status, BandStatus::ComparatorStale);
2364        assert!(!d.baseline_eligible());
2365    }
2366
2367    // -- PP-3 / PP-22 / PP-5: the join -------------------------------------
2368
2369    fn jkey(c: u32) -> JoinKey {
2370        JoinKey {
2371            host: "lambda".to_string(),
2372            workload: Workload::W1,
2373            band: c,
2374            model: "qwen2.5-coder-7b-apache-q4k-v1".to_string(),
2375            quant: "Q4_K_M".to_string(),
2376            tokenization: TokenCountingMethod::ClientTokenizer,
2377            window_ms: 1_000,
2378            replicates: 5,
2379            interleaved: true,
2380            n_ctx_slot: Some(1024),
2381            kv_type: Some("f16".to_string()),
2382            fa: Some(true),
2383            n_batch: Some(2048),
2384            n_predict: 128,
2385        }
2386    }
2387
2388    fn same_run() -> RunId {
2389        RunId::derive("2026-09-02T10:11:12.345Z", "lambda", &"a".repeat(64), 4242)
2390    }
2391
2392    fn another_run() -> RunId {
2393        RunId::derive("2026-09-02T11:00:00.000Z", "lambda", &"a".repeat(64), 4243)
2394    }
2395
2396    /// PP-3's must-not-fire: a same-run comparator lane joins, and the ratios
2397    /// that come out carry the estimator each metric's unit demands.
2398    #[test]
2399    fn ratio_paired__a_same_run_baseline_joins() {
2400        let subject = conformant_band(1);
2401        let comparator = conformant_band(1);
2402        let id = same_run();
2403        let status = BandInput::join_status(&subject, &comparator, &jkey(1), &jkey(1), (&id, &id))
2404            .expect("a same-run, same-key, timeout-free join");
2405
2406        let ComparatorStatus::Measured(join) = &status else {
2407            panic!("expected Measured, got {status:?}");
2408        };
2409        let (baseline, ratios) = (join.baseline(), join.ratios());
2410        assert_eq!(
2411            baseline.run_id.as_ref(),
2412            Some(&id),
2413            "PP-3: the baseline says which run it came from"
2414        );
2415        assert_eq!(baseline.join_key.as_ref(), Some(&jkey(1)));
2416        assert_eq!(status.wire_token(), "MEASURED");
2417
2418        // Identical lanes are parity, by both estimators.
2419        assert!((ratios.agg.point - 1.0).abs() < 1e-9, "{:?}", ratios.agg);
2420        assert_eq!(ratios.agg.method, RatioMethod::ReplicateTLower);
2421        assert!(
2422            ratios.agg.lcb95.is_none(),
2423            "one replicate bounds no variance (§4.3)"
2424        );
2425        let dec = ratios.dec.as_ref().expect("a live stream has a dec ratio");
2426        assert_eq!(dec.method, RatioMethod::PairedPercentileBootstrap);
2427        assert!((dec.point - 1.0).abs() < 1e-9, "{dec:?}");
2428        assert!(dec.lcb95.is_some(), "the request unit does bound");
2429        assert!(ratios.prefill.is_some(), "both lanes reported prefill");
2430
2431        // …and the joined band is MEASURED end to end.
2432        let joined =
2433            BandInput::join(&subject, &comparator, &jkey(1), &jkey(1), (&id, &id)).expect("joins");
2434        assert_eq!(joined.status, BandStatus::Measured);
2435        assert!(joined.baseline_eligible());
2436    }
2437
2438    /// PP-3's must-fire: a baseline from another invocation saw another thermal
2439    /// state, another free-VRAM figure and another scheduler.
2440    #[test]
2441    fn a_baseline_from_another_run_is_refused() {
2442        let subject = conformant_band(1);
2443        let comparator = conformant_band(1);
2444        let (mine, theirs) = (same_run(), another_run());
2445        assert_ne!(mine, theirs);
2446        let err =
2447            BandInput::join_status(&subject, &comparator, &jkey(1), &jkey(1), (&mine, &theirs))
2448                .expect_err("cross-run baseline");
2449        assert!(err.contains("PP-3"), "{err}");
2450        assert!(err.contains("SAME run"), "{err}");
2451    }
2452
2453    /// PP-22 at the join, not merely at the key: a c=4 subject against a c=16
2454    /// comparator never reaches the estimator.
2455    #[test]
2456    fn a_key_mismatch_stops_the_join_before_any_ratio_is_computed() {
2457        let id = same_run();
2458        let err = BandInput::join_status(
2459            &conformant_band(4),
2460            &conformant_band(16),
2461            &jkey(4),
2462            &jkey(16),
2463            (&id, &id),
2464        )
2465        .expect_err("c=4 against c=16");
2466        assert!(err.contains("band: 4 != 16"), "{err}");
2467    }
2468
2469    /// PP-5's must-fire: the requests that did not return are exactly the ones
2470    /// a ratio would have to account for.
2471    #[test]
2472    fn a_timed_out_band_cannot_carry_a_ratio() {
2473        let id = same_run();
2474        let mut timed_out = conformant_band(1);
2475        timed_out.requests.push(RequestOutcome::new(
2476            10.0,
2477            10.0 + REQUEST_TIMEOUT_MS,
2478            Outcome::Timeout,
2479            0,
2480        ));
2481        assert_eq!(
2482            timed_out.derive().expect("renders").timeouts,
2483            1,
2484            "control: the band itself still renders its evidence"
2485        );
2486
2487        let subject_side = BandInput::join_status(
2488            &timed_out,
2489            &conformant_band(1),
2490            &jkey(1),
2491            &jkey(1),
2492            (&id, &id),
2493        )
2494        .expect_err("the subject timed out");
2495        assert!(subject_side.contains("PP-5"), "{subject_side}");
2496        assert!(subject_side.contains("subject"), "{subject_side}");
2497
2498        let comparator_side = BandInput::join_status(
2499            &conformant_band(1),
2500            &timed_out,
2501            &jkey(1),
2502            &jkey(1),
2503            (&id, &id),
2504        )
2505        .expect_err("the comparator timed out");
2506        assert!(comparator_side.contains("comparator"), "{comparator_side}");
2507
2508        // …and the clean pair still joins, so the refusal is about the timeout.
2509        BandInput::join_status(
2510            &conformant_band(1),
2511            &conformant_band(1),
2512            &jkey(1),
2513            &jkey(1),
2514            (&id, &id),
2515        )
2516        .expect("a clean pair joins");
2517    }
2518
2519    /// The ratio direction is subject over comparator, and it MOVES: a faster
2520    /// subject gives a ratio above 1.
2521    #[test]
2522    fn the_joined_ratio_is_subject_over_comparator() {
2523        let id = same_run();
2524        let subject = conformant_band(1);
2525        // Halve every comparator request's duration: twice the throughput.
2526        let mut fast_comparator = conformant_band(1);
2527        for r in &mut fast_comparator.requests {
2528            let dur = r.settled_ms - r.issued_ms;
2529            r.settled_ms = r.issued_ms + dur / 2.0;
2530            let first = r.token_times_ms[0];
2531            for t in &mut r.token_times_ms {
2532                *t = first + (*t - first) / 2.0;
2533            }
2534        }
2535        let status =
2536            BandInput::join_status(&subject, &fast_comparator, &jkey(1), &jkey(1), (&id, &id))
2537                .expect("joins");
2538        let ComparatorStatus::Measured(join) = &status else {
2539            panic!("expected Measured");
2540        };
2541        let ratios = join.ratios();
2542        assert!(
2543            ratios.agg.point < 1.0,
2544            "a slower subject is below parity: {:?}",
2545            ratios.agg
2546        );
2547        let dec = ratios.dec.as_ref().expect("dec ratio");
2548        assert!((dec.point - 0.5).abs() < 0.02, "{dec:?}");
2549    }
2550
2551    // -- §7.4 precedence, pairwise ----------------------------------------
2552
2553    /// §7.4's order, as a table. Every adjacent pair, both ways round, so a
2554    /// flipped comparison in `rank` or an inverted `stronger_of` is caught by
2555    /// name rather than by a downstream status happening to differ.
2556    #[test]
2557    fn the_status_precedence_is_a_total_order_correctness_first() {
2558        use BandStatus::{
2559            ComparatorStale, InvalidCorrectness, Measured, Na, NonconformantValid, Unmeasured,
2560        };
2561        let strongest_first = [
2562            InvalidCorrectness,
2563            ComparatorStale,
2564            Na,
2565            NonconformantValid,
2566            Unmeasured,
2567            Measured,
2568        ];
2569        for (i, strong) in strongest_first.iter().enumerate() {
2570            for weak in &strongest_first[i + 1..] {
2571                assert_eq!(
2572                    strong.stronger_of(*weak),
2573                    *strong,
2574                    "{strong:?} must win over {weak:?}"
2575                );
2576                assert_eq!(
2577                    weak.stronger_of(*strong),
2578                    *strong,
2579                    "…in either argument order"
2580                );
2581            }
2582            assert_eq!(strong.stronger_of(*strong), *strong, "idempotent");
2583        }
2584        // The vocabulary and the order are the same six tokens: a status added
2585        // to one and not the other would rank arbitrarily.
2586        assert_eq!(strongest_first.len(), BandStatus::vocabulary().len());
2587    }
2588
2589    /// MUST-FIRE, the inversion itself: a `c > 1` band with no witness under an
2590    /// EXPIRED comparator pin stays `INVALID-CORRECTNESS`.
2591    ///
2592    /// `marked_comparator_stale` used to assign the status, so this band came
2593    /// out labelled `COMPARATOR_STALE` — a reader would have concluded the only
2594    /// thing wrong was an out-of-date pin, while the band in fact reported no
2595    /// throughput at all because nothing established the tokens were right.
2596    #[test]
2597    fn an_unwitnessed_batch_under_a_stale_pin_stays_invalid_correctness() {
2598        let ctx = BandContext {
2599            comparator_stale: true,
2600            ..BandContext::default()
2601        };
2602        let unwitnessed = BandInput::new(4, 1000.0, conformant_band(4).requests, unmeasured())
2603            .n_predict(128)
2604            .stream_mode(StreamMode::Live);
2605        let d = unwitnessed
2606            .derive_in(&ctx)
2607            .expect("renders")
2608            .marked_comparator_stale("2026-01-01T00:00:00.000Z", "2026-09-02T10:11:12.345Z");
2609        assert_eq!(d.status, BandStatus::InvalidCorrectness);
2610        assert_eq!(
2611            d.aggregate_tok_per_sec, None,
2612            "and it reports no throughput"
2613        );
2614        assert!(!d.baseline_eligible());
2615        // REVERT -> the same band WITH a witness is COMPARATOR_STALE, which is
2616        // what the stale pin alone is supposed to say.
2617        let witnessed = conformant_band(4)
2618            .derive_in(&ctx)
2619            .expect("renders")
2620            .marked_comparator_stale("2026-01-01T00:00:00.000Z", "2026-09-02T10:11:12.345Z");
2621        assert_eq!(witnessed.status, BandStatus::ComparatorStale);
2622    }
2623
2624    /// `NA` outranks `NONCONFORMANT-VALID`: a band excluded permanently — one
2625    /// that usually never ran at all — is not first a finding about how it ran.
2626    #[test]
2627    fn a_not_applicable_band_is_na_even_when_it_is_also_nonconformant() {
2628        let ctx = BandContext {
2629            interleaved: false,
2630            ..BandContext::default()
2631        };
2632        let na = ComparatorStatus::not_applicable("perf-matrix.yaml", "no Metal path (#2841)");
2633        let d = BandInput::new(1, 1000.0, conformant_band(1).requests, na)
2634            .n_predict(128)
2635            .stream_mode(StreamMode::Live)
2636            .derive_in(&ctx)
2637            .expect("renders");
2638        assert_eq!(d.status, BandStatus::Na);
2639        // …and the same departure over an UNMEASURED comparator is the weaker
2640        // token, so this is a fact about NA and not about the departure.
2641        let d2 = conformant_band(1).derive_in(&ctx).expect("renders");
2642        assert_eq!(d2.status, BandStatus::NonconformantValid);
2643    }
2644
2645    // -- PP-3 / PP-22 / PP-5: the payload has no public constructor ---------
2646
2647    /// PP-3 must-not-fire, at the type level: `ComparatorStatus::Measured`'s
2648    /// payload is a [`MeasuredJoin`] whose fields are private and whose only
2649    /// constructor is `pub(crate)`. Outside this crate there is no expression
2650    /// that builds one, so a baseline from another run — or another band, or a
2651    /// lane that timed out — cannot be attached by writing a struct literal.
2652    ///
2653    /// The compile-fail half cannot be a `#[test]`; it is this, which does not
2654    /// compile from `apr-cli`:
2655    ///
2656    /// ```text
2657    /// ComparatorStatus::Measured(MeasuredJoin { baseline, ratios })  // private fields
2658    /// ComparatorStatus::Measured(MeasuredJoin::sealed(band, ratios)) // private fn
2659    /// ```
2660    ///
2661    /// What IS public is reading, which the receipt renderer needs.
2662    #[test]
2663    fn ratio_paired__the_measured_payload_is_read_only_outside_the_join() {
2664        let id = same_run();
2665        let status = BandInput::join_status(
2666            &conformant_band(1),
2667            &conformant_band(1),
2668            &jkey(1),
2669            &jkey(1),
2670            (&id, &id),
2671        )
2672        .expect("joins");
2673        let ComparatorStatus::Measured(join) = &status else {
2674            panic!("expected Measured");
2675        };
2676        assert_eq!(join.baseline().concurrency, 1);
2677        assert_eq!(join.baseline().run_id.as_ref(), Some(&id));
2678        assert!((join.ratios().agg.point - 1.0).abs() < 1e-9);
2679    }
2680
2681    // -- PP-26: the witness is about the SUBJECT ---------------------------
2682
2683    /// PP-26 must-not-fire on the oracle: a **comparator**-lane band at `c > 1`
2684    /// with no witness is NOT `INVALID-CORRECTNESS` and keeps its throughput.
2685    ///
2686    /// The witness answers "does `apr serve` return the same tokens under
2687    /// batching as it does alone?". `llama-server` is what that question is
2688    /// asked against; demanding it witness itself would red every baseline, and
2689    /// the producer's workaround — copying the SUBJECT's witness onto the
2690    /// comparator band — had a subject-side PASS vouching for the oracle.
2691    #[test]
2692    fn a_comparator_lane_band_needs_no_batch_invariance_witness() {
2693        let subject = BandInput::new(4, 1000.0, conformant_band(4).requests, unmeasured())
2694            .n_predict(128)
2695            .stream_mode(StreamMode::Live);
2696        let subject_band = subject.clone().derive().expect("renders");
2697        assert_eq!(
2698            subject_band.status,
2699            BandStatus::InvalidCorrectness,
2700            "the SUBJECT still needs one"
2701        );
2702
2703        let comparator_band = subject.role(Lane::Llama).derive().expect("renders");
2704        assert_ne!(comparator_band.status, BandStatus::InvalidCorrectness);
2705        assert!(
2706            comparator_band.aggregate_tok_per_sec.is_some(),
2707            "the oracle's throughput is not withheld for a witness it is not the subject of"
2708        );
2709        assert!(
2710            comparator_band.witness.is_none(),
2711            "and it carries no witness of its own"
2712        );
2713    }
2714
2715    /// …and `c = 1` on either lane needs none, so the exemption above is about
2716    /// the LANE and not about the concurrency.
2717    #[test]
2718    fn the_comparator_exemption_is_about_the_lane_not_the_band_width() {
2719        let one = BandInput::new(1, 1000.0, conformant_band(1).requests, unmeasured())
2720            .n_predict(128)
2721            .stream_mode(StreamMode::Live);
2722        assert_ne!(
2723            one.clone().derive().expect("renders").status,
2724            BandStatus::InvalidCorrectness
2725        );
2726        assert_ne!(
2727            one.role(Lane::Llama).derive().expect("renders").status,
2728            BandStatus::InvalidCorrectness
2729        );
2730    }
2731
2732    // -- P-5: a ratio of two withheld numbers -------------------------------
2733
2734    /// MUST-FIRE: `ratios.dec` is `None` when either lane's decode was
2735    /// suppressed as unreliable.
2736    ///
2737    /// `dec` is a paired bootstrap over the RAW request samples, which survive
2738    /// whatever the band decided about them — so a lane whose decode was
2739    /// withheld (here: a replayed stream) still produced a `dec` ratio computed
2740    /// from exactly the samples the band refused to report.
2741    #[test]
2742    fn a_lane_with_suppressed_decode_forms_no_dec_ratio() {
2743        let id = same_run();
2744        let replayed = BandInput::new(1, 1000.0, conformant_band(1).requests, unmeasured())
2745            .n_predict(128)
2746            .stream_mode(StreamMode::Replayed)
2747            .witness(passing_witness());
2748        assert_eq!(
2749            replayed.derive().expect("renders").decode_tok_per_sec,
2750            None,
2751            "the fixture's decode must actually be withheld"
2752        );
2753
2754        let status = BandInput::join_status(
2755            &conformant_band(1),
2756            &replayed,
2757            &jkey(1),
2758            &jkey(1),
2759            (&id, &id),
2760        )
2761        .expect("joins");
2762        let ComparatorStatus::Measured(join) = &status else {
2763            panic!("expected Measured");
2764        };
2765        assert!(
2766            join.ratios().dec.is_none(),
2767            "a ratio whose denominator the band refused to report is not a ratio: {:?}",
2768            join.ratios().dec
2769        );
2770        // REVERT -> GREEN: two live lanes do form one.
2771        let live = BandInput::join_status(
2772            &conformant_band(1),
2773            &conformant_band(1),
2774            &jkey(1),
2775            &jkey(1),
2776            (&id, &id),
2777        )
2778        .expect("joins");
2779        let ComparatorStatus::Measured(join) = &live else {
2780            panic!("expected Measured");
2781        };
2782        assert!(join.ratios().dec.is_some());
2783    }
2784
2785    /// The same rule for `prefill`: a lane with no server timings forms no
2786    /// prefill ratio, and the numerator alone is not one.
2787    #[test]
2788    fn a_lane_without_server_prefill_forms_no_prefill_ratio() {
2789        let id = same_run();
2790        let no_timings: Vec<RequestOutcome> = (0..8)
2791            .map(|i| {
2792                let (issued, dur) = (f64::from(i) * 100.0, 90.0 + f64::from(i));
2793                let ttft = dur * 0.08;
2794                let times: Vec<f64> = (0..128)
2795                    .map(|k| issued + ttft + f64::from(k) * (dur - ttft) / 128.0)
2796                    .collect();
2797                RequestOutcome::completed(issued, issued + dur, 128)
2798                    .with_prompt_tokens(512)
2799                    .streamed(ttft, times)
2800            })
2801            .collect();
2802        let bare = BandInput::new(1, 1000.0, no_timings, unmeasured())
2803            .n_predict(128)
2804            .stream_mode(StreamMode::Live)
2805            .witness(passing_witness());
2806        assert_eq!(bare.derive().expect("renders").prefill_tok_per_sec, None);
2807
2808        let status =
2809            BandInput::join_status(&conformant_band(1), &bare, &jkey(1), &jkey(1), (&id, &id))
2810                .expect("joins");
2811        let ComparatorStatus::Measured(join) = &status else {
2812            panic!("expected Measured");
2813        };
2814        assert!(join.ratios().prefill.is_none());
2815    }
2816
2817    // -- §4.4.2: the driver's protocol violations reach the receipt ---------
2818
2819    /// MUST-FIRE: a protocol departure the DRIVER observed makes the band
2820    /// `NONCONFORMANT-VALID` and is named in `unproduced_fields`.
2821    ///
2822    /// The producer printed these to stdout and dropped them. A violation the
2823    /// operator watched scroll past and the receipt did not carry is a receipt
2824    /// that reads conformant — and the receipt is the only thing the gate sees.
2825    #[test]
2826    fn a_driver_protocol_violation_reaches_the_band_and_its_status() {
2827        let clean = conformant_band(1).derive().expect("renders");
2828        assert_eq!(clean.status, BandStatus::Unmeasured);
2829
2830        let violated = conformant_band(1)
2831            .conformance_violations(vec![
2832                "window closed after 30 samples, below the max(30, 8c) floor".to_string(),
2833            ])
2834            .derive()
2835            .expect("renders");
2836        assert_eq!(violated.status, BandStatus::NonconformantValid);
2837        assert!(
2838            violated
2839                .unproduced
2840                .iter()
2841                .any(|u| u.contains("below the max(30, 8c) floor") && u.contains("§4.4.2")),
2842            "the violation text itself must be on the receipt: {:?}",
2843            violated.unproduced
2844        );
2845    }
2846
2847    /// PP-7 — every band carries its own rows, and the token times are NOT in
2848    /// them (they live in the gz side file the receipt links by digest).
2849    #[test]
2850    fn a_band_carries_one_sample_row_per_request() {
2851        let d = conformant_band(1).derive().expect("renders");
2852        assert_eq!(d.samples.len(), d.requested);
2853        assert_eq!(d.samples[0].index, 0);
2854        assert_eq!(d.samples[0].generated_tokens, 128);
2855        assert_eq!(d.samples[0].prompt_tokens, 512);
2856        assert!(d.samples[0].ttft_ms.is_some());
2857        let json = serde_json::to_string(&d.samples[0]).expect("serialises");
2858        assert!(
2859            !json.contains("token_times"),
2860            "token times stay in the side file: {json}"
2861        );
2862    }
2863}