1use serde::{Deserialize, Serialize};
17use serde_json::Value;
18
19pub const SERVICE_NAME: &str = "agent-evaluate";
20pub const DEFAULT_PORT: u16 = 5150;
21pub const AUDIENCE: &str = "agent-evaluate";
22pub const SERVICE_TOKEN_ENV: &str = "AGENT_EVALUATE_SERVICE_TOKEN";
23pub const MAX_RESOURCE_ID_CHARS: usize = 191;
24
25pub const HEALTH_PATH: &str = "/health";
26pub const RUN_EVAL_PATH: &str = "/internal/v1/evaluate/run";
27pub const LIST_CASES_PATH: &str = "/internal/v1/evaluate/cases";
28pub const GET_CASE_PATH: &str = "/internal/v1/evaluate/cases/get";
29pub const GET_REPORT_PATH: &str = "/internal/v1/evaluate/reports";
30pub const LOAD_CASES_PATH: &str = "/internal/v1/evaluate/cases/load";
31pub const SUITES_PATH: &str = "/internal/v1/evaluate/suites";
32pub const RUNS_PATH: &str = "/internal/v1/evaluate/runs";
33pub const OPERATIONS_PATH: &str = "/internal/v1/evaluate/operations";
34pub const RUN_CASES_PATH: &str = "/internal/v1/evaluate/runs/cases";
35pub const CANCEL_RUN_PATH: &str = "/internal/v1/evaluate/runs/cancel";
36pub const DURABLE_REPORT_PATH: &str = "/internal/v1/evaluate/reports/get";
37pub const COMPARE_REPORTS_PATH: &str = "/internal/v1/evaluate/reports/compare";
38pub const REVIEW_QUEUE_PATH: &str = "/internal/v1/evaluate/reviews";
39pub const CLAIM_REVIEW_PATH: &str = "/internal/v1/evaluate/reviews/claim";
40pub const COMPLETE_REVIEW_PATH: &str = "/internal/v1/evaluate/reviews/complete";
41pub const ENVIRONMENT_PROFILES_PATH: &str = "/internal/v1/evaluate/environment-profiles";
42pub const EVALUATOR_VERSIONS_PATH: &str = "/internal/v1/evaluate/evaluator-versions";
43pub const DRIVER_CLAIM_PATH: &str = "/internal/v1/evaluate/driver/cases/claim";
44pub const DRIVER_RENEW_PATH: &str = "/internal/v1/evaluate/driver/cases/renew";
45pub const DRIVER_SUBMIT_PATH: &str = "/internal/v1/evaluate/driver/cases/submit";
46pub const ADMIN_OVERVIEW_PATH: &str = "/internal/v1/evaluate/admin/overview";
47pub const ADMIN_SUITES_PATH: &str = "/internal/v1/evaluate/admin/suites";
48pub const ADMIN_RUNS_PATH: &str = "/internal/v1/evaluate/admin/runs";
49pub const ADMIN_RUN_CASES_PATH: &str = "/internal/v1/evaluate/admin/runs/cases";
50pub const ADMIN_CANCEL_RUN_PATH: &str = "/internal/v1/evaluate/admin/runs/cancel";
51pub const ADMIN_REPORT_PATH: &str = "/internal/v1/evaluate/admin/reports/get";
52pub const ADMIN_REVIEWS_PATH: &str = "/internal/v1/evaluate/admin/reviews";
53pub const ADMIN_CLAIM_REVIEW_PATH: &str = "/internal/v1/evaluate/admin/reviews/claim";
54pub const ADMIN_COMPLETE_REVIEW_PATH: &str = "/internal/v1/evaluate/admin/reviews/complete";
55
56#[derive(Debug, Clone, Serialize, Deserialize)]
61#[serde(rename_all = "camelCase")]
62pub struct EvalCaseFile {
63 pub schema_version: i32,
64 pub cases: Vec<EvalCase>,
65}
66
67#[derive(Debug, Clone, Serialize, Deserialize)]
68#[serde(rename_all = "camelCase")]
69pub struct EvalCase {
70 pub id: String,
71 pub category: String,
72 #[serde(default)]
73 pub notes: String,
74 pub turns: Vec<EvalTurn>,
75 #[serde(default, skip_serializing_if = "Option::is_none")]
76 pub workspace_files: Option<Value>,
77 #[serde(default, skip_serializing_if = "Option::is_none")]
78 pub oracle: Option<EvalOracle>,
79 #[serde(default, skip_serializing_if = "Option::is_none")]
80 pub verify_commands: Option<Vec<VerifyCommand>>,
81 #[serde(default, skip_serializing_if = "Option::is_none")]
82 pub trace_assertions: Option<Value>,
83 #[serde(default, skip_serializing_if = "Option::is_none")]
84 pub outcome_assertions: Option<Value>,
85 #[serde(default)]
86 pub expects_artifact: bool,
87 #[serde(default, skip_serializing_if = "Option::is_none")]
88 pub generated_workspace_files: Option<Value>,
89 #[serde(default, skip_serializing_if = "Option::is_none")]
90 pub workspace_copies: Option<Value>,
91 #[serde(default, skip_serializing_if = "Option::is_none")]
92 pub workspace_patches: Option<Value>,
93}
94
95#[derive(Debug, Clone, Serialize, Deserialize)]
96#[serde(rename_all = "camelCase")]
97pub struct EvalTurn {
98 pub user: String,
99}
100
101#[derive(Debug, Clone, Serialize, Deserialize)]
102#[serde(rename_all = "camelCase")]
103pub struct EvalOracle {
104 pub kind: String,
105 #[serde(default)]
106 pub expected_values: Vec<Value>,
107 #[serde(default)]
108 pub notes: String,
109}
110
111#[derive(Debug, Clone, Serialize, Deserialize)]
112#[serde(rename_all = "camelCase")]
113pub struct VerifyCommand {
114 pub name: String,
115 pub command: String,
116 #[serde(default)]
117 pub expected_exit_code: i32,
118 #[serde(default, skip_serializing_if = "Option::is_none")]
119 pub output_contains: Option<Vec<String>>,
120}
121
122#[derive(Debug, Clone, Serialize, Deserialize)]
127#[serde(rename_all = "camelCase")]
128pub struct RunEvalRequest {
129 #[serde(default)]
130 pub case_ids: Vec<String>,
131 #[serde(default)]
132 pub categories: Vec<String>,
133 #[serde(default)]
134 pub runtime_types: Vec<String>,
135 #[serde(default)]
136 pub max_turns: usize,
137 #[serde(default)]
138 pub turn_timeout_ms: u64,
139}
140
141#[derive(Debug, Clone, Serialize, Deserialize)]
142#[serde(rename_all = "camelCase")]
143pub struct EvalReport {
144 pub id: String,
145 pub total_cases: usize,
146 pub passed_cases: usize,
147 pub failed_cases: usize,
148 pub infrastructure_failure_cases: usize,
149 #[serde(default)]
153 pub invalid_case_count: usize,
154 pub non_infrastructure_pass_rate: f64,
155 pub case_results: Vec<EvalCaseResult>,
156 pub created_at: String,
157 pub duration_ms: u64,
158 #[serde(default)]
159 pub total_cost_micros: u64,
160 #[serde(default)]
161 pub p95_case_duration_ms: u64,
162}
163
164#[derive(Debug, Clone, Serialize, Deserialize)]
165#[serde(rename_all = "camelCase")]
166pub struct EvalCaseResult {
167 pub case_id: String,
168 pub category: String,
169 pub passed: bool,
170 pub infrastructure_failure: bool,
171 #[serde(default, skip_serializing_if = "Option::is_none")]
174 pub failure_kind: Option<CaseFailureKind>,
175 pub turns_completed: usize,
176 pub total_turns: usize,
177 #[serde(default)]
178 pub oracle_results: Vec<OracleVerdict>,
179 #[serde(default)]
180 pub verify_results: Vec<VerifyCommandResult>,
181 #[serde(default)]
182 pub trace_assertion_results: Vec<AssertionVerdict>,
183 #[serde(default)]
184 pub outcome_assertion_results: Vec<AssertionVerdict>,
185 #[serde(default)]
188 pub evaluator_verdicts: Vec<EvaluatorVerdict>,
189 #[serde(default, skip_serializing_if = "Option::is_none")]
190 pub error: Option<String>,
191 pub duration_ms: u64,
192 #[serde(default)]
193 pub cost_micros: u64,
194 #[serde(default)]
198 pub sample_index: u32,
199 #[serde(default, skip_serializing_if = "Option::is_none")]
200 pub variant_id: Option<String>,
201}
202
203#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
204#[serde(rename_all = "snake_case")]
205pub enum CaseFailureKind {
206 InfraFailed,
207 InvalidCase,
208}
209
210#[derive(Debug, Clone, Serialize, Deserialize)]
211#[serde(rename_all = "camelCase")]
212pub struct OracleVerdict {
213 pub expected: String,
214 pub actual: String,
215 pub matched: bool,
216}
217
218#[derive(Debug, Clone, Serialize, Deserialize)]
219#[serde(rename_all = "camelCase")]
220pub struct VerifyCommandResult {
221 pub name: String,
222 pub exit_code: i32,
223 pub expected_exit_code: i32,
224 pub passed: bool,
225 #[serde(default, skip_serializing_if = "Option::is_none")]
226 pub output: Option<String>,
227}
228
229#[derive(Debug, Clone, Serialize, Deserialize)]
230#[serde(rename_all = "camelCase")]
231pub struct AssertionVerdict {
232 pub rule: String,
233 pub passed: bool,
234 #[serde(default, skip_serializing_if = "Option::is_none")]
235 pub detail: Option<String>,
236}
237
238#[derive(Debug, Clone, Default, Serialize, Deserialize)]
244#[serde(rename_all = "camelCase", deny_unknown_fields)]
245pub struct OutcomeAssertionSpec {
246 #[serde(default)]
247 pub turns: Vec<TurnAssertionSpec>,
248 #[serde(default)]
249 pub workspace_files: Vec<WorkspaceFileAssertionSpec>,
250 #[serde(default)]
251 pub required_changed_paths: Vec<String>,
252 #[serde(default)]
253 pub forbid_workspace_changes: bool,
254}
255
256#[derive(Debug, Clone, Default, Serialize, Deserialize)]
257#[serde(rename_all = "camelCase", deny_unknown_fields)]
258pub struct TurnAssertionSpec {
259 pub index: usize,
260 #[serde(default)]
261 pub contains: Vec<String>,
262 #[serde(default)]
263 pub contains_any: Vec<Vec<String>>,
264}
265
266#[derive(Debug, Clone, Default, Serialize, Deserialize)]
267#[serde(rename_all = "camelCase", deny_unknown_fields)]
268pub struct WorkspaceFileAssertionSpec {
269 pub path: String,
270 #[serde(default)]
271 pub contains: Vec<String>,
272 #[serde(default)]
273 pub contains_any: Vec<Vec<String>>,
274 #[serde(default, skip_serializing_if = "Option::is_none")]
275 pub max_chars: Option<usize>,
276 #[serde(default, skip_serializing_if = "Option::is_none")]
277 pub max_lines: Option<usize>,
278}
279
280#[derive(Debug, Clone, Serialize, Deserialize)]
283#[serde(
284 tag = "kind",
285 rename_all = "snake_case",
286 rename_all_fields = "camelCase",
287 deny_unknown_fields
288)]
289pub enum TypedAssertion {
290 OutputContains {
291 turn: usize,
292 values: Vec<String>,
293 #[serde(default)]
294 any: bool,
295 },
296 WorkspaceFile {
297 path: String,
298 #[serde(default)]
299 contains: Vec<String>,
300 #[serde(default, skip_serializing_if = "Option::is_none")]
301 max_bytes: Option<usize>,
302 },
303 CommandExit {
304 name: String,
305 command_ref: String,
306 expected_exit_code: i32,
307 },
308 TraceEvent {
309 event_type: String,
310 #[serde(default)]
311 minimum_count: usize,
312 },
313 ArtifactExists {
314 artifact_kind: String,
315 },
316 Plugin {
320 plugin_ref: String,
321 #[serde(default)]
322 config: Value,
323 },
324}
325
326#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
329#[serde(rename_all = "camelCase", deny_unknown_fields)]
330pub struct VersionedResourceRef {
331 pub id: String,
332 pub version: String,
333}
334
335#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
338#[serde(rename_all = "camelCase", deny_unknown_fields)]
339pub struct EvaluationSubject {
340 pub agent_id: String,
341 pub runtime_id: String,
342}
343
344#[derive(Debug, Clone, Serialize, Deserialize)]
345#[serde(rename_all = "camelCase", deny_unknown_fields)]
346pub struct RegisterEnvironmentProfileRequest {
347 pub profile_id: String,
348 pub version: String,
349 #[serde(default, skip_serializing_if = "Option::is_none")]
352 pub space_id: Option<String>,
353 #[serde(default)]
354 pub metadata: std::collections::BTreeMap<String, String>,
355}
356
357#[derive(Debug, Clone, Serialize, Deserialize)]
358#[serde(rename_all = "camelCase", deny_unknown_fields)]
359pub struct EnvironmentProfileVersion {
360 pub tenant_id: String,
361 pub project_id: String,
362 pub profile_id: String,
363 pub version: String,
364 pub digest: String,
365 #[serde(default, skip_serializing_if = "Option::is_none")]
366 pub space_id: Option<String>,
367 #[serde(default)]
368 pub metadata: std::collections::BTreeMap<String, String>,
369 pub created_at_ms: i64,
370}
371
372#[derive(Debug, Clone, Serialize, Deserialize)]
373#[serde(tag = "kind", rename_all = "snake_case", deny_unknown_fields)]
374pub enum EvaluatorImplementation {
375 BuiltinAssertions { assertions: Vec<TypedAssertion> },
377}
378
379#[derive(Debug, Clone, Serialize, Deserialize)]
380#[serde(rename_all = "camelCase", deny_unknown_fields)]
381pub struct RegisterEvaluatorVersionRequest {
382 pub evaluator_id: String,
383 pub version: String,
384 pub implementation: EvaluatorImplementation,
385}
386
387#[derive(Debug, Clone, Serialize, Deserialize)]
388#[serde(rename_all = "camelCase", deny_unknown_fields)]
389pub struct EvaluatorVersion {
390 pub tenant_id: String,
391 pub project_id: String,
392 pub evaluator_id: String,
393 pub version: String,
394 pub digest: String,
395 pub implementation: EvaluatorImplementation,
396 pub created_at_ms: i64,
397}
398
399#[derive(Debug, Clone, Serialize, Deserialize)]
400#[serde(rename_all = "camelCase")]
401pub struct EvaluatorVerdict {
402 pub evaluator_id: String,
403 pub evaluator_version: String,
404 pub passed: bool,
405 #[serde(default)]
406 pub assertions: Vec<AssertionVerdict>,
407}
408
409#[derive(Debug, Clone, Serialize, Deserialize)]
410#[serde(rename_all = "camelCase", deny_unknown_fields)]
411pub struct VersionedEvalCase {
412 pub id: String,
413 pub category: String,
414 #[serde(default)]
415 pub turns: Vec<EvalTurn>,
416 #[serde(default)]
420 pub workspace_files: std::collections::BTreeMap<String, String>,
421 #[serde(default)]
422 pub assertions: Vec<TypedAssertion>,
423 #[serde(default, skip_serializing_if = "Option::is_none")]
425 pub environment_profile: Option<VersionedResourceRef>,
426 #[serde(default)]
428 pub evaluator_versions: Vec<VersionedResourceRef>,
429 #[serde(default)]
430 pub verify_commands: Vec<VerifyCommand>,
431 #[serde(default, skip_serializing_if = "Option::is_none")]
432 pub fixture_ref: Option<String>,
433 #[serde(default)]
434 pub budget: EvalBudget,
435}
436
437#[derive(Debug, Clone, Serialize, Deserialize)]
438#[serde(rename_all = "camelCase", deny_unknown_fields)]
439pub struct EvalBudget {
440 pub timeout_ms: u64,
441 pub max_output_bytes: usize,
442 pub max_cost_micros: u64,
443}
444
445impl Default for EvalBudget {
446 fn default() -> Self {
447 Self {
448 timeout_ms: 300_000,
449 max_output_bytes: 8 * 1024 * 1024,
450 max_cost_micros: 0,
451 }
452 }
453}
454
455#[derive(Debug, Clone, Serialize, Deserialize)]
456#[serde(rename_all = "camelCase", deny_unknown_fields)]
457pub struct EvalSuiteVersion {
458 pub tenant_id: String,
459 pub project_id: String,
460 pub suite_id: String,
461 pub version: String,
462 pub manifest_digest: String,
463 pub schema_version: u32,
464 pub cases: Vec<VersionedEvalCase>,
465 pub created_at_ms: i64,
466}
467
468#[derive(Debug, Clone, Serialize, Deserialize)]
469#[serde(rename_all = "camelCase", deny_unknown_fields)]
470pub struct RegisterSuiteRequest {
471 pub suite_id: String,
472 pub version: String,
473 pub manifest_digest: String,
474 pub schema_version: u32,
475 pub cases: Vec<VersionedEvalCase>,
476}
477
478#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
479#[serde(rename_all = "snake_case")]
480pub enum OperationState {
481 Queued,
482 Running,
483 Succeeded,
484 Failed,
485 Canceling,
486 Canceled,
487}
488
489#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
490#[serde(rename_all = "snake_case")]
491pub enum CaseRunState {
492 Queued,
493 Running,
494 Passed,
495 QualityFailed,
496 InfraFailed,
497 InvalidCase,
498 Canceled,
499}
500
501#[derive(Debug, Clone, Serialize, Deserialize)]
502#[serde(rename_all = "camelCase", deny_unknown_fields)]
503pub struct CreateEvalRunRequest {
504 pub suite_id: String,
505 pub suite_version: String,
506 pub subject_version: String,
507 pub executor_profile: String,
508 #[serde(default, skip_serializing_if = "Option::is_none")]
511 pub subject: Option<EvaluationSubject>,
512 #[serde(default)]
513 pub case_ids: Vec<String>,
514 #[serde(default)]
515 pub seed: u64,
516 #[serde(default = "default_concurrency")]
517 pub concurrency: usize,
518 #[serde(default)]
519 pub idempotency_key: String,
520 #[serde(default = "default_samples")]
522 pub samples: u32,
523 #[serde(default)]
526 pub experiment_matrix: Vec<ExperimentVariant>,
527}
528
529fn default_concurrency() -> usize {
530 8
531}
532
533fn default_samples() -> u32 {
534 1
535}
536
537#[derive(Debug, Clone, Serialize, Deserialize)]
538#[serde(rename_all = "camelCase", deny_unknown_fields)]
539pub struct ExperimentVariant {
540 pub id: String,
541 pub subject_version: String,
542 pub executor_profile: String,
543 #[serde(default)]
544 pub parameters: Value,
545}
546
547#[derive(Debug, Clone, Serialize, Deserialize)]
548#[serde(rename_all = "camelCase")]
549pub struct EvalOperation {
550 pub id: String,
551 pub run_id: String,
552 pub state: OperationState,
553 pub phase: String,
554 pub attempt: u32,
555 pub completed_cases: usize,
556 pub total_cases: usize,
557 #[serde(default, skip_serializing_if = "Option::is_none")]
558 pub report_id: Option<String>,
559 pub created_at_ms: i64,
560 pub updated_at_ms: i64,
561 pub version: i64,
562 #[serde(default, skip_serializing_if = "Option::is_none")]
563 pub next_poll_after_ms: Option<u64>,
564 #[serde(default, skip_serializing_if = "Option::is_none")]
565 pub error_code: Option<String>,
566}
567
568#[derive(Debug, Clone, Serialize, Deserialize)]
569#[serde(rename_all = "camelCase", deny_unknown_fields)]
570pub struct GetOperationRequest {
571 pub operation_id: String,
572}
573
574#[derive(Debug, Clone, Serialize, Deserialize)]
575#[serde(rename_all = "camelCase", deny_unknown_fields)]
576pub struct CancelRunRequest {
577 pub run_id: String,
578}
579
580#[derive(Debug, Clone, Serialize, Deserialize)]
581#[serde(rename_all = "camelCase", deny_unknown_fields)]
582pub struct ListCaseRunsRequest {
583 pub run_id: String,
584 #[serde(default, skip_serializing_if = "Option::is_none")]
585 pub cursor: Option<String>,
586 #[serde(default, skip_serializing_if = "Option::is_none")]
587 pub state: Option<CaseRunState>,
588 #[serde(default = "default_case_page")]
589 pub limit: usize,
590}
591
592fn default_case_page() -> usize {
593 50
594}
595
596#[derive(Debug, Clone, Serialize, Deserialize)]
597#[serde(rename_all = "camelCase")]
598pub struct CaseRunSummary {
599 pub id: String,
600 pub run_id: String,
601 pub case_id: String,
602 pub position: usize,
603 pub state: CaseRunState,
604 pub attempt: u32,
605 pub duration_ms: u64,
606 #[serde(default, skip_serializing_if = "Option::is_none")]
607 pub error: Option<String>,
608 #[serde(default)]
609 pub evidence_refs: Vec<EvidenceRef>,
610 #[serde(default)]
611 pub sample_index: u32,
612 #[serde(default, skip_serializing_if = "Option::is_none")]
613 pub variant_id: Option<String>,
614}
615
616#[derive(Debug, Clone, Serialize, Deserialize)]
617#[serde(rename_all = "camelCase")]
618pub struct CaseRunPage {
619 pub items: Vec<CaseRunSummary>,
620 #[serde(default, skip_serializing_if = "Option::is_none")]
621 pub next_cursor: Option<String>,
622}
623
624#[derive(Debug, Clone, Serialize, Deserialize)]
625#[serde(rename_all = "camelCase", deny_unknown_fields)]
626pub struct EvalAccessScope {
627 pub tenant_id: String,
628 pub project_id: String,
629 pub subject: String,
630}
631
632#[derive(Debug, Clone, Serialize, Deserialize)]
633#[serde(rename_all = "camelCase", deny_unknown_fields)]
634pub struct SandboxExecutionRequest {
635 pub scope: EvalAccessScope,
639 pub operation_id: String,
640 pub run_id: String,
641 pub case_run_id: String,
642 pub subject_version: String,
643 pub executor_profile: String,
644 #[serde(default, skip_serializing_if = "Option::is_none")]
645 pub subject: Option<EvaluationSubject>,
646 #[serde(default, skip_serializing_if = "Option::is_none")]
647 pub environment_profile: Option<EnvironmentProfileVersion>,
648 #[serde(default)]
649 pub evaluator_versions: Vec<EvaluatorVersion>,
650 pub seed: u64,
651 pub case: VersionedEvalCase,
652 pub fencing_token: i64,
653 #[serde(default)]
654 pub sample_index: u32,
655 #[serde(default, skip_serializing_if = "Option::is_none")]
656 pub variant: Option<ExperimentVariant>,
657}
658
659#[derive(Debug, Clone, Serialize, Deserialize)]
660#[serde(rename_all = "camelCase", deny_unknown_fields)]
661pub struct PluginEvaluationEvidence {
662 pub plugin_ref: String,
663 pub passed: bool,
664 #[serde(default)]
665 pub detail: String,
666 pub attestation: String,
669}
670
671#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
673#[serde(rename_all = "camelCase", deny_unknown_fields)]
674pub struct EvidenceRef {
675 pub reference: String,
676 pub tenant_id: String,
677 pub project_id: String,
678 pub run_id: String,
679 pub case_run_id: String,
680 pub digest: String,
682 pub size_bytes: u64,
683 pub attestation: String,
685}
686
687#[derive(Debug, Clone, Default, Serialize, Deserialize)]
690#[serde(rename_all = "camelCase", deny_unknown_fields)]
691pub struct SandboxExecutionEvidence {
692 #[serde(default)]
693 pub responses: Vec<String>,
694 #[serde(default)]
695 pub workspace_files: std::collections::BTreeMap<String, String>,
696 #[serde(default)]
697 pub command_exit_codes: std::collections::BTreeMap<String, i32>,
698 #[serde(default)]
699 pub trace_event_counts: std::collections::BTreeMap<String, usize>,
700 #[serde(default)]
701 pub artifact_kinds: Vec<String>,
702 #[serde(default)]
704 pub evidence_refs: Vec<EvidenceRef>,
705 #[serde(default)]
706 pub plugin_evaluations: Vec<PluginEvaluationEvidence>,
707 #[serde(default, skip_serializing_if = "Option::is_none")]
708 pub infrastructure_error: Option<String>,
709 #[serde(default, skip_serializing_if = "Option::is_none")]
713 pub invalid_case_error: Option<String>,
714 #[serde(default)]
715 pub duration_ms: u64,
716 #[serde(default)]
718 pub cost_micros: u64,
719}
720
721#[derive(Debug, Clone, Serialize, Deserialize)]
725#[serde(rename_all = "camelCase", deny_unknown_fields)]
726pub struct ClaimEvaluationCaseRequest {
727 pub evaluate_task_id: String,
728 #[serde(default = "default_driver_lease_ms")]
729 pub lease_ms: i64,
730}
731
732fn default_driver_lease_ms() -> i64 {
733 60_000
734}
735
736#[derive(Debug, Clone, Serialize, Deserialize)]
737#[serde(rename_all = "camelCase", deny_unknown_fields)]
738pub struct EvaluationCaseLease {
739 pub execution: SandboxExecutionRequest,
740 pub lease_until_ms: i64,
741}
742
743#[derive(Debug, Clone, Serialize, Deserialize)]
744#[serde(rename_all = "camelCase", deny_unknown_fields)]
745pub struct ClaimEvaluationCaseResponse {
746 pub operation: EvalOperation,
747 #[serde(default, skip_serializing_if = "Option::is_none")]
748 pub case: Option<EvaluationCaseLease>,
749}
750
751#[derive(Debug, Clone, Serialize, Deserialize)]
752#[serde(rename_all = "camelCase", deny_unknown_fields)]
753pub struct RenewEvaluationCaseRequest {
754 pub evaluate_task_id: String,
755 pub case_run_id: String,
756 pub fencing_token: i64,
757 #[serde(default = "default_driver_lease_ms")]
758 pub lease_ms: i64,
759}
760
761#[derive(Debug, Clone, Serialize, Deserialize)]
762#[serde(rename_all = "camelCase", deny_unknown_fields)]
763pub struct RenewEvaluationCaseResponse {
764 pub renewed: bool,
765 pub lease_until_ms: i64,
766}
767
768#[derive(Debug, Clone, Serialize, Deserialize)]
769#[serde(rename_all = "camelCase", deny_unknown_fields)]
770pub struct SubmitEvaluationEvidenceRequest {
771 pub evaluate_task_id: String,
772 pub case_run_id: String,
773 pub fencing_token: i64,
774 pub evidence: SandboxExecutionEvidence,
775}
776
777#[derive(Debug, Clone, Serialize, Deserialize)]
778#[serde(rename_all = "camelCase", deny_unknown_fields)]
779pub struct SubmitEvaluationEvidenceResponse {
780 pub accepted: bool,
781 pub operation: EvalOperation,
782}
783
784#[derive(Debug, Clone, Serialize, Deserialize)]
785#[serde(rename_all = "camelCase")]
786pub struct ListCasesRequest {
787 #[serde(default, skip_serializing_if = "Option::is_none")]
788 pub category: Option<String>,
789}
790
791#[derive(Debug, Clone, Serialize, Deserialize)]
792#[serde(rename_all = "camelCase")]
793pub struct ListCasesResponse {
794 pub cases: Vec<EvalCaseSummary>,
795}
796
797#[derive(Debug, Clone, Serialize, Deserialize)]
798#[serde(rename_all = "camelCase")]
799pub struct EvalCaseSummary {
800 pub id: String,
801 pub category: String,
802 pub notes: String,
803 pub turns: usize,
804 #[serde(default)]
805 pub has_oracle: bool,
806 #[serde(default)]
807 pub has_verify_commands: bool,
808 #[serde(default)]
809 pub expects_artifact: bool,
810}
811
812#[derive(Debug, Clone, Serialize, Deserialize)]
813#[serde(rename_all = "camelCase")]
814pub struct GetCaseRequest {
815 pub case_id: String,
816}
817
818#[derive(Debug, Clone, Serialize, Deserialize)]
819#[serde(rename_all = "camelCase")]
820pub struct LoadCasesRequest {
821 pub file_path: String,
822}
823
824#[derive(Debug, Clone, Serialize, Deserialize)]
825#[serde(rename_all = "camelCase")]
826pub struct GetReportRequest {
827 pub report_id: String,
828}
829
830#[derive(Debug, Clone, Serialize, Deserialize)]
831#[serde(rename_all = "camelCase", deny_unknown_fields)]
832pub struct CompareReportsRequest {
833 pub report_id: String,
834 pub baseline_report_id: String,
835 #[serde(default)]
836 pub minimum_pass_rate: f64,
837 #[serde(default = "default_max_infra_rate")]
838 pub maximum_infra_failure_rate: f64,
839 #[serde(default = "default_significance")]
840 pub significance_level: f64,
841 #[serde(default)]
842 pub minimum_effect: f64,
843}
844
845fn default_max_infra_rate() -> f64 {
846 1.0
847}
848
849fn default_significance() -> f64 {
850 0.05
851}
852
853#[derive(Debug, Clone, Serialize, Deserialize)]
854#[serde(rename_all = "camelCase")]
855pub struct CaseOutcomeDiff {
856 pub case_id: String,
857 #[serde(default, skip_serializing_if = "Option::is_none")]
858 pub variant_id: Option<String>,
859 pub baseline_passed: bool,
860 pub current_passed: bool,
861 pub flaky_signal: bool,
862 #[serde(default)]
863 pub historical_flake_rate: f64,
864 #[serde(default)]
865 pub historical_samples: u64,
866}
867
868#[derive(Debug, Clone, Serialize, Deserialize)]
869#[serde(rename_all = "camelCase")]
870pub struct ReportComparison {
871 pub report_id: String,
872 pub baseline_report_id: String,
873 pub pass_rate_delta: f64,
874 pub infra_failure_rate_delta: f64,
875 pub pass_rate_confidence_low: f64,
876 pub pass_rate_confidence_high: f64,
877 pub effect_confidence_low: f64,
878 pub effect_confidence_high: f64,
879 pub p_value: f64,
880 pub statistically_significant: bool,
881 pub changed_cases: Vec<CaseOutcomeDiff>,
882 pub gate_passed: bool,
883}
884
885#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
886#[serde(rename_all = "snake_case")]
887pub enum ReviewState {
888 Queued,
889 Claimed,
890 Approved,
891 Rejected,
892}
893
894#[derive(Debug, Clone, Serialize, Deserialize)]
895#[serde(rename_all = "camelCase")]
896pub struct HumanReview {
897 pub id: String,
898 pub run_id: String,
899 pub case_run_id: String,
900 pub state: ReviewState,
901 pub reason: String,
902 #[serde(default, skip_serializing_if = "Option::is_none")]
903 pub reviewer: Option<String>,
904 #[serde(default, skip_serializing_if = "Option::is_none")]
905 pub decision_note: Option<String>,
906 pub fencing_token: i64,
907 pub created_at_ms: i64,
908 pub updated_at_ms: i64,
909}
910
911#[derive(Debug, Clone, Serialize, Deserialize)]
912#[serde(rename_all = "camelCase", deny_unknown_fields)]
913pub struct EnqueueReviewRequest {
914 pub run_id: String,
915 pub case_run_id: String,
916 pub reason: String,
917}
918
919#[derive(Debug, Clone, Serialize, Deserialize)]
920#[serde(rename_all = "camelCase", deny_unknown_fields)]
921pub struct ClaimReviewRequest {
922 #[serde(default = "default_review_lease")]
923 pub lease_ms: i64,
924}
925
926fn default_review_lease() -> i64 {
927 300_000
928}
929
930#[derive(Debug, Clone, Serialize, Deserialize)]
931#[serde(rename_all = "camelCase", deny_unknown_fields)]
932pub struct CompleteReviewRequest {
933 pub review_id: String,
934 pub fencing_token: i64,
935 pub approved: bool,
936 #[serde(default)]
937 pub note: String,
938}
939
940pub const DEFAULT_ADMIN_PAGE_SIZE: usize = 50;
945pub const MAX_ADMIN_PAGE_SIZE: usize = 200;
946
947fn default_admin_page() -> usize {
948 DEFAULT_ADMIN_PAGE_SIZE
949}
950
951#[derive(Debug, Clone, Serialize, Deserialize)]
952#[serde(rename_all = "camelCase", deny_unknown_fields)]
953pub struct ListAdminSuitesRequest {
954 #[serde(default, skip_serializing_if = "Option::is_none")]
955 pub cursor: Option<String>,
956 #[serde(default = "default_admin_page")]
957 pub limit: usize,
958}
959
960impl Default for ListAdminSuitesRequest {
961 fn default() -> Self {
962 Self {
963 cursor: None,
964 limit: DEFAULT_ADMIN_PAGE_SIZE,
965 }
966 }
967}
968
969#[derive(Debug, Clone, Serialize, Deserialize)]
970#[serde(rename_all = "camelCase", deny_unknown_fields)]
971pub struct ListAdminRunsRequest {
972 #[serde(default, skip_serializing_if = "Option::is_none")]
973 pub cursor: Option<String>,
974 #[serde(default, skip_serializing_if = "Option::is_none")]
975 pub state: Option<OperationState>,
976 #[serde(default = "default_admin_page")]
977 pub limit: usize,
978}
979
980impl Default for ListAdminRunsRequest {
981 fn default() -> Self {
982 Self {
983 cursor: None,
984 state: None,
985 limit: DEFAULT_ADMIN_PAGE_SIZE,
986 }
987 }
988}
989
990#[derive(Debug, Clone, Serialize, Deserialize)]
991#[serde(rename_all = "camelCase", deny_unknown_fields)]
992pub struct ListAdminReviewsRequest {
993 #[serde(default, skip_serializing_if = "Option::is_none")]
994 pub cursor: Option<String>,
995 #[serde(default, skip_serializing_if = "Option::is_none")]
996 pub state: Option<ReviewState>,
997 #[serde(default = "default_admin_page")]
998 pub limit: usize,
999}
1000
1001impl Default for ListAdminReviewsRequest {
1002 fn default() -> Self {
1003 Self {
1004 cursor: None,
1005 state: None,
1006 limit: DEFAULT_ADMIN_PAGE_SIZE,
1007 }
1008 }
1009}
1010
1011#[derive(Debug, Clone, Serialize, Deserialize)]
1012#[serde(rename_all = "camelCase")]
1013pub struct EvaluateAdminOverview {
1014 pub suite_versions: u64,
1015 pub run_states: std::collections::BTreeMap<String, u64>,
1016 pub case_states: std::collections::BTreeMap<String, u64>,
1017 pub review_states: std::collections::BTreeMap<String, u64>,
1018}
1019
1020#[derive(Debug, Clone, Serialize, Deserialize)]
1021#[serde(rename_all = "camelCase")]
1022pub struct AdminSuiteSummary {
1023 pub suite_id: String,
1024 pub version: String,
1025 pub manifest_digest: String,
1026 pub schema_version: u32,
1027 pub case_count: usize,
1028 pub created_at_ms: i64,
1029}
1030
1031#[derive(Debug, Clone, Serialize, Deserialize)]
1032#[serde(rename_all = "camelCase")]
1033pub struct AdminSuitePage {
1034 pub items: Vec<AdminSuiteSummary>,
1035 #[serde(default, skip_serializing_if = "Option::is_none")]
1036 pub next_cursor: Option<String>,
1037}
1038
1039#[derive(Debug, Clone, Serialize, Deserialize)]
1040#[serde(rename_all = "camelCase")]
1041pub struct AdminRunSummary {
1042 pub run_id: String,
1043 pub operation_id: String,
1044 pub suite_id: String,
1045 pub suite_version: String,
1046 pub subject_version: String,
1047 pub executor_profile: String,
1048 #[serde(default, skip_serializing_if = "Option::is_none")]
1049 pub subject: Option<EvaluationSubject>,
1050 pub state: OperationState,
1051 pub phase: String,
1052 pub completed_cases: usize,
1053 pub total_cases: usize,
1054 #[serde(default, skip_serializing_if = "Option::is_none")]
1055 pub report_id: Option<String>,
1056 pub created_at_ms: i64,
1057 pub updated_at_ms: i64,
1058 pub version: i64,
1059}
1060
1061#[derive(Debug, Clone, Serialize, Deserialize)]
1062#[serde(rename_all = "camelCase")]
1063pub struct AdminRunPage {
1064 pub items: Vec<AdminRunSummary>,
1065 #[serde(default, skip_serializing_if = "Option::is_none")]
1066 pub next_cursor: Option<String>,
1067}
1068
1069#[derive(Debug, Clone, Serialize, Deserialize)]
1070#[serde(rename_all = "camelCase")]
1071pub struct AdminReviewPage {
1072 pub items: Vec<HumanReview>,
1073 #[serde(default, skip_serializing_if = "Option::is_none")]
1074 pub next_cursor: Option<String>,
1075}
1076
1077#[derive(Debug, Clone, Serialize, Deserialize)]
1079pub struct ApiError {
1080 pub status: u16,
1081 pub message: String,
1082}
1083
1084#[cfg(test)]
1085mod tests {
1086 use super::{
1087 DEFAULT_ADMIN_PAGE_SIZE, ListAdminReviewsRequest, ListAdminRunsRequest,
1088 ListAdminSuitesRequest, TypedAssertion,
1089 };
1090 use serde_json::json;
1091
1092 #[test]
1093 fn typed_assertion_uses_snake_case_tags_and_camel_case_fields() {
1094 let assertion = TypedAssertion::TraceEvent {
1095 event_type: "task.completed".into(),
1096 minimum_count: 1,
1097 };
1098 let value = serde_json::to_value(&assertion).unwrap();
1099 assert_eq!(
1100 value,
1101 json!({
1102 "kind": "trace_event",
1103 "eventType": "task.completed",
1104 "minimumCount": 1
1105 })
1106 );
1107 assert!(serde_json::from_value::<TypedAssertion>(value).is_ok());
1108 }
1109
1110 #[test]
1111 fn admin_queries_use_the_same_bounded_default_in_rust_and_serde() {
1112 assert_eq!(
1113 ListAdminSuitesRequest::default().limit,
1114 DEFAULT_ADMIN_PAGE_SIZE
1115 );
1116 assert_eq!(
1117 ListAdminRunsRequest::default().limit,
1118 DEFAULT_ADMIN_PAGE_SIZE
1119 );
1120 assert_eq!(
1121 ListAdminReviewsRequest::default().limit,
1122 DEFAULT_ADMIN_PAGE_SIZE
1123 );
1124 let decoded: ListAdminRunsRequest = serde_json::from_value(json!({})).unwrap();
1125 assert_eq!(decoded.limit, DEFAULT_ADMIN_PAGE_SIZE);
1126 }
1127}