Skip to main content

router_eval/
lib.rs

1// SPDX-License-Identifier: AGPL-3.0-or-later
2
3#![forbid(unsafe_code)]
4#![cfg(feature = "legacy-selector-fixtures")]
5
6//! Reproducible, content-free M2 routing-policy evaluator.
7
8use router_policy::{
9    AccountStatus, ActivationGate, ActivationGates, Candidate, CandidateSnapshot,
10    CandidateStateRefs, CapabilityDeclaration, CapabilityRequirements, CapabilityState,
11    ClassificationInput, ConformanceEvidence, DecisionMode, DecisionRecord, DecisionRecordContext,
12    DecisionStateRefs, DisabledPolicy, GateStatus, ModelStatus, PinMode, PolicyPack,
13    PolicyStateRefs, PreviousOutcome, PrivacyMode, ProviderStatus, Quality, ReasoningEffort,
14    RequestedAlias, Role, RoleDeclaration, SelectionOptions, SelectionSnapshot, Signal,
15    build_decision_record, candidate_meets_quality_floor, classify, hard_exclusion_for, select,
16};
17use serde::{Deserialize, Serialize};
18use sha2::{Digest, Sha256};
19use std::collections::{BTreeMap, BTreeSet};
20use std::fmt::Write as _;
21use std::sync::Arc;
22use thiserror::Error;
23
24pub const MAX_EVALUATION_CORPUS_BYTES: usize = 1024 * 1024;
25const MAX_EVALUATION_CASES: usize = 1024;
26const REQUIRED_EVALUATION_CASES: usize = 17;
27const MIN_ROUTINE_CASES: usize = 5;
28const MAX_SCARCE_CAPACITY_UNITS: u64 = 1;
29const SYNTHETIC_CANDIDATE_IDS: [&str; 14] = [
30    "reject-protocol",
31    "reject-tools",
32    "reject-modality",
33    "reject-context",
34    "reject-privacy",
35    "reject-provider-support",
36    "reject-policy-activation",
37    "reject-provider-status",
38    "reject-model-status",
39    "reject-account-status",
40    "reject-quota",
41    "reject-budget",
42    "gate-conformance",
43    "gate-operator",
44];
45const REQUIRED_BASELINES: [&str; 3] = [
46    "always_frontier",
47    "fixed_model_per_provider",
48    "ordered_provider_fallback_only",
49];
50const REQUIRED_ACCEPTANCE: [AcceptanceCriterion; 11] = [
51    AcceptanceCriterion::M2Ac01,
52    AcceptanceCriterion::M2Ac02,
53    AcceptanceCriterion::M2Ac03,
54    AcceptanceCriterion::M2Ac04,
55    AcceptanceCriterion::M2Ac05,
56    AcceptanceCriterion::M2Ac06,
57    AcceptanceCriterion::M2Ac07,
58    AcceptanceCriterion::M2Ac08,
59    AcceptanceCriterion::M2Ac09,
60    AcceptanceCriterion::M2Ac10,
61    AcceptanceCriterion::M2Ac11,
62];
63
64#[derive(Debug, Error, PartialEq, Eq)]
65pub enum EvaluationError {
66    #[error("evaluation corpus is invalid")]
67    InvalidCorpus,
68    #[error("evaluation corpus references an invalid fixture")]
69    InvalidFixture,
70    #[error("evaluation thresholds were not met")]
71    ThresholdFailure,
72}
73
74#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
75#[serde(deny_unknown_fields)]
76struct EvaluationCorpus {
77    schema_version: String,
78    corpus_id: String,
79    corpus_version: String,
80    policy_version: String,
81    execution_profile: String,
82    metric_thresholds: MetricThresholds,
83    capability_profiles: BTreeMap<String, CapabilityRequirements>,
84    routes: BTreeMap<String, RouteFixture>,
85    baselines: Vec<BaselineFixture>,
86    cases: Vec<EvaluationCase>,
87    aggregate_assertions: Vec<AggregateAssertion>,
88}
89
90#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
91#[serde(deny_unknown_fields)]
92struct MetricThresholds {
93    expected_route_accuracy_min: f64,
94    capability_preservation_required: f64,
95    routine_frontier_avoidance_min: f64,
96    intelligent_correctness_non_inferior_to: String,
97    routine_standard_scarce_capacity_strictly_less_than: Vec<String>,
98    required_baseline_rows: Vec<String>,
99}
100
101#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
102#[serde(deny_unknown_fields)]
103struct RouteFixture {
104    provider_id: String,
105    model_id: String,
106    account_pseudonym: String,
107    endpoint_pseudonym: String,
108    reasoning_effort: ReasoningEffort,
109    frontier: bool,
110    scarce_capacity_units: u64,
111}
112
113#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
114#[serde(tag = "rule", rename_all = "snake_case", deny_unknown_fields)]
115enum BaselineFixture {
116    StrongestHardEligibleInFirstEligiblePhase {
117        baseline_id: BaselineId,
118    },
119    FixedProviderModelThenDescendWhenHardIneligible {
120        baseline_id: BaselineId,
121        fixed_model_ids: BTreeMap<String, String>,
122    },
123    OrderedCandidatesWithoutTaskRoleSelection {
124        baseline_id: BaselineId,
125        ordered_candidate_ids: Vec<String>,
126    },
127}
128
129impl BaselineFixture {
130    const fn id(&self) -> BaselineId {
131        match self {
132            Self::StrongestHardEligibleInFirstEligiblePhase { baseline_id }
133            | Self::FixedProviderModelThenDescendWhenHardIneligible { baseline_id, .. }
134            | Self::OrderedCandidatesWithoutTaskRoleSelection { baseline_id, .. } => *baseline_id,
135        }
136    }
137}
138
139#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
140#[serde(rename_all = "snake_case")]
141enum BaselineId {
142    AlwaysFrontier,
143    FixedModelPerProvider,
144    OrderedProviderFallbackOnly,
145}
146
147impl BaselineId {
148    const fn as_str(self) -> &'static str {
149        match self {
150            Self::AlwaysFrontier => "always_frontier",
151            Self::FixedModelPerProvider => "fixed_model_per_provider",
152            Self::OrderedProviderFallbackOnly => "ordered_provider_fallback_only",
153        }
154    }
155}
156
157#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
158#[serde(deny_unknown_fields)]
159struct EvaluationCase {
160    case_id: String,
161    acceptance_criteria: Vec<AcceptanceCriterion>,
162    input: CaseInput,
163    expected_role: Role,
164    expected_capability_profile: String,
165    expected_classifier_reasons: Vec<router_policy::ClassifierReason>,
166    expected_outcome: ExpectedOutcome,
167    expected_route: Option<ExpectedRoute>,
168    #[serde(default)]
169    expected_dispatch_route: Option<ExpectedRoute>,
170    #[serde(default)]
171    expected_no_eligible_route: Option<ExpectedNoEligibleRoute>,
172    expected_exclusions: Vec<ExpectedExclusion>,
173    #[serde(default)]
174    record_expectations: Option<RecordExpectations>,
175    #[serde(default)]
176    strategy_expectations: Vec<StrategyExpectation>,
177    metrics: CaseMetrics,
178    assertions: Vec<CaseAssertion>,
179}
180
181#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
182enum AcceptanceCriterion {
183    #[serde(rename = "M2-AC-01")]
184    M2Ac01,
185    #[serde(rename = "M2-AC-02")]
186    M2Ac02,
187    #[serde(rename = "M2-AC-03")]
188    M2Ac03,
189    #[serde(rename = "M2-AC-04")]
190    M2Ac04,
191    #[serde(rename = "M2-AC-05")]
192    M2Ac05,
193    #[serde(rename = "M2-AC-06")]
194    M2Ac06,
195    #[serde(rename = "M2-AC-07")]
196    M2Ac07,
197    #[serde(rename = "M2-AC-08")]
198    M2Ac08,
199    #[serde(rename = "M2-AC-09")]
200    M2Ac09,
201    #[serde(rename = "M2-AC-10")]
202    M2Ac10,
203    #[serde(rename = "M2-AC-11")]
204    M2Ac11,
205}
206
207#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
208#[serde(deny_unknown_fields)]
209struct RecordExpectations {
210    append_only: bool,
211    required_fields: Vec<RequiredRecordField>,
212    forbidden_fields: Vec<ForbiddenRecordField>,
213}
214
215#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
216#[serde(rename_all = "snake_case")]
217enum RequiredRecordField {
218    PolicyVersion,
219    SelectionSnapshotId,
220    StateRefs,
221    SelectedRoute,
222    SelectedRouteTrace,
223    Exclusions,
224}
225
226impl RequiredRecordField {
227    const fn as_str(self) -> &'static str {
228        match self {
229            Self::PolicyVersion => "policy_version",
230            Self::SelectionSnapshotId => "selection_snapshot_id",
231            Self::StateRefs => "state_refs",
232            Self::SelectedRoute => "selected_route",
233            Self::SelectedRouteTrace => "selected_route_trace",
234            Self::Exclusions => "exclusions",
235        }
236    }
237}
238
239#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
240#[serde(rename_all = "snake_case")]
241enum ForbiddenRecordField {
242    Prompt,
243    SourceCode,
244    ToolArguments,
245    ToolResults,
246    RawAccountId,
247    Credential,
248    Token,
249    Authorization,
250    Cookie,
251    UpstreamHeaders,
252    UpstreamBody,
253    EndpointUrl,
254}
255
256impl ForbiddenRecordField {
257    const fn as_str(self) -> &'static str {
258        match self {
259            Self::Prompt => "prompt",
260            Self::SourceCode => "source_code",
261            Self::ToolArguments => "tool_arguments",
262            Self::ToolResults => "tool_results",
263            Self::RawAccountId => "raw_account_id",
264            Self::Credential => "credential",
265            Self::Token => "token",
266            Self::Authorization => "authorization",
267            Self::Cookie => "cookie",
268            Self::UpstreamHeaders => "upstream_headers",
269            Self::UpstreamBody => "upstream_body",
270            Self::EndpointUrl => "endpoint_url",
271        }
272    }
273}
274
275#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
276#[serde(rename_all = "snake_case")]
277enum StrategyId {
278    Intelligent,
279    AlwaysFrontier,
280    FixedModelPerProvider,
281    OrderedProviderFallbackOnly,
282}
283
284impl From<BaselineId> for StrategyId {
285    fn from(value: BaselineId) -> Self {
286        match value {
287            BaselineId::AlwaysFrontier => Self::AlwaysFrontier,
288            BaselineId::FixedModelPerProvider => Self::FixedModelPerProvider,
289            BaselineId::OrderedProviderFallbackOnly => Self::OrderedProviderFallbackOnly,
290        }
291    }
292}
293
294#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
295#[serde(deny_unknown_fields)]
296struct StrategyExpectation {
297    strategy_id: StrategyId,
298    candidate_id: String,
299    objective_correct: bool,
300    scarce_capacity_units: u64,
301}
302
303#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
304#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
305enum CaseAssertion {
306    Equals {
307        path: EqualsPath,
308        value: ScalarValue,
309    },
310    Contains {
311        path: ContainsPath,
312        value: String,
313    },
314    OrderedEquals {
315        path: OrderedPath,
316        value: Vec<router_policy::FilterReason>,
317    },
318    FieldsAbsent {
319        path: RecordPath,
320        value: Vec<ForbiddenRecordField>,
321    },
322    RowSetEquals {
323        path: RowSetPath,
324        value: Vec<BaselineId>,
325    },
326    LessThan {
327        path: ScarcityPath,
328        comparison_path: BaselineScarcityPath,
329    },
330}
331
332#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
333enum EqualsPath {
334    #[serde(rename = "classification.resolved_role")]
335    ClassificationResolvedRole,
336    #[serde(rename = "selected_route.candidate_id")]
337    SelectedRouteCandidateId,
338    #[serde(rename = "classification.confidence")]
339    ClassificationConfidence,
340    #[serde(rename = "classification.minimum_capability.function_tools")]
341    ClassificationFunctionTools,
342    #[serde(rename = "classification.minimum_capability.min_quality")]
343    ClassificationMinQuality,
344    #[serde(rename = "classification.minimum_capability.images")]
345    ClassificationImages,
346    #[serde(rename = "classification.minimum_capability.min_context_tokens")]
347    ClassificationMinContextTokens,
348    #[serde(rename = "outcome")]
349    Outcome,
350    #[serde(rename = "policy_version")]
351    PolicyVersion,
352    #[serde(rename = "decision_record.mode")]
353    DecisionRecordMode,
354    #[serde(rename = "shadow.intelligent_dispatch_count")]
355    ShadowIntelligentDispatchCount,
356    #[serde(rename = "dispatch.candidate_id")]
357    DispatchCandidateId,
358    #[serde(rename = "evaluation.policy_version")]
359    EvaluationPolicyVersion,
360}
361
362#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
363enum ContainsPath {
364    #[serde(rename = "classification.reasons")]
365    ClassificationReasons,
366    #[serde(rename = "exclusions[].category")]
367    ExclusionCategories,
368}
369
370#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
371enum OrderedPath {
372    #[serde(rename = "exclusions[].primary_reason")]
373    ExclusionReasons,
374}
375
376#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
377enum RecordPath {
378    #[serde(rename = "decision_record")]
379    DecisionRecord,
380}
381
382#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
383enum RowSetPath {
384    #[serde(rename = "baseline_rows[].strategy_id")]
385    BaselineStrategyIds,
386}
387
388#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
389enum ScarcityPath {
390    #[serde(rename = "intelligent.scarce_capacity_units")]
391    Intelligent,
392}
393
394#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
395enum BaselineScarcityPath {
396    #[serde(rename = "always_frontier.scarce_capacity_units")]
397    AlwaysFrontier,
398}
399
400#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
401#[serde(untagged)]
402enum ScalarValue {
403    Boolean(bool),
404    Unsigned(u64),
405    Number(f64),
406    Text(String),
407}
408
409#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
410#[serde(deny_unknown_fields)]
411struct CaseInput {
412    alias: RequestedAlias,
413    confidence: f64,
414    signals: Vec<Signal>,
415    candidate_ids: Vec<String>,
416    #[serde(default)]
417    fallback_cause: Option<String>,
418    #[serde(default)]
419    previous_outcome: Option<PreviousOutcome>,
420    #[serde(default)]
421    runtime_observations: BTreeMap<String, RuntimeObservation>,
422    #[serde(default)]
423    mode: Option<DecisionMode>,
424    #[serde(default)]
425    pinned_baseline_candidate_id: Option<String>,
426    #[serde(default)]
427    gate_states: BTreeMap<String, GateStateFixture>,
428}
429
430#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
431#[serde(deny_unknown_fields)]
432struct RuntimeObservation {
433    latency_ms: u64,
434    estimated_cost_microunits: u64,
435}
436
437#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
438#[serde(deny_unknown_fields)]
439struct GateStateFixture {
440    policy_evaluation: GateStatus,
441    protocol_conformance: GateStatus,
442    provider_support: GateStatus,
443    security_review: GateStatus,
444    operator_approval: GateStatus,
445}
446
447#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
448#[serde(rename_all = "snake_case")]
449enum ExpectedOutcome {
450    Selected,
451    ShadowSelected,
452    NoEligibleRoute,
453}
454
455#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
456#[serde(deny_unknown_fields)]
457struct ExpectedRoute {
458    candidate_id: String,
459    provider_id: String,
460    model_id: String,
461    reasoning_effort: ReasoningEffort,
462}
463
464#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
465#[serde(deny_unknown_fields)]
466struct ExpectedNoEligibleRoute {
467    request_id: String,
468    selection_snapshot_id: String,
469    resolved_role: Role,
470    minimum_capability_profile: String,
471    policy_version: String,
472    retryable: bool,
473}
474
475#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
476#[serde(deny_unknown_fields)]
477struct ExpectedExclusion {
478    candidate_id: String,
479    category: router_policy::ExclusionCategory,
480    primary_reason: router_policy::FilterReason,
481    #[serde(default)]
482    diagnostic_reason: Option<String>,
483}
484
485#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
486#[serde(deny_unknown_fields)]
487struct CaseMetrics {
488    objective_correct: bool,
489    capability_preserved: bool,
490    routine_workload: bool,
491    frontier_avoided: bool,
492    scarce_capacity_units: u64,
493}
494
495#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
496#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
497enum AggregateAssertion {
498    MetricAtLeast {
499        metric: RatioMetric,
500        value: f64,
501        acceptance_criteria: Vec<AcceptanceCriterion>,
502    },
503    MetricEquals {
504        metric: RatioMetric,
505        value: f64,
506        acceptance_criteria: Vec<AcceptanceCriterion>,
507    },
508    MetricNonInferior {
509        metric: CorrectnessMetric,
510        baseline: BaselineId,
511        acceptance_criteria: Vec<AcceptanceCriterion>,
512    },
513    MetricStrictlyLess {
514        metric: ScarcityMetric,
515        baselines: Vec<BaselineId>,
516        acceptance_criteria: Vec<AcceptanceCriterion>,
517    },
518    BaselineRowsPresent {
519        baselines: Vec<BaselineId>,
520        policy_version: String,
521        acceptance_criteria: Vec<AcceptanceCriterion>,
522    },
523    AllAutomaticRolesFullyGated {
524        required_gates: Vec<RequiredGate>,
525        acceptance_criteria: Vec<AcceptanceCriterion>,
526    },
527}
528
529impl AggregateAssertion {
530    fn acceptance_criteria(&self) -> &[AcceptanceCriterion] {
531        match self {
532            Self::MetricAtLeast {
533                acceptance_criteria,
534                ..
535            }
536            | Self::MetricEquals {
537                acceptance_criteria,
538                ..
539            }
540            | Self::MetricNonInferior {
541                acceptance_criteria,
542                ..
543            }
544            | Self::MetricStrictlyLess {
545                acceptance_criteria,
546                ..
547            }
548            | Self::BaselineRowsPresent {
549                acceptance_criteria,
550                ..
551            }
552            | Self::AllAutomaticRolesFullyGated {
553                acceptance_criteria,
554                ..
555            } => acceptance_criteria,
556        }
557    }
558}
559
560#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
561#[serde(rename_all = "snake_case")]
562enum RatioMetric {
563    ExpectedRouteAccuracy,
564    CapabilityPreservation,
565    RoutineFrontierAvoidance,
566}
567
568#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
569#[serde(rename_all = "snake_case")]
570enum CorrectnessMetric {
571    IntelligentCorrectness,
572}
573
574#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
575#[serde(rename_all = "snake_case")]
576enum ScarcityMetric {
577    RoutineStandardScarceCapacity,
578}
579
580#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
581#[serde(rename_all = "snake_case")]
582enum RequiredGate {
583    PolicyEvaluation,
584    ProtocolConformance,
585    ProviderSupport,
586    SecurityReview,
587    OperatorApproval,
588}
589
590#[derive(Clone, Debug, PartialEq, Eq)]
591struct StrategyOutcome {
592    candidate_id: Option<String>,
593    objective_correct: bool,
594    scarce_capacity_units: u64,
595}
596
597#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
598#[serde(deny_unknown_fields)]
599pub struct EvaluationReport {
600    pub schema_version: String,
601    pub corpus_id: String,
602    pub corpus_version: String,
603    pub corpus_digest_sha256: String,
604    pub policy_version: String,
605    pub execution_profile: String,
606    pub case_count: u64,
607    pub passed_cases: u64,
608    pub cases: Vec<CaseReportRow>,
609    pub expected_route_accuracy: f64,
610    pub capability_preservation: f64,
611    pub routine_frontier_avoidance: f64,
612    pub intelligent_correctness: f64,
613    pub always_frontier_correctness: f64,
614    pub routine_standard_scarce_capacity: u64,
615    pub baseline_rows: Vec<BaselineReportRow>,
616    pub thresholds_passed: bool,
617}
618
619#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
620#[serde(deny_unknown_fields)]
621pub struct CaseReportRow {
622    pub case_id: String,
623    pub passed: bool,
624    pub resolved_role: Role,
625    pub selected_candidate_id: Option<String>,
626    pub exclusion_reasons: Vec<router_policy::FilterReason>,
627}
628
629#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
630#[serde(deny_unknown_fields)]
631pub struct BaselineReportRow {
632    pub strategy_id: String,
633    pub correctness: f64,
634    pub routine_standard_scarce_capacity: u64,
635}
636
637pub fn evaluate_corpus(bytes: &[u8]) -> Result<EvaluationReport, EvaluationError> {
638    if bytes.len() > MAX_EVALUATION_CORPUS_BYTES {
639        return Err(EvaluationError::InvalidCorpus);
640    }
641    let corpus: EvaluationCorpus =
642        serde_json::from_slice(bytes).map_err(|_| EvaluationError::InvalidCorpus)?;
643    validate_corpus(&corpus)?;
644
645    let digest = sha256_hex(bytes);
646    let mut passed_cases = 0_u64;
647    let mut preservation_total = 0_u64;
648    let mut preservation_passed = 0_u64;
649    let mut routine_total = 0_u64;
650    let mut routine_avoided = 0_u64;
651    let mut intelligent_correct = 0_u64;
652    let mut correctness_total = 0_u64;
653    let mut intelligent_scarce = 0_u64;
654    let mut baseline_scarce = BTreeMap::<BaselineId, u64>::new();
655    let mut baseline_correct = BTreeMap::<BaselineId, u64>::new();
656    let mut case_rows = Vec::with_capacity(corpus.cases.len());
657    let mut exercised_acceptance = BTreeSet::new();
658
659    for baseline in &corpus.baselines {
660        baseline_scarce.insert(baseline.id(), 0);
661        baseline_correct.insert(baseline.id(), 0);
662    }
663
664    for case in &corpus.cases {
665        let classification = classify(&ClassificationInput {
666            requested_alias: case.input.alias,
667            confidence: case.input.confidence,
668            confidence_threshold: 0.80,
669            signals: case.input.signals.clone(),
670            previous_outcome: case.input.previous_outcome,
671            min_context_tokens: corpus
672                .capability_profiles
673                .get(&case.expected_capability_profile)
674                .map_or(0, |profile| profile.min_context_tokens),
675        })
676        .map_err(|_| EvaluationError::InvalidFixture)?;
677
678        let snapshot_id = case.expected_no_eligible_route.as_ref().map_or_else(
679            || format!("snapshot:{}", case.case_id),
680            |value| value.selection_snapshot_id.clone(),
681        );
682        let request_id = case.expected_no_eligible_route.as_ref().map_or_else(
683            || format!("request:{}", case.case_id),
684            |value| value.request_id.clone(),
685        );
686        let candidates = build_candidates(&corpus, case, &snapshot_id)?;
687        let frontier_by_id = candidates
688            .iter()
689            .map(|candidate| {
690                let id = candidate.snapshot().candidate_id.clone();
691                let fixture = corpus.routes.get(&id);
692                (id, fixture.is_some_and(|route| route.frontier))
693            })
694            .collect::<BTreeMap<_, _>>();
695        let scarcity_by_id = candidates
696            .iter()
697            .map(|candidate| {
698                (
699                    candidate.snapshot().candidate_id.clone(),
700                    candidate.snapshot().scarcity_score as u64,
701                )
702            })
703            .collect::<BTreeMap<_, _>>();
704        let snapshot = Arc::new(
705            SelectionSnapshot::new(snapshot_id, candidates)
706                .map_err(|_| EvaluationError::InvalidFixture)?,
707        );
708        let mode = case.input.mode.unwrap_or(DecisionMode::Live);
709        let selection_classification = if case.case_id == "ordered-hard-filters-empty" {
710            comprehensive_filter_classification(&classification)
711        } else {
712            classification.clone()
713        };
714        let result = select(
715            &selection_classification,
716            Arc::clone(&snapshot),
717            &SelectionOptions {
718                request_id,
719                policy_version: corpus.policy_version.clone(),
720                mode,
721                pin_mode: if mode == DecisionMode::Shadow {
722                    PinMode::ShadowBaseline
723                } else {
724                    PinMode::None
725                },
726                pinned_candidate_id: case.input.pinned_baseline_candidate_id.clone(),
727                retryable_when_empty: false,
728                subagent_bearing: false,
729            },
730        );
731
732        let core_passed = match (&case.expected_outcome, &result) {
733            (ExpectedOutcome::Selected, Ok(decision)) => case
734                .expected_route
735                .as_ref()
736                .is_some_and(|expected| route_matches(&decision.intelligent_route, expected)),
737            (ExpectedOutcome::ShadowSelected, Ok(decision)) => {
738                decision.mode == DecisionMode::Shadow
739                    && decision.intelligent_dispatch_count == 0
740                    && case.expected_route.as_ref().is_some_and(|expected| {
741                        route_matches(&decision.intelligent_route, expected)
742                    })
743                    && case
744                        .expected_dispatch_route
745                        .as_ref()
746                        .is_some_and(|expected| route_matches(&decision.dispatch_route, expected))
747            }
748            (ExpectedOutcome::NoEligibleRoute, Err(error)) => case
749                .expected_no_eligible_route
750                .as_ref()
751                .is_some_and(|expected| {
752                    no_eligible_route_matches(error, expected, &corpus.capability_profiles)
753                        && exclusions_match(&error.exclusions, &case.expected_exclusions)
754                }),
755            _ => false,
756        } && classification.resolved_role == case.expected_role
757            && classification.reasons == case.expected_classifier_reasons
758            && classification.minimum_capability
759                == *corpus
760                    .capability_profiles
761                    .get(&case.expected_capability_profile)
762                    .ok_or(EvaluationError::InvalidFixture)?;
763
764        let decision_record = result
765            .as_ref()
766            .ok()
767            .map(|decision| {
768                build_decision_record(
769                    decision,
770                    DecisionRecordContext {
771                        decision_id: format!("decision:{}", case.case_id),
772                        request_id: format!("request:{}", case.case_id),
773                        timestamp: "2026-07-27T00:00:00Z".to_owned(),
774                        policy_version: corpus.policy_version.clone(),
775                        state_refs: DecisionStateRefs {
776                            health: "offline-fixture:health".to_owned(),
777                            quota: "offline-fixture:quota".to_owned(),
778                            budget: "offline-fixture:budget".to_owned(),
779                        },
780                        attempt: 1,
781                        fallback_cause: case.input.fallback_cause.clone(),
782                        replay_safe: true,
783                        latency_ms: 0,
784                    },
785                )
786                .map_err(|_| EvaluationError::InvalidFixture)
787            })
788            .transpose()?;
789
790        let mut strategy_outcomes = BTreeMap::new();
791        let intelligent_candidate_id = result
792            .as_ref()
793            .ok()
794            .map(|decision| decision.intelligent_route.candidate_id.clone());
795        strategy_outcomes.insert(
796            StrategyId::Intelligent,
797            strategy_outcome(
798                intelligent_candidate_id,
799                &snapshot,
800                &selection_classification,
801                &corpus.policy_version,
802                case.expected_outcome,
803                &scarcity_by_id,
804            ),
805        );
806        for baseline in &corpus.baselines {
807            let candidate_id = execute_baseline(
808                baseline,
809                &snapshot,
810                &selection_classification,
811                &corpus.policy_version,
812            );
813            strategy_outcomes.insert(
814                StrategyId::from(baseline.id()),
815                strategy_outcome(
816                    candidate_id,
817                    &snapshot,
818                    &selection_classification,
819                    &corpus.policy_version,
820                    case.expected_outcome,
821                    &scarcity_by_id,
822                ),
823            );
824        }
825
826        let assertions_passed = case.assertions.iter().all(|assertion| {
827            execute_case_assertion(
828                assertion,
829                &classification,
830                &result,
831                decision_record.as_ref(),
832                &strategy_outcomes,
833                &corpus,
834            )
835        });
836        let record_passed = case
837            .record_expectations
838            .as_ref()
839            .is_none_or(|expectations| {
840                decision_record
841                    .as_ref()
842                    .is_some_and(|record| record_expectations_pass(record, expectations))
843            });
844        let strategies_passed = case.strategy_expectations.iter().all(|expectation| {
845            strategy_outcomes
846                .get(&expectation.strategy_id)
847                .is_some_and(|actual| strategy_expectation_matches(actual, expectation))
848        });
849        let intelligent_outcome = strategy_outcomes
850            .get(&StrategyId::Intelligent)
851            .ok_or(EvaluationError::InvalidFixture)?;
852        let actual_frontier_avoided = case.metrics.routine_workload
853            && intelligent_outcome
854                .candidate_id
855                .as_ref()
856                .is_some_and(|id| !frontier_by_id.get(id).copied().unwrap_or(false));
857        let metrics_passed = case.metrics.objective_correct
858            == intelligent_outcome.objective_correct
859            && case.metrics.capability_preserved == intelligent_outcome.objective_correct
860            && case.metrics.frontier_avoided == actual_frontier_avoided
861            && case.metrics.scarce_capacity_units == intelligent_outcome.scarce_capacity_units;
862        let case_passed = core_passed
863            && assertions_passed
864            && record_passed
865            && strategies_passed
866            && metrics_passed;
867
868        if case_passed {
869            passed_cases += 1;
870            exercised_acceptance.extend(case.acceptance_criteria.iter().copied());
871        }
872        case_rows.push(CaseReportRow {
873            case_id: case.case_id.clone(),
874            passed: case_passed,
875            resolved_role: classification.resolved_role,
876            selected_candidate_id: result
877                .as_ref()
878                .ok()
879                .map(|decision| decision.intelligent_route.candidate_id.clone()),
880            exclusion_reasons: match &result {
881                Ok(decision) => decision
882                    .exclusions
883                    .iter()
884                    .map(|exclusion| exclusion.primary_reason)
885                    .collect(),
886                Err(error) => error
887                    .exclusions
888                    .iter()
889                    .map(|exclusion| exclusion.primary_reason)
890                    .collect(),
891            },
892        });
893        if case.metrics.objective_correct {
894            correctness_total += 1;
895            if intelligent_outcome.objective_correct {
896                intelligent_correct += 1;
897            }
898        }
899        if case.metrics.capability_preserved && result.is_ok() {
900            preservation_total += 1;
901            if intelligent_outcome.objective_correct {
902                preservation_passed += 1;
903            }
904        }
905        if case.metrics.routine_workload {
906            routine_total += 1;
907            if let Ok(decision) = &result
908                && !frontier_by_id
909                    .get(&decision.intelligent_route.candidate_id)
910                    .copied()
911                    .unwrap_or(false)
912            {
913                routine_avoided += 1;
914            }
915        }
916        if matches!(classification.resolved_role, Role::Routine | Role::Standard)
917            && let Ok(decision) = &result
918        {
919            intelligent_scarce += scarcity_by_id
920                .get(&decision.intelligent_route.candidate_id)
921                .copied()
922                .unwrap_or_default();
923        }
924
925        for baseline in &corpus.baselines {
926            let outcome = strategy_outcomes
927                .get(&StrategyId::from(baseline.id()))
928                .ok_or(EvaluationError::InvalidFixture)?;
929            if case.metrics.objective_correct && outcome.objective_correct {
930                *baseline_correct
931                    .get_mut(&baseline.id())
932                    .ok_or(EvaluationError::InvalidFixture)? += 1;
933            }
934            if matches!(classification.resolved_role, Role::Routine | Role::Standard) {
935                *baseline_scarce
936                    .get_mut(&baseline.id())
937                    .ok_or(EvaluationError::InvalidFixture)? += outcome.scarce_capacity_units;
938            }
939        }
940    }
941
942    let case_count = corpus.cases.len() as u64;
943    let expected_route_accuracy = ratio(passed_cases, case_count);
944    let capability_preservation = ratio(preservation_passed, preservation_total);
945    let routine_frontier_avoidance = ratio(routine_avoided, routine_total);
946    let intelligent_correctness = ratio(intelligent_correct, correctness_total);
947    let always_frontier_correctness = ratio(
948        *baseline_correct
949            .get(&BaselineId::AlwaysFrontier)
950            .unwrap_or(&0),
951        correctness_total,
952    );
953    let baseline_rows = corpus
954        .baselines
955        .iter()
956        .map(|baseline| BaselineReportRow {
957            strategy_id: baseline.id().as_str().to_owned(),
958            correctness: ratio(
959                *baseline_correct.get(&baseline.id()).unwrap_or(&0),
960                correctness_total,
961            ),
962            routine_standard_scarce_capacity: *baseline_scarce.get(&baseline.id()).unwrap_or(&0),
963        })
964        .collect::<Vec<_>>();
965    let mut aggregate_passed = true;
966    for assertion in &corpus.aggregate_assertions {
967        let passed = execute_aggregate_assertion(
968            assertion,
969            expected_route_accuracy,
970            capability_preservation,
971            routine_frontier_avoidance,
972            intelligent_correctness,
973            intelligent_scarce,
974            &baseline_rows,
975            &corpus.policy_version,
976        );
977        aggregate_passed &= passed;
978        if passed {
979            exercised_acceptance.extend(assertion.acceptance_criteria().iter().copied());
980        }
981    }
982    let acceptance_complete = REQUIRED_ACCEPTANCE
983        .iter()
984        .all(|criterion| exercised_acceptance.contains(criterion));
985    let thresholds_passed = aggregate_passed
986        && acceptance_complete
987        && passed_cases == case_count
988        && expected_route_accuracy >= corpus.metric_thresholds.expected_route_accuracy_min
989        && capability_preservation >= corpus.metric_thresholds.capability_preservation_required
990        && routine_frontier_avoidance >= corpus.metric_thresholds.routine_frontier_avoidance_min
991        && intelligent_correctness >= always_frontier_correctness
992        && baseline_rows
993            .iter()
994            .all(|row| intelligent_scarce < row.routine_standard_scarce_capacity);
995
996    let report = EvaluationReport {
997        schema_version: "1.0.0".to_owned(),
998        corpus_id: corpus.corpus_id,
999        corpus_version: corpus.corpus_version,
1000        corpus_digest_sha256: digest,
1001        policy_version: corpus.policy_version,
1002        execution_profile: corpus.execution_profile,
1003        case_count,
1004        passed_cases,
1005        cases: case_rows,
1006        expected_route_accuracy,
1007        capability_preservation,
1008        routine_frontier_avoidance,
1009        intelligent_correctness,
1010        always_frontier_correctness,
1011        routine_standard_scarce_capacity: intelligent_scarce,
1012        baseline_rows,
1013        thresholds_passed,
1014    };
1015    if report.thresholds_passed {
1016        Ok(report)
1017    } else {
1018        Err(EvaluationError::ThresholdFailure)
1019    }
1020}
1021
1022fn validate_corpus(corpus: &EvaluationCorpus) -> Result<(), EvaluationError> {
1023    let route_ids = corpus.routes.keys().cloned().collect::<BTreeSet<_>>();
1024    let ordered_route_ids = corpus.baselines.iter().find_map(|baseline| match baseline {
1025        BaselineFixture::OrderedCandidatesWithoutTaskRoleSelection {
1026            ordered_candidate_ids,
1027            ..
1028        } => Some(
1029            ordered_candidate_ids
1030                .iter()
1031                .cloned()
1032                .collect::<BTreeSet<_>>(),
1033        ),
1034        _ => None,
1035    });
1036    let providers = corpus
1037        .routes
1038        .values()
1039        .map(|route| route.provider_id.clone())
1040        .collect::<BTreeSet<_>>();
1041    let routes_valid = !corpus.routes.is_empty()
1042        && corpus.routes.iter().all(|(id, route)| {
1043            safe_reference(id)
1044                && safe_reference(&route.provider_id)
1045                && safe_reference(&route.model_id)
1046                && safe_reference(&route.account_pseudonym)
1047                && safe_reference(&route.endpoint_pseudonym)
1048                && route.scarce_capacity_units <= MAX_SCARCE_CAPACITY_UNITS
1049                && route.frontier == (route.scarce_capacity_units > 0)
1050        })
1051        && ordered_route_ids.as_ref() == Some(&route_ids);
1052    let baseline_ids = corpus
1053        .baselines
1054        .iter()
1055        .map(|baseline| baseline.id().as_str())
1056        .collect::<Vec<_>>();
1057    let baseline_shapes_valid = matches!(
1058        corpus.baselines.as_slice(),
1059        [
1060            BaselineFixture::StrongestHardEligibleInFirstEligiblePhase {
1061                baseline_id: BaselineId::AlwaysFrontier
1062            },
1063            BaselineFixture::FixedProviderModelThenDescendWhenHardIneligible {
1064                baseline_id: BaselineId::FixedModelPerProvider,
1065                ..
1066            },
1067            BaselineFixture::OrderedCandidatesWithoutTaskRoleSelection {
1068                baseline_id: BaselineId::OrderedProviderFallbackOnly,
1069                ..
1070            }
1071        ]
1072    );
1073    let baseline_configs_valid = corpus.baselines.iter().all(|baseline| match baseline {
1074        BaselineFixture::StrongestHardEligibleInFirstEligiblePhase { .. } => true,
1075        BaselineFixture::FixedProviderModelThenDescendWhenHardIneligible {
1076            fixed_model_ids,
1077            ..
1078        } => {
1079            fixed_model_ids.keys().cloned().collect::<BTreeSet<_>>() == providers
1080                && fixed_model_ids.iter().all(|(provider, model)| {
1081                    safe_reference(provider)
1082                        && safe_reference(model)
1083                        && corpus.routes.values().any(|route| {
1084                            route.provider_id == *provider
1085                                && route.model_id == *model
1086                                && route.frontier
1087                        })
1088                })
1089        }
1090        BaselineFixture::OrderedCandidatesWithoutTaskRoleSelection {
1091            ordered_candidate_ids,
1092            ..
1093        } => {
1094            !ordered_candidate_ids.is_empty()
1095                && values_are_unique(ordered_candidate_ids)
1096                && ordered_candidate_ids.iter().all(|id| safe_reference(id))
1097        }
1098    });
1099    let mut case_ids = BTreeSet::new();
1100    let cases_valid = corpus.cases.iter().all(|case| {
1101        case_ids.insert(case.case_id.as_str())
1102            && safe_reference(&case.case_id)
1103            && !case.acceptance_criteria.is_empty()
1104            && values_are_unique(&case.acceptance_criteria)
1105            && !case.assertions.is_empty()
1106            && !case.input.candidate_ids.is_empty()
1107            && values_are_unique(&case.input.candidate_ids)
1108            && case.input.candidate_ids.iter().all(|id| safe_reference(id))
1109            && case.input.candidate_ids.iter().all(|id| {
1110                corpus.routes.contains_key(id) || SYNTHETIC_CANDIDATE_IDS.contains(&id.as_str())
1111            })
1112            && case
1113                .input
1114                .runtime_observations
1115                .keys()
1116                .all(|id| case.input.candidate_ids.contains(id))
1117            && case
1118                .input
1119                .gate_states
1120                .keys()
1121                .all(|id| case.input.candidate_ids.contains(id))
1122            && case
1123                .input
1124                .pinned_baseline_candidate_id
1125                .as_ref()
1126                .is_none_or(|id| case.input.candidate_ids.contains(id))
1127            && case
1128                .input
1129                .fallback_cause
1130                .as_ref()
1131                .is_none_or(|value| safe_reference(value))
1132            && corpus
1133                .capability_profiles
1134                .contains_key(&case.expected_capability_profile)
1135            && case
1136                .expected_no_eligible_route
1137                .as_ref()
1138                .is_none_or(|expected| {
1139                    corpus
1140                        .capability_profiles
1141                        .contains_key(&expected.minimum_capability_profile)
1142                })
1143            && expected_outcome_shape_valid(case)
1144            && case.expected_route.as_ref().is_none_or(|route| {
1145                safe_reference(&route.candidate_id)
1146                    && case.input.candidate_ids.contains(&route.candidate_id)
1147                    && safe_reference(&route.provider_id)
1148                    && safe_reference(&route.model_id)
1149            })
1150            && case.expected_dispatch_route.as_ref().is_none_or(|route| {
1151                safe_reference(&route.candidate_id)
1152                    && case.input.candidate_ids.contains(&route.candidate_id)
1153                    && safe_reference(&route.provider_id)
1154                    && safe_reference(&route.model_id)
1155            })
1156            && case.expected_exclusions.iter().all(|exclusion| {
1157                safe_reference(&exclusion.candidate_id)
1158                    && case.input.candidate_ids.contains(&exclusion.candidate_id)
1159                    && exclusion
1160                        .diagnostic_reason
1161                        .as_ref()
1162                        .is_none_or(|value| safe_reference(value))
1163            })
1164            && case.strategy_expectations.iter().all(|expectation| {
1165                safe_reference(&expectation.candidate_id)
1166                    && case.input.candidate_ids.contains(&expectation.candidate_id)
1167            })
1168            && values_are_unique(
1169                &case
1170                    .strategy_expectations
1171                    .iter()
1172                    .map(|expectation| expectation.strategy_id)
1173                    .collect::<Vec<_>>(),
1174            )
1175            && (case.strategy_expectations.is_empty()
1176                || case
1177                    .strategy_expectations
1178                    .iter()
1179                    .map(|expectation| expectation.strategy_id)
1180                    .eq([
1181                        StrategyId::Intelligent,
1182                        StrategyId::AlwaysFrontier,
1183                        StrategyId::FixedModelPerProvider,
1184                        StrategyId::OrderedProviderFallbackOnly,
1185                    ]))
1186            && case
1187                .record_expectations
1188                .as_ref()
1189                .is_none_or(|expectations| {
1190                    expectations.append_only
1191                        && expectations.required_fields
1192                            == [
1193                                RequiredRecordField::PolicyVersion,
1194                                RequiredRecordField::SelectionSnapshotId,
1195                                RequiredRecordField::StateRefs,
1196                                RequiredRecordField::SelectedRoute,
1197                                RequiredRecordField::SelectedRouteTrace,
1198                                RequiredRecordField::Exclusions,
1199                            ]
1200                        && expectations.forbidden_fields
1201                            == [
1202                                ForbiddenRecordField::Prompt,
1203                                ForbiddenRecordField::SourceCode,
1204                                ForbiddenRecordField::ToolArguments,
1205                                ForbiddenRecordField::ToolResults,
1206                                ForbiddenRecordField::RawAccountId,
1207                                ForbiddenRecordField::Credential,
1208                                ForbiddenRecordField::Token,
1209                                ForbiddenRecordField::Authorization,
1210                                ForbiddenRecordField::Cookie,
1211                                ForbiddenRecordField::UpstreamHeaders,
1212                                ForbiddenRecordField::UpstreamBody,
1213                                ForbiddenRecordField::EndpointUrl,
1214                            ]
1215                })
1216            && case
1217                .acceptance_criteria
1218                .iter()
1219                .all(|criterion| case_supports_acceptance(case, *criterion))
1220    });
1221    let declared_acceptance = corpus
1222        .cases
1223        .iter()
1224        .flat_map(|case| case.acceptance_criteria.iter().copied())
1225        .chain(
1226            corpus
1227                .aggregate_assertions
1228                .iter()
1229                .flat_map(|assertion| assertion.acceptance_criteria().iter().copied()),
1230        )
1231        .collect::<BTreeSet<_>>();
1232    let aggregate_valid = aggregate_assertions_are_frozen(&corpus.aggregate_assertions);
1233    if corpus.schema_version != "1.0.0"
1234        || !safe_reference(&corpus.corpus_id)
1235        || !valid_version(&corpus.corpus_version)
1236        || !valid_version(&corpus.policy_version)
1237        || corpus.execution_profile != "offline_fixture"
1238        || corpus.cases.len() != REQUIRED_EVALUATION_CASES
1239        || corpus.cases.len() > MAX_EVALUATION_CASES
1240        || !routes_valid
1241        || baseline_ids != REQUIRED_BASELINES
1242        || !baseline_shapes_valid
1243        || !baseline_configs_valid
1244        || !cases_valid
1245        || !aggregate_valid
1246        || declared_acceptance != REQUIRED_ACCEPTANCE.into_iter().collect()
1247        || corpus.metric_thresholds.required_baseline_rows != REQUIRED_BASELINES.map(str::to_owned)
1248        || corpus
1249            .metric_thresholds
1250            .routine_standard_scarce_capacity_strictly_less_than
1251            != REQUIRED_BASELINES.map(str::to_owned)
1252        || corpus
1253            .metric_thresholds
1254            .intelligent_correctness_non_inferior_to
1255            != "always_frontier"
1256        || corpus.metric_thresholds.expected_route_accuracy_min != 0.95
1257        || corpus.metric_thresholds.capability_preservation_required != 1.0
1258        || corpus.metric_thresholds.routine_frontier_avoidance_min != 0.80
1259        || !corpus
1260            .cases
1261            .iter()
1262            .all(|case| case.metrics.capability_preserved && case.metrics.objective_correct)
1263        || corpus
1264            .cases
1265            .iter()
1266            .filter(|case| case.metrics.routine_workload)
1267            .count()
1268            < MIN_ROUTINE_CASES
1269        || !corpus.cases.iter().any(|case| {
1270            case.strategy_expectations.len() == 4
1271                && case
1272                    .acceptance_criteria
1273                    .contains(&AcceptanceCriterion::M2Ac10)
1274        })
1275    {
1276        return Err(EvaluationError::InvalidCorpus);
1277    }
1278    Ok(())
1279}
1280
1281fn expected_outcome_shape_valid(case: &EvaluationCase) -> bool {
1282    match case.expected_outcome {
1283        ExpectedOutcome::Selected => {
1284            case.expected_route.is_some()
1285                && case.expected_dispatch_route.is_none()
1286                && case.expected_no_eligible_route.is_none()
1287                && case
1288                    .input
1289                    .mode
1290                    .is_none_or(|mode| mode == DecisionMode::Live)
1291        }
1292        ExpectedOutcome::ShadowSelected => {
1293            case.expected_route.is_some()
1294                && case.expected_dispatch_route.is_some()
1295                && case.expected_no_eligible_route.is_none()
1296                && case.input.mode == Some(DecisionMode::Shadow)
1297                && case.input.pinned_baseline_candidate_id.is_some()
1298        }
1299        ExpectedOutcome::NoEligibleRoute => {
1300            case.expected_route.is_none()
1301                && case.expected_dispatch_route.is_none()
1302                && case
1303                    .expected_no_eligible_route
1304                    .as_ref()
1305                    .is_some_and(|expected| {
1306                        safe_reference(&expected.request_id)
1307                            && safe_reference(&expected.selection_snapshot_id)
1308                            && valid_version(&expected.policy_version)
1309                            && corpus_profile_name_is_safe(&expected.minimum_capability_profile)
1310                    })
1311        }
1312    }
1313}
1314
1315fn corpus_profile_name_is_safe(value: &str) -> bool {
1316    safe_reference(value)
1317}
1318
1319fn case_supports_acceptance(case: &EvaluationCase, criterion: AcceptanceCriterion) -> bool {
1320    match criterion {
1321        AcceptanceCriterion::M2Ac01 => case.expected_route.is_some(),
1322        AcceptanceCriterion::M2Ac02 => case
1323            .expected_classifier_reasons
1324            .contains(&router_policy::ClassifierReason::LowConfidenceUpwardGuard),
1325        AcceptanceCriterion::M2Ac03 => {
1326            case.expected_outcome == ExpectedOutcome::NoEligibleRoute
1327                && !case.expected_exclusions.is_empty()
1328        }
1329        AcceptanceCriterion::M2Ac04 => {
1330            case.input.fallback_cause.is_some() || case.input.previous_outcome.is_some()
1331        }
1332        AcceptanceCriterion::M2Ac05 => !case.input.runtime_observations.is_empty(),
1333        AcceptanceCriterion::M2Ac06 => case.record_expectations.is_some(),
1334        AcceptanceCriterion::M2Ac07 => case.expected_outcome == ExpectedOutcome::ShadowSelected,
1335        AcceptanceCriterion::M2Ac08 => case.metrics.capability_preserved,
1336        AcceptanceCriterion::M2Ac09 => case.metrics.routine_workload,
1337        AcceptanceCriterion::M2Ac10 => case.strategy_expectations.len() == 4,
1338        AcceptanceCriterion::M2Ac11 => !case.input.gate_states.is_empty(),
1339    }
1340}
1341
1342fn aggregate_assertions_are_frozen(assertions: &[AggregateAssertion]) -> bool {
1343    matches!(
1344        assertions,
1345        [
1346            AggregateAssertion::MetricAtLeast {
1347                metric: RatioMetric::ExpectedRouteAccuracy,
1348                value,
1349                acceptance_criteria,
1350            },
1351            AggregateAssertion::MetricEquals {
1352                metric: RatioMetric::CapabilityPreservation,
1353                value: capability_value,
1354                acceptance_criteria: capability_acceptance,
1355            },
1356            AggregateAssertion::MetricAtLeast {
1357                metric: RatioMetric::RoutineFrontierAvoidance,
1358                value: routine_value,
1359                acceptance_criteria: routine_acceptance,
1360            },
1361            AggregateAssertion::MetricNonInferior {
1362                metric: CorrectnessMetric::IntelligentCorrectness,
1363                baseline: BaselineId::AlwaysFrontier,
1364                acceptance_criteria: non_inferior_acceptance,
1365            },
1366            AggregateAssertion::MetricStrictlyLess {
1367                metric: ScarcityMetric::RoutineStandardScarceCapacity,
1368                baselines,
1369                acceptance_criteria: scarce_acceptance,
1370            },
1371            AggregateAssertion::BaselineRowsPresent {
1372                baselines: row_baselines,
1373                policy_version,
1374                acceptance_criteria: row_acceptance,
1375            },
1376            AggregateAssertion::AllAutomaticRolesFullyGated {
1377                required_gates,
1378                acceptance_criteria: gate_acceptance,
1379            },
1380        ] if *value == 0.95
1381            && *capability_value == 1.0
1382            && *routine_value == 0.80
1383            && *acceptance_criteria == [AcceptanceCriterion::M2Ac08]
1384            && *capability_acceptance == [AcceptanceCriterion::M2Ac08]
1385            && *routine_acceptance == [AcceptanceCriterion::M2Ac09]
1386            && *non_inferior_acceptance == [AcceptanceCriterion::M2Ac10]
1387            && *scarce_acceptance == [AcceptanceCriterion::M2Ac10]
1388            && *row_acceptance == [AcceptanceCriterion::M2Ac10]
1389            && *gate_acceptance == [AcceptanceCriterion::M2Ac11]
1390            && baselines == &REQUIRED_BASELINES.map(|value| match value {
1391                "always_frontier" => BaselineId::AlwaysFrontier,
1392                "fixed_model_per_provider" => BaselineId::FixedModelPerProvider,
1393                _ => BaselineId::OrderedProviderFallbackOnly,
1394            })
1395            && row_baselines == baselines
1396            && policy_version == "1.0.0"
1397            && *required_gates == [
1398                RequiredGate::PolicyEvaluation,
1399                RequiredGate::ProtocolConformance,
1400                RequiredGate::ProviderSupport,
1401                RequiredGate::SecurityReview,
1402                RequiredGate::OperatorApproval,
1403            ]
1404    )
1405}
1406
1407fn values_are_unique<T: Ord + Clone>(values: &[T]) -> bool {
1408    values.iter().cloned().collect::<BTreeSet<_>>().len() == values.len()
1409}
1410
1411fn build_candidates(
1412    corpus: &EvaluationCorpus,
1413    case: &EvaluationCase,
1414    snapshot_id: &str,
1415) -> Result<Vec<Candidate>, EvaluationError> {
1416    case.input
1417        .candidate_ids
1418        .iter()
1419        .map(|id| {
1420            let mut snapshot = candidate_snapshot(corpus.routes.get(id), id, snapshot_id)?;
1421            if let Some(observation) = case.input.runtime_observations.get(id) {
1422                snapshot.estimated_latency_ms = observation.latency_ms;
1423                snapshot.estimated_cost_microunits = observation.estimated_cost_microunits;
1424            }
1425            apply_case_overrides(&mut snapshot, id, &case.case_id);
1426            if let Some(gates) = case.input.gate_states.get(id) {
1427                candidate_from_gates(snapshot, &corpus.policy_version, gates)
1428            } else if id == "reject-provider-support" {
1429                let fixture = GateStateFixture {
1430                    policy_evaluation: GateStatus::Passed,
1431                    protocol_conformance: GateStatus::Passed,
1432                    provider_support: GateStatus::Blocked,
1433                    security_review: GateStatus::Passed,
1434                    operator_approval: GateStatus::Passed,
1435                };
1436                candidate_from_gates(snapshot, &corpus.policy_version, &fixture)
1437            } else if id == "reject-policy-activation" {
1438                let fixture = GateStateFixture {
1439                    policy_evaluation: GateStatus::Passed,
1440                    protocol_conformance: GateStatus::Passed,
1441                    provider_support: GateStatus::Passed,
1442                    security_review: GateStatus::Passed,
1443                    operator_approval: GateStatus::Blocked,
1444                };
1445                candidate_from_gates(snapshot, &corpus.policy_version, &fixture)
1446            } else {
1447                Ok(Candidate::offline_fixture(snapshot))
1448            }
1449        })
1450        .collect()
1451}
1452
1453fn candidate_snapshot(
1454    fixture: Option<&RouteFixture>,
1455    id: &str,
1456    snapshot_id: &str,
1457) -> Result<CandidateSnapshot, EvaluationError> {
1458    let default = RouteFixture {
1459        provider_id: "provider-a".to_owned(),
1460        model_id: format!("model:{id}"),
1461        account_pseudonym: "acct-a".to_owned(),
1462        endpoint_pseudonym: "endpoint-a".to_owned(),
1463        reasoning_effort: ReasoningEffort::Medium,
1464        frontier: false,
1465        scarce_capacity_units: 0,
1466    };
1467    let route = match fixture {
1468        Some(route) => route,
1469        None if SYNTHETIC_CANDIDATE_IDS.contains(&id) => &default,
1470        None => return Err(EvaluationError::InvalidFixture),
1471    };
1472    let roles = if id.contains("routine") {
1473        vec![Role::Routine]
1474    } else if id.contains("vision") {
1475        vec![Role::Vision]
1476    } else if id.contains("long-context") {
1477        vec![Role::LongContext]
1478    } else if id.contains("frontier") || id.contains("complex") {
1479        vec![
1480            Role::Routine,
1481            Role::Standard,
1482            Role::Complex,
1483            Role::Vision,
1484            Role::LongContext,
1485        ]
1486    } else {
1487        vec![Role::Standard]
1488    };
1489    Ok(CandidateSnapshot {
1490        candidate_id: id.to_owned(),
1491        selection_snapshot_id: snapshot_id.to_owned(),
1492        phase: if id.starts_with("fallback-") {
1493            "fallback".to_owned()
1494        } else {
1495            "primary".to_owned()
1496        },
1497        phase_priority: u32::from(id.starts_with("fallback-")),
1498        subagent_capable: true,
1499        provider_id: route.provider_id.clone(),
1500        account_pseudonym: route.account_pseudonym.clone(),
1501        model_id: route.model_id.clone(),
1502        reasoning_effort: route.reasoning_effort,
1503        endpoint_pseudonym: route.endpoint_pseudonym.clone(),
1504        billing_class: "offline_fixture".to_owned(),
1505        roles,
1506        capabilities: CapabilityDeclaration {
1507            wire_protocol: CapabilityState::Verified,
1508            streaming: CapabilityState::Verified,
1509            function_tools: CapabilityState::Verified,
1510            tool_result_continuation: CapabilityState::Verified,
1511            images: if id.contains("vision") || id.contains("frontier") {
1512                CapabilityState::Verified
1513            } else {
1514                CapabilityState::Unsupported
1515            },
1516            structured_output: CapabilityState::Verified,
1517            public_reasoning: CapabilityState::Verified,
1518            audio: CapabilityState::Unsupported,
1519            max_context_tokens: if id.contains("long-context") || id.contains("frontier") {
1520                200_000
1521            } else {
1522                32_768
1523            },
1524            privacy_modes: vec![PrivacyMode::ConfiguredProviderAllowed],
1525        },
1526        provider_status: ProviderStatus::Healthy,
1527        model_status: ModelStatus::Available,
1528        account_status: AccountStatus::Available,
1529        quota_headroom: Some(1.0),
1530        budget_headroom: Some(1.0),
1531        scarcity_score: route.scarce_capacity_units as f64,
1532        estimated_cost_microunits: 0,
1533        estimated_latency_ms: 1,
1534        state_refs: CandidateStateRefs {
1535            catalog: "offline-fixture:catalog".to_owned(),
1536            health: "offline-fixture:health".to_owned(),
1537            quota: "offline-fixture:quota".to_owned(),
1538            budget: "offline-fixture:budget".to_owned(),
1539            conformance: "offline-fixture:conformance".to_owned(),
1540        },
1541    })
1542}
1543
1544fn apply_case_overrides(snapshot: &mut CandidateSnapshot, id: &str, case_id: &str) {
1545    if case_id == "ordered-hard-filters-empty" {
1546        snapshot.capabilities.wire_protocol = CapabilityState::Verified;
1547        snapshot.capabilities.streaming = CapabilityState::Verified;
1548        snapshot.capabilities.function_tools = CapabilityState::Verified;
1549        snapshot.capabilities.tool_result_continuation = CapabilityState::Verified;
1550        snapshot.capabilities.images = CapabilityState::Verified;
1551        snapshot.capabilities.structured_output = CapabilityState::Verified;
1552        snapshot.capabilities.public_reasoning = CapabilityState::Verified;
1553        snapshot.capabilities.audio = CapabilityState::Verified;
1554        snapshot.capabilities.max_context_tokens = 200_000;
1555        snapshot.capabilities.privacy_modes = vec![PrivacyMode::ConfiguredProviderAllowed];
1556    }
1557    match id {
1558        "reject-protocol" => snapshot.capabilities.wire_protocol = CapabilityState::Unknown,
1559        "reject-tools" => snapshot.capabilities.function_tools = CapabilityState::Unknown,
1560        "reject-modality" => snapshot.capabilities.images = CapabilityState::Unsupported,
1561        "reject-context" => snapshot.capabilities.max_context_tokens = 1,
1562        "reject-privacy" => snapshot.capabilities.privacy_modes.clear(),
1563        "reject-provider-status" => snapshot.provider_status = ProviderStatus::Unavailable,
1564        "reject-model-status" => snapshot.model_status = ModelStatus::Unavailable,
1565        "reject-account-status" => snapshot.account_status = AccountStatus::Unavailable,
1566        "reject-quota" => snapshot.quota_headroom = None,
1567        "reject-budget" => snapshot.budget_headroom = None,
1568        _ => {}
1569    }
1570    if case_id == "provider-fallback-preserves-capability" && id == "primary-standard" {
1571        snapshot.provider_status = ProviderStatus::Unavailable;
1572    }
1573}
1574
1575fn comprehensive_filter_classification(
1576    classification: &router_policy::Classification,
1577) -> router_policy::Classification {
1578    let mut classification = classification.clone();
1579    classification.minimum_capability.function_tools = true;
1580    classification.minimum_capability.tool_result_continuation = true;
1581    classification.minimum_capability.images = true;
1582    classification.minimum_capability.structured_output = true;
1583    classification.minimum_capability.public_reasoning = true;
1584    classification.minimum_capability.audio = true;
1585    classification.minimum_capability.min_context_tokens = 200_000;
1586    classification
1587}
1588
1589fn activation_gates(version: &str, fixture: &GateStateFixture) -> ActivationGates {
1590    let gate = |name: &str, status: GateStatus| ActivationGate {
1591        status,
1592        policy_version: version.to_owned(),
1593        evidence_refs: if status == GateStatus::Passed {
1594            vec![format!("offline-fixture:{name}")]
1595        } else {
1596            Vec::new()
1597        },
1598    };
1599    ActivationGates {
1600        policy_evaluation: gate("policy_evaluation", fixture.policy_evaluation),
1601        protocol_conformance: gate("protocol_conformance", fixture.protocol_conformance),
1602        provider_support: gate("provider_support", fixture.provider_support),
1603        security_review: gate("security_review", fixture.security_review),
1604        operator_approval: gate("operator_approval", fixture.operator_approval),
1605    }
1606}
1607
1608fn candidate_from_gates(
1609    snapshot: CandidateSnapshot,
1610    version: &str,
1611    fixture: &GateStateFixture,
1612) -> Result<Candidate, EvaluationError> {
1613    let gates = activation_gates(version, fixture);
1614    let all_passed = gates.all_passed_for(version);
1615    let roles = [
1616        Role::Routine,
1617        Role::Standard,
1618        Role::Complex,
1619        Role::Vision,
1620        Role::LongContext,
1621    ]
1622    .into_iter()
1623    .map(|role| RoleDeclaration {
1624        role,
1625        quality: match role {
1626            Role::Routine => Quality::Routine,
1627            Role::Complex => Quality::Complex,
1628            Role::Standard | Role::Vision | Role::LongContext => Quality::Standard,
1629        },
1630        reasoning_efforts: vec![snapshot.reasoning_effort],
1631        capabilities: snapshot.capabilities.clone(),
1632        model_ids: vec![snapshot.model_id.clone()],
1633        escalates_to: None,
1634        estimated_quota_units: Some(0.0),
1635        estimated_cost_microunits: Some(0),
1636        conformance: ConformanceEvidence {
1637            complete: all_passed,
1638            policy_version: version.to_owned(),
1639            evidence_refs: if all_passed {
1640                vec!["offline-fixture:role-evidence".to_owned()]
1641            } else {
1642                Vec::new()
1643            },
1644        },
1645    })
1646    .collect();
1647    let pack = PolicyPack {
1648        schema_version: "1.0.0".to_owned(),
1649        policy_version: version.to_owned(),
1650        pack_id: format!("pack:{}", snapshot.candidate_id),
1651        provider_id: snapshot.provider_id.clone(),
1652        phase: snapshot.phase.clone(),
1653        billing_class: snapshot.billing_class.clone(),
1654        fixture_only: false,
1655        conformance_complete: all_passed,
1656        operator_approved: all_passed,
1657        automatic_live_eligible: all_passed,
1658        automatic_enabled: all_passed,
1659        live_enabled: all_passed,
1660        confidence_threshold: 0.80,
1661        roles,
1662        gates,
1663        state_refs: PolicyStateRefs {
1664            catalog: "offline-fixture:catalog".to_owned(),
1665            provider_support: "offline-fixture:provider-support".to_owned(),
1666            conformance: "offline-fixture:conformance".to_owned(),
1667        },
1668        disabled: DisabledPolicy {
1669            reasons: if all_passed {
1670                Vec::new()
1671            } else {
1672                vec!["activation_blocked".to_owned()]
1673            },
1674            remediation: Vec::new(),
1675        },
1676    };
1677    Candidate::from_policy_pack(snapshot, &pack).map_err(|_| EvaluationError::InvalidFixture)
1678}
1679
1680fn route_matches(actual: &router_policy::SelectedRoute, expected: &ExpectedRoute) -> bool {
1681    actual.candidate_id == expected.candidate_id
1682        && actual.provider_id == expected.provider_id
1683        && actual.model_id == expected.model_id
1684        && actual.reasoning_effort == expected.reasoning_effort
1685}
1686
1687fn no_eligible_route_matches(
1688    actual: &router_policy::NoEligibleRoute,
1689    expected: &ExpectedNoEligibleRoute,
1690    profiles: &BTreeMap<String, CapabilityRequirements>,
1691) -> bool {
1692    actual.code == "no_eligible_route"
1693        && actual.request_id == expected.request_id
1694        && actual.selection_snapshot_id == expected.selection_snapshot_id
1695        && actual.resolved_role == expected.resolved_role
1696        && profiles
1697            .get(&expected.minimum_capability_profile)
1698            .is_some_and(|profile| actual.minimum_capability == *profile)
1699        && actual.policy_version == expected.policy_version
1700        && actual.retryable == expected.retryable
1701}
1702
1703fn exclusions_match(actual: &[router_policy::Exclusion], expected: &[ExpectedExclusion]) -> bool {
1704    actual.len() == expected.len()
1705        && actual.iter().zip(expected).all(|(actual, expected)| {
1706            actual.candidate_id == expected.candidate_id
1707                && actual.category == expected.category
1708                && actual.primary_reason == expected.primary_reason
1709                && expected
1710                    .diagnostic_reason
1711                    .as_ref()
1712                    .is_none_or(|reason| actual.diagnostic_reasons.contains(reason))
1713        })
1714}
1715
1716fn execute_baseline(
1717    baseline: &BaselineFixture,
1718    snapshot: &SelectionSnapshot,
1719    classification: &router_policy::Classification,
1720    policy_version: &str,
1721) -> Option<String> {
1722    let eligible = snapshot
1723        .candidates()
1724        .iter()
1725        .filter(|candidate| hard_exclusion_for(candidate, classification, policy_version).is_none())
1726        .collect::<Vec<_>>();
1727    match baseline {
1728        BaselineFixture::StrongestHardEligibleInFirstEligiblePhase { .. } => {
1729            let phase = eligible
1730                .iter()
1731                .map(|candidate| candidate.snapshot().phase_priority)
1732                .min()?;
1733            eligible
1734                .into_iter()
1735                .filter(|candidate| candidate.snapshot().phase_priority == phase)
1736                .max_by(|left, right| {
1737                    left.snapshot()
1738                        .reasoning_effort
1739                        .cmp(&right.snapshot().reasoning_effort)
1740                        .then_with(|| {
1741                            right
1742                                .snapshot()
1743                                .candidate_id
1744                                .cmp(&left.snapshot().candidate_id)
1745                        })
1746                })
1747                .map(|candidate| candidate.snapshot().candidate_id.clone())
1748        }
1749        BaselineFixture::FixedProviderModelThenDescendWhenHardIneligible {
1750            fixed_model_ids,
1751            ..
1752        } => {
1753            let phases = eligible
1754                .iter()
1755                .map(|candidate| candidate.snapshot().phase_priority)
1756                .collect::<BTreeSet<_>>();
1757            for phase in phases {
1758                let providers = eligible
1759                    .iter()
1760                    .filter(|candidate| candidate.snapshot().phase_priority == phase)
1761                    .map(|candidate| candidate.snapshot().provider_id.as_str())
1762                    .collect::<BTreeSet<_>>();
1763                for provider in providers {
1764                    let Some(model_id) = fixed_model_ids.get(provider) else {
1765                        continue;
1766                    };
1767                    if let Some(candidate) = eligible.iter().find(|candidate| {
1768                        candidate.snapshot().phase_priority == phase
1769                            && candidate.snapshot().provider_id == provider
1770                            && candidate.snapshot().model_id == *model_id
1771                    }) {
1772                        return Some(candidate.snapshot().candidate_id.clone());
1773                    }
1774                }
1775            }
1776            None
1777        }
1778        BaselineFixture::OrderedCandidatesWithoutTaskRoleSelection {
1779            ordered_candidate_ids,
1780            ..
1781        } => ordered_candidate_ids.iter().find_map(|candidate_id| {
1782            eligible
1783                .iter()
1784                .find(|candidate| candidate.snapshot().candidate_id == *candidate_id)
1785                .map(|candidate| candidate.snapshot().candidate_id.clone())
1786        }),
1787    }
1788}
1789
1790fn strategy_outcome(
1791    candidate_id: Option<String>,
1792    snapshot: &SelectionSnapshot,
1793    classification: &router_policy::Classification,
1794    policy_version: &str,
1795    expected_outcome: ExpectedOutcome,
1796    scarcity_by_id: &BTreeMap<String, u64>,
1797) -> StrategyOutcome {
1798    let candidate = candidate_id.as_deref().and_then(|id| {
1799        snapshot
1800            .candidates()
1801            .iter()
1802            .find(|candidate| candidate.snapshot().candidate_id == id)
1803    });
1804    let objective_correct = match expected_outcome {
1805        ExpectedOutcome::NoEligibleRoute => candidate.is_none(),
1806        ExpectedOutcome::Selected | ExpectedOutcome::ShadowSelected => {
1807            candidate.is_some_and(|candidate| {
1808                hard_exclusion_for(candidate, classification, policy_version).is_none()
1809                    && candidate_meets_quality_floor(candidate.snapshot(), classification)
1810            })
1811        }
1812    };
1813    let scarce_capacity_units = candidate_id
1814        .as_ref()
1815        .and_then(|id| scarcity_by_id.get(id))
1816        .copied()
1817        .unwrap_or_default();
1818    StrategyOutcome {
1819        candidate_id,
1820        objective_correct,
1821        scarce_capacity_units,
1822    }
1823}
1824
1825fn execute_case_assertion(
1826    assertion: &CaseAssertion,
1827    classification: &router_policy::Classification,
1828    result: &Result<router_policy::SelectionDecision, Box<router_policy::NoEligibleRoute>>,
1829    decision_record: Option<&DecisionRecord>,
1830    strategy_outcomes: &BTreeMap<StrategyId, StrategyOutcome>,
1831    corpus: &EvaluationCorpus,
1832) -> bool {
1833    match assertion {
1834        CaseAssertion::Equals { path, value } => {
1835            let actual = match path {
1836                EqualsPath::ClassificationResolvedRole => {
1837                    ScalarValue::Text(serialized_name(classification.resolved_role))
1838                }
1839                EqualsPath::SelectedRouteCandidateId => ScalarValue::Text(
1840                    result
1841                        .as_ref()
1842                        .ok()
1843                        .map(|decision| decision.intelligent_route.candidate_id.clone())
1844                        .unwrap_or_default(),
1845                ),
1846                EqualsPath::ClassificationConfidence => {
1847                    ScalarValue::Number(classification.confidence)
1848                }
1849                EqualsPath::ClassificationFunctionTools => {
1850                    ScalarValue::Boolean(classification.minimum_capability.function_tools)
1851                }
1852                EqualsPath::ClassificationMinQuality => ScalarValue::Text(serialized_name(
1853                    classification.minimum_capability.min_quality,
1854                )),
1855                EqualsPath::ClassificationImages => {
1856                    ScalarValue::Boolean(classification.minimum_capability.images)
1857                }
1858                EqualsPath::ClassificationMinContextTokens => {
1859                    ScalarValue::Unsigned(classification.minimum_capability.min_context_tokens)
1860                }
1861                EqualsPath::Outcome => ScalarValue::Text(match result {
1862                    Ok(_) => "selected".to_owned(),
1863                    Err(_) => "no_eligible_route".to_owned(),
1864                }),
1865                EqualsPath::PolicyVersion | EqualsPath::EvaluationPolicyVersion => {
1866                    ScalarValue::Text(corpus.policy_version.clone())
1867                }
1868                EqualsPath::DecisionRecordMode => ScalarValue::Text(
1869                    decision_record
1870                        .map(|record| serialized_name(record.mode))
1871                        .unwrap_or_default(),
1872                ),
1873                EqualsPath::ShadowIntelligentDispatchCount => ScalarValue::Unsigned(
1874                    result
1875                        .as_ref()
1876                        .ok()
1877                        .map_or(0, |decision| decision.intelligent_dispatch_count),
1878                ),
1879                EqualsPath::DispatchCandidateId => ScalarValue::Text(
1880                    result
1881                        .as_ref()
1882                        .ok()
1883                        .map(|decision| decision.dispatch_route.candidate_id.clone())
1884                        .unwrap_or_default(),
1885                ),
1886            };
1887            scalar_values_equal(&actual, value)
1888        }
1889        CaseAssertion::Contains { path, value } => match path {
1890            ContainsPath::ClassificationReasons => classification
1891                .reasons
1892                .iter()
1893                .any(|reason| serialized_name(*reason) == *value),
1894            ContainsPath::ExclusionCategories => exclusions_for(result)
1895                .iter()
1896                .any(|exclusion| serialized_name(exclusion.category) == *value),
1897        },
1898        CaseAssertion::OrderedEquals { path, value } => match path {
1899            OrderedPath::ExclusionReasons => exclusions_for(result)
1900                .iter()
1901                .map(|exclusion| exclusion.primary_reason)
1902                .eq(value.iter().copied()),
1903        },
1904        CaseAssertion::FieldsAbsent { path, value } => match path {
1905            RecordPath::DecisionRecord => decision_record.is_some_and(|record| {
1906                serde_json::to_value(record).is_ok_and(|encoded| {
1907                    value
1908                        .iter()
1909                        .all(|field| !json_contains_key(&encoded, field.as_str()))
1910                })
1911            }),
1912        },
1913        CaseAssertion::RowSetEquals { path, value } => match path {
1914            RowSetPath::BaselineStrategyIds => corpus
1915                .baselines
1916                .iter()
1917                .map(BaselineFixture::id)
1918                .eq(value.iter().copied()),
1919        },
1920        CaseAssertion::LessThan {
1921            path,
1922            comparison_path,
1923        } => match (path, comparison_path) {
1924            (ScarcityPath::Intelligent, BaselineScarcityPath::AlwaysFrontier) => {
1925                let intelligent = strategy_outcomes.get(&StrategyId::Intelligent);
1926                let baseline = strategy_outcomes.get(&StrategyId::AlwaysFrontier);
1927                intelligent
1928                    .zip(baseline)
1929                    .is_some_and(|(intelligent, baseline)| {
1930                        intelligent.scarce_capacity_units < baseline.scarce_capacity_units
1931                    })
1932            }
1933        },
1934    }
1935}
1936
1937fn scalar_values_equal(left: &ScalarValue, right: &ScalarValue) -> bool {
1938    left == right
1939}
1940
1941fn serialized_name(value: impl Serialize) -> String {
1942    serde_json::to_value(value)
1943        .ok()
1944        .and_then(|value| value.as_str().map(str::to_owned))
1945        .unwrap_or_default()
1946}
1947
1948fn exclusions_for(
1949    result: &Result<router_policy::SelectionDecision, Box<router_policy::NoEligibleRoute>>,
1950) -> &[router_policy::Exclusion] {
1951    match result {
1952        Ok(decision) => &decision.exclusions,
1953        Err(error) => &error.exclusions,
1954    }
1955}
1956
1957fn record_expectations_pass(record: &DecisionRecord, expectations: &RecordExpectations) -> bool {
1958    if !expectations.append_only || record.selected_route_trace.len() != 7 {
1959        return false;
1960    }
1961    serde_json::to_value(record).is_ok_and(|encoded| {
1962        encoded.as_object().is_some_and(|object| {
1963            expectations
1964                .required_fields
1965                .iter()
1966                .all(|field| object.contains_key(field.as_str()))
1967        }) && expectations
1968            .forbidden_fields
1969            .iter()
1970            .all(|field| !json_contains_key(&encoded, field.as_str()))
1971    })
1972}
1973
1974fn json_contains_key(value: &serde_json::Value, key: &str) -> bool {
1975    match value {
1976        serde_json::Value::Object(object) => {
1977            object.contains_key(key) || object.values().any(|value| json_contains_key(value, key))
1978        }
1979        serde_json::Value::Array(values) => {
1980            values.iter().any(|value| json_contains_key(value, key))
1981        }
1982        _ => false,
1983    }
1984}
1985
1986fn strategy_expectation_matches(actual: &StrategyOutcome, expected: &StrategyExpectation) -> bool {
1987    actual.candidate_id.as_deref() == Some(expected.candidate_id.as_str())
1988        && actual.objective_correct == expected.objective_correct
1989        && actual.scarce_capacity_units == expected.scarce_capacity_units
1990}
1991
1992#[allow(clippy::too_many_arguments)]
1993fn execute_aggregate_assertion(
1994    assertion: &AggregateAssertion,
1995    expected_route_accuracy: f64,
1996    capability_preservation: f64,
1997    routine_frontier_avoidance: f64,
1998    intelligent_correctness: f64,
1999    intelligent_scarce: u64,
2000    baseline_rows: &[BaselineReportRow],
2001    policy_version: &str,
2002) -> bool {
2003    match assertion {
2004        AggregateAssertion::MetricAtLeast { metric, value, .. } => {
2005            ratio_metric(
2006                *metric,
2007                expected_route_accuracy,
2008                capability_preservation,
2009                routine_frontier_avoidance,
2010            ) >= *value
2011        }
2012        AggregateAssertion::MetricEquals { metric, value, .. } => {
2013            ratio_metric(
2014                *metric,
2015                expected_route_accuracy,
2016                capability_preservation,
2017                routine_frontier_avoidance,
2018            ) == *value
2019        }
2020        AggregateAssertion::MetricNonInferior {
2021            metric, baseline, ..
2022        } => match metric {
2023            CorrectnessMetric::IntelligentCorrectness => baseline_rows
2024                .iter()
2025                .find(|row| row.strategy_id == baseline.as_str())
2026                .is_some_and(|row| intelligent_correctness >= row.correctness),
2027        },
2028        AggregateAssertion::MetricStrictlyLess {
2029            metric, baselines, ..
2030        } => match metric {
2031            ScarcityMetric::RoutineStandardScarceCapacity => baselines.iter().all(|baseline| {
2032                baseline_rows
2033                    .iter()
2034                    .find(|row| row.strategy_id == baseline.as_str())
2035                    .is_some_and(|row| intelligent_scarce < row.routine_standard_scarce_capacity)
2036            }),
2037        },
2038        AggregateAssertion::BaselineRowsPresent {
2039            baselines,
2040            policy_version: expected_version,
2041            ..
2042        } => {
2043            expected_version == policy_version
2044                && baseline_rows
2045                    .iter()
2046                    .map(|row| row.strategy_id.as_str())
2047                    .eq(baselines.iter().map(|baseline| baseline.as_str()))
2048        }
2049        AggregateAssertion::AllAutomaticRolesFullyGated { required_gates, .. } => {
2050            *required_gates
2051                == [
2052                    RequiredGate::PolicyEvaluation,
2053                    RequiredGate::ProtocolConformance,
2054                    RequiredGate::ProviderSupport,
2055                    RequiredGate::SecurityReview,
2056                    RequiredGate::OperatorApproval,
2057                ]
2058                && checked_in_packs_satisfy_automatic_gate_contract()
2059        }
2060    }
2061}
2062
2063fn ratio_metric(
2064    metric: RatioMetric,
2065    expected_route_accuracy: f64,
2066    capability_preservation: f64,
2067    routine_frontier_avoidance: f64,
2068) -> f64 {
2069    match metric {
2070        RatioMetric::ExpectedRouteAccuracy => expected_route_accuracy,
2071        RatioMetric::CapabilityPreservation => capability_preservation,
2072        RatioMetric::RoutineFrontierAvoidance => routine_frontier_avoidance,
2073    }
2074}
2075
2076fn checked_in_packs_satisfy_automatic_gate_contract() -> bool {
2077    // The five PRD-001y live-granted packs (openai, ollama-cloud, openrouter,
2078    // deepseek, qwen) claim live only through all five evidence-backed gates,
2079    // re-qualified on the 2026-08-24 frontier (deepseek after its account was
2080    // topped up and qwen after the token-plan model catalog fix, then all five
2081    // re-qualified on frontier pins). The remaining pack, kimi, was never
2082    // granted a live authority under the five-provider grant and must fail
2083    // closed for automatic live routing.
2084    [
2085        include_bytes!("../../../policy/providers/openai.json").as_slice(),
2086        include_bytes!("../../../policy/providers/ollama-cloud.json").as_slice(),
2087        include_bytes!("../../../policy/providers/openrouter.json").as_slice(),
2088        include_bytes!("../../../policy/providers/deepseek.json").as_slice(),
2089        include_bytes!("../../../policy/providers/qwen.json").as_slice(),
2090    ]
2091    .into_iter()
2092    .all(|bytes| {
2093        PolicyPack::from_json(bytes).is_ok_and(|pack| {
2094            pack.roles.len() == 5
2095                && !pack.fixture_only
2096                && pack.require_automatic_live_eligibility().is_ok()
2097        })
2098    }) && [include_bytes!("../../../policy/providers/kimi.json").as_slice()]
2099        .into_iter()
2100        .all(|bytes| {
2101            PolicyPack::from_json(bytes).is_ok_and(|pack| {
2102                pack.roles.len() == 5 && pack.require_automatic_live_eligibility().is_err()
2103            })
2104        })
2105}
2106
2107fn ratio(numerator: u64, denominator: u64) -> f64 {
2108    if denominator == 0 {
2109        0.0
2110    } else {
2111        numerator as f64 / denominator as f64
2112    }
2113}
2114
2115fn valid_version(value: &str) -> bool {
2116    if value.is_empty()
2117        || value.len() > 64
2118        || contains_sensitive_marker(value)
2119        || !value
2120            .bytes()
2121            .all(|byte| byte.is_ascii_digit() || byte == b'.')
2122    {
2123        return false;
2124    }
2125    let mut parts = value.split('.');
2126    matches!(
2127        (parts.next(), parts.next(), parts.next(), parts.next()),
2128        (Some(a), Some(b), Some(c), None)
2129            if [a, b, c].into_iter().all(|part| !part.is_empty() && part.bytes().all(|byte| byte.is_ascii_digit()))
2130    )
2131}
2132
2133fn safe_reference(value: &str) -> bool {
2134    !value.is_empty()
2135        && value.len() <= 256
2136        && !value.contains('@')
2137        && !contains_sensitive_marker(value)
2138        && !looks_like_sensitive_number(value)
2139        && value.bytes().all(|byte| {
2140            byte.is_ascii_alphanumeric() || matches!(byte, b'-' | b'_' | b'.' | b':' | b'/' | b'@')
2141        })
2142}
2143
2144fn contains_sensitive_marker(value: &str) -> bool {
2145    let lowered = value.to_ascii_lowercase();
2146    let sensitive_word = [
2147        "http://", "https://", "bearer", "token", "secret", "password", "api_key", "apikey",
2148        "cookie",
2149    ]
2150    .iter()
2151    .any(|marker| lowered.contains(marker));
2152    let sensitive_prefix = [
2153        "sk-",
2154        "sk_",
2155        "rk_live_",
2156        "pk_live_",
2157        "ghp_",
2158        "github_pat_",
2159        "xoxb-",
2160        "xoxp-",
2161        "akia",
2162        "eyj",
2163    ]
2164    .iter()
2165    .any(|marker| {
2166        lowered.match_indices(marker).any(|(index, _)| {
2167            index == 0
2168                || lowered
2169                    .as_bytes()
2170                    .get(index - 1)
2171                    .is_some_and(|byte| matches!(byte, b':' | b'/' | b'_' | b'-' | b'.'))
2172        })
2173    });
2174    sensitive_word || sensitive_prefix
2175}
2176
2177fn looks_like_sensitive_number(value: &str) -> bool {
2178    if !value
2179        .bytes()
2180        .all(|byte| byte.is_ascii_digit() || byte == b'-')
2181    {
2182        return false;
2183    }
2184    let digits = value.bytes().filter(u8::is_ascii_digit).count();
2185    matches!(digits, 9 | 15 | 16 | 19)
2186}
2187
2188fn sha256_hex(bytes: &[u8]) -> String {
2189    let digest = Sha256::digest(bytes);
2190    let mut output = String::with_capacity(digest.len() * 2);
2191    for byte in digest {
2192        let _ = write!(&mut output, "{byte:02x}");
2193    }
2194    output
2195}