1use std::collections::BTreeSet;
9
10use serde::{Deserialize, Serialize};
11
12pub const CONTRACT_VERSION: u32 = 1;
13pub const EVIDENCE_ARCHIVE_SCHEMA_VERSION: u32 = 3;
14pub const EVIDENCE_ARCHIVE_MAGIC: &str = "SUPERCOV-EVIDENCE-3\n";
15pub const COVERAGE_MODEL_SCHEMA_VERSION: u32 = 1;
16pub const COVERAGE_MODEL_MAX_IDENTIFIER_BYTES: usize = 64;
17pub const COVERAGE_MODEL_MAX_DESCRIPTION_BYTES: usize = 4_096;
18pub const COVERAGE_MODEL_MAX_SURFACES_PER_LIST: usize = 256;
19pub const AGENT_JSON_SCHEMA_VERSION: u32 = 1;
20pub const AGENT_JSON_MAX_BYTES: usize = 65_536;
21pub const DEFAULT_PAGE_SIZE: usize = 20;
22pub const PROCESS_SUPERVISION_SCHEMA_VERSION: u32 = 1;
23pub const DEFAULT_DIAGNOSTIC_INTERVAL_MS: u64 = 60_000;
24pub const COMMAND_TIMEOUT_EXIT_CODE: i32 = 124;
25pub const COMMAND_TERMINATION_GRACE_MS: u64 = 5_000;
26pub const PROBE_V2_VERSION: u32 = 2;
27pub const PROBE_V2_RADIX: u32 = 3;
28pub const PROBE_V2_JS_MAX_CONDITIONS: usize = 32;
29pub const LANGUAGE_FRONTEND_PROTOCOL_VERSION: u32 = 2;
30pub const RUST_PROBE_TRANSPORT_PROTOCOL_VERSION: u32 = 1;
31pub const RUST_PROBE_TRANSPORT_MAGIC: &str = "SCVRUST1";
32pub const RUST_PROBE_TRANSPORT_V3_PROTOCOL_VERSION: u32 = 3;
33pub const RUST_PROBE_TRANSPORT_V3_MAGIC: &str = "SCVRUST3";
34pub const RUST_PROBE_TRANSPORT_HEADER_SIZE: usize = 128;
35pub const RUST_PROBE_TRANSPORT_DESCRIPTOR_SIZE: usize = 40;
36pub const RUST_PROBE_TRANSPORT_TOKEN_SIZE: usize = 16;
37pub const RUST_LIBTEST_EVENT_PROTOCOL_VERSION: u32 = 1;
38pub const RUST_LIBTEST_EVENT_MAGIC: &str = "SCVLTST1";
39pub const RUST_LIBTEST_EVENT_HEADER_SIZE: usize = 64;
40pub const RUST_LIBTEST_EVENT_RECORD_HEADER_SIZE: usize = 48;
41pub const RUST_LIBTEST_EVENT_TOKEN_SIZE: usize = 16;
42pub const RUST_LIBTEST_EVENT_MAX_NAME_BYTES: usize = 1_048_576;
43pub const RUST_LIBTEST_COMPANION_BUNDLE_SCHEMA_VERSION: u32 = 3;
44
45pub const ERROR_CODES: &[&str] = &[
46 "AMBIGUOUS_SELECTOR",
47 "DECISION_NOT_FOUND",
48 "FILTER_UNAVAILABLE",
49 "INTERNAL_ERROR",
50 "INVALID_ARGUMENT",
51 "MINIMIZATION_COMPLEXITY_LIMIT",
52 "NO_RUNS",
53 "RESPONSE_TOO_LARGE",
54 "RUN_NOT_FOUND",
55 "SCOPE_UNAVAILABLE",
56 "SOURCE_NOT_FOUND",
57 "TARGET_UNREACHABLE",
58 "TEST_FILTER_EMPTY",
59 "TEST_NOT_FOUND",
60 "UNATTRIBUTED_EVIDENCE",
61 "UNKNOWN_COMMAND",
62];
63
64#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
65#[serde(rename_all = "camelCase")]
66pub struct ContractRegistry {
67 pub contract_version: u32,
68 pub status: String,
69 pub resident_process: bool,
70 pub evidence_archive: EvidenceArchiveContract,
71 pub run_store: RunStoreContract,
72 pub agent_json: AgentJsonContract,
73 pub process_supervision: ProcessSupervisionContract,
74}
75
76#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
77#[serde(rename_all = "camelCase")]
78pub struct EvidenceArchiveContract {
79 pub schema_version: u32,
80 pub file: String,
81 pub format: String,
82 pub magic: String,
83}
84
85#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
86#[serde(rename_all = "camelCase")]
87pub struct RunStoreContract {
88 pub schema_version: u32,
89 pub store: String,
90 pub workspace_store: String,
91 pub published_run_files: Vec<String>,
92}
93
94#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
95#[serde(rename_all = "camelCase")]
96pub struct AgentJsonContract {
97 pub schema_version: u32,
98 pub max_bytes: usize,
99 pub default_page_size: usize,
100 pub error_codes: Vec<String>,
101}
102
103#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
104#[serde(rename_all = "camelCase")]
105pub struct ProcessSupervisionContract {
106 pub schema_version: u32,
107 pub diagnostic_interval_ms: u64,
108 pub timeout_exit_code: i32,
109 pub termination_grace_ms: u64,
110}
111
112#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
113#[serde(rename_all = "camelCase")]
114pub struct AgentPagination {
115 pub offset: usize,
116 pub limit: usize,
117 pub returned: usize,
118 pub total: usize,
119 pub has_more: bool,
120 pub next_offset: Option<usize>,
121}
122
123pub fn registry() -> Result<ContractRegistry, serde_json::Error> {
124 serde_json::from_str(include_str!("../assets/v1/contract.json"))
125}
126
127#[derive(Debug, Clone, PartialEq, Deserialize)]
128#[serde(rename_all = "camelCase")]
129pub struct ProbeV2Contract {
130 pub probe_version: u32,
131 pub semantics: String,
132 pub implementation: String,
133 pub decision_encoding: ProbeV2DecisionEncoding,
134 pub published_evidence: String,
135 pub attribution_epoch: Vec<String>,
136 pub fallback: String,
137 pub promotion: ProbeV2Promotion,
138}
139
140#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
141#[serde(rename_all = "camelCase")]
142pub struct ProbeV2DecisionEncoding {
143 pub radix: u32,
144 pub digits: ProbeV2Digits,
145 pub outcome_stored_separately: bool,
146 pub javascript_maximum_encoded_conditions: usize,
147 pub wider_decision_behavior: String,
148}
149
150#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
151pub struct ProbeV2Digits {
152 pub unreached: u32,
153 pub r#false: u32,
154 pub r#true: u32,
155}
156
157#[derive(Debug, Clone, PartialEq, Deserialize)]
158#[serde(rename_all = "camelCase")]
159pub struct ProbeV2Promotion {
160 pub realistic_median_runtime_ratio_max: f64,
161 pub semantic_equivalence_required: bool,
162 pub manifest_parity_required: bool,
163 pub evidence_parity_required: bool,
164}
165
166pub fn probe_v2_contract() -> Result<ProbeV2Contract, serde_json::Error> {
167 serde_json::from_str(include_str!("../assets/probe-v2/contract.json"))
168}
169
170#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
171#[serde(rename_all = "camelCase", deny_unknown_fields)]
172pub struct LanguageFrontendProtocolContract {
173 pub frontend_protocol_version: u32,
174 pub status: String,
175 pub manifest_model: String,
176 pub observation_model: String,
177 pub probe_model: String,
178 pub identity_axes: Vec<String>,
179 pub transition_kinds: Vec<String>,
180 pub structural_sources: Vec<String>,
181 pub execution_models: Vec<String>,
182 pub attribution_precisions: Vec<String>,
183 pub limitation_scopes: Vec<String>,
184 pub requirements: LanguageFrontendRequirements,
185}
186
187#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
188#[serde(rename_all = "camelCase", deny_unknown_fields)]
189pub struct LanguageFrontendRequirements {
190 pub complete_manifest_before_execution: bool,
191 pub unknown_obligation_fatal: bool,
192 pub identity_downgrade_requires_limitation: bool,
193 pub unknown_phase_reference_fatal: bool,
194 pub phase_causality_acyclic: bool,
195 pub selected_unstarted_has_test_identity_only: bool,
196 pub multiple_runners_per_frontend: bool,
197 pub structural_limitations_reference_manifest_ids: bool,
198 pub attribution_limitations_runner_scoped: bool,
199 pub timestamp_attribution_may_claim_causality: bool,
200 pub frontend_may_compute_coverage_verdicts: bool,
201 pub engine_owns_manifest_merge: bool,
202 pub engine_owns_evidence_validation: bool,
203 pub engine_owns_attribution_merge: bool,
204 pub engine_owns_coverage_analysis: bool,
205 pub engine_owns_persistence_and_queries: bool,
206}
207
208pub fn language_frontend_protocol_contract()
209-> Result<LanguageFrontendProtocolContract, serde_json::Error> {
210 serde_json::from_str(include_str!("../assets/frontend-v2/contract.json"))
211}
212
213#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
214#[serde(rename_all = "camelCase", deny_unknown_fields)]
215pub struct PythonCoverageImportContract {
216 pub schema_version: u32,
217 pub status: String,
218 pub producer: String,
219 pub supported_collector_cores_for_exact_contexts: Vec<String>,
220 pub requires_branch_measurement: bool,
221 pub database_access: String,
222 pub frontend_computes_verdicts: bool,
223 pub unknown_fields_fatal: bool,
224 pub preserve_unrecognized_contexts_as_background: bool,
225 pub mcdc_availability: String,
226 pub column_locations: String,
227}
228
229pub fn python_coverage_import_contract() -> Result<PythonCoverageImportContract, serde_json::Error>
230{
231 serde_json::from_str(include_str!("../assets/python-coverage-v1/contract.json"))
232}
233
234#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
235#[serde(rename_all = "camelCase", deny_unknown_fields)]
236pub struct EvidenceV3Contract {
237 pub schema_version: u32,
238 pub status: String,
239 pub magic: String,
240 pub framing: String,
241 pub required_entries: Vec<String>,
242 pub frontend_protocol_version: u32,
243 pub coverage_model_schema_version: u32,
244 pub unknown_frontend_fields_fatal: bool,
245 pub unknown_coverage_model_fields_fatal: bool,
246 pub frontend_language_must_match_coverage_model: bool,
247 pub malformed_recognized_jsonl_fatal: bool,
248 pub recognized_jsonl_requires_final_newline: bool,
249}
250
251pub fn evidence_v3_contract() -> Result<EvidenceV3Contract, serde_json::Error> {
252 serde_json::from_str(include_str!("../assets/evidence-v3/contract.json"))
253}
254
255#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
256#[serde(rename_all = "camelCase", deny_unknown_fields)]
257pub struct CoverageModelV1Contract {
258 pub schema_version: u32,
259 pub status: String,
260 pub persisted_entry: String,
261 pub required_fields: Vec<String>,
262 pub unknown_fields_fatal: bool,
263 pub frontend_language_must_match: bool,
264 pub measured_must_be_nonempty: bool,
265 pub surface_lists_must_be_unique: bool,
266 pub surface_lists_must_be_disjoint: bool,
267 pub strings_must_be_trimmed_single_line: bool,
268 pub max_identifier_bytes: usize,
269 pub max_description_bytes: usize,
270 pub max_surfaces_per_list: usize,
271}
272
273pub fn coverage_model_v1_contract() -> Result<CoverageModelV1Contract, serde_json::Error> {
274 serde_json::from_str(include_str!("../assets/coverage-model-v1/contract.json"))
275}
276
277#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
278#[serde(rename_all = "camelCase", deny_unknown_fields)]
279pub struct RustCoverageV1Contract {
280 pub model_version: u32,
281 pub status: String,
282 pub language: String,
283 pub variant: String,
284 pub decision_semantics: String,
285 pub condition_order: String,
286 pub probe_model: String,
287 pub generic_aggregation: String,
288 pub source_identity: RustSourceIdentityContract,
289 pub test_context_identity: RustTestContextIdentityContract,
290 pub runner_attempt_identity: RustRunnerAttemptIdentityContract,
291 pub libtest_event_transport: RustLibtestEventTransportContract,
292 pub point_kinds: Vec<String>,
293 pub control_decision_kinds: Vec<String>,
294 pub branch_kinds: Vec<String>,
295 pub required_owned_surfaces: Vec<String>,
296 pub required_identity_axes: Vec<String>,
297 pub completeness_requires: Vec<String>,
298 pub external_coverage_in_product: bool,
299}
300
301#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
302#[serde(rename_all = "camelCase", deny_unknown_fields)]
303pub struct RustLibtestEventTransportContract {
304 pub protocol_version: u32,
305 pub status: String,
306 pub magic: String,
307 pub byte_order: String,
308 pub header_size: usize,
309 pub record_header_size: usize,
310 pub token_size: usize,
311 pub maximum_name_bytes: usize,
312 pub writer: String,
313 pub event_kinds: Vec<String>,
314 pub terminal_results: Vec<String>,
315 pub publication: String,
316 pub sequence: String,
317 pub integrity: String,
318 pub unknown_event: String,
319 pub truncated_record: String,
320 pub invalid_semantics: String,
321 pub missing_transport: String,
322 pub process_model: String,
323 pub output_authority: String,
324 pub artifact_binding: RustLibtestArtifactBindingContract,
325}
326
327#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
328#[serde(rename_all = "camelCase", deny_unknown_fields)]
329pub struct RustLibtestArtifactBindingContract {
330 pub schema_version: u32,
331 pub manifest_suffix: String,
332 pub digest: String,
333 pub required_bindings: Vec<String>,
334 pub artifact_location: String,
335 pub unknown_fields_fatal: bool,
336 pub mismatch: String,
337}
338
339#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
340#[serde(rename_all = "camelCase", deny_unknown_fields)]
341pub struct RustRunnerAttemptIdentityContract {
342 pub version: u32,
343 pub cargo_test: RustSingleAttemptRunnerContract,
344 pub rustdoc: RustSingleAttemptRunnerContract,
345 pub nextest: RustNextestAttemptContract,
346}
347
348#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
349#[serde(rename_all = "camelCase", deny_unknown_fields)]
350pub struct RustSingleAttemptRunnerContract {
351 pub retry: usize,
352 pub total_attempts: usize,
353 pub identity_authority: String,
354}
355
356#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
357#[serde(rename_all = "camelCase", deny_unknown_fields)]
358pub struct RustNextestAttemptContract {
359 pub minimum_version: String,
360 pub maximum_version: String,
361 pub verified_released_versions: Vec<String>,
362 pub execution_mode: String,
363 pub identity_environment: Vec<String>,
364 pub attempt_numbering: String,
365 pub retry_derivation: String,
366 pub attempt_id_semantics: String,
367 pub list_phase: String,
368 pub selection_projection: String,
369 pub selected_but_unstarted: String,
370 pub concurrent_attempts: String,
371 pub target_runner_death: String,
372 pub partial_identity: String,
373 pub stress_iteration: String,
374}
375
376#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
377#[serde(rename_all = "camelCase", deny_unknown_fields)]
378pub struct RustTestContextIdentityContract {
379 pub version: u32,
380 pub algorithm: String,
381 pub domain: String,
382 pub input: String,
383 pub reserved_values: Vec<String>,
384 pub reserved_remap_xor: String,
385 pub collision_policy: String,
386}
387
388#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
389#[serde(rename_all = "camelCase", deny_unknown_fields)]
390pub struct RustSourceIdentityContract {
391 pub version: u32,
392 pub digest: String,
393 pub id_digest_bytes: usize,
394 pub separator: String,
395 pub authored_canonical_fields: Vec<String>,
396 pub synthetic_expansion_canonical_fields: Vec<String>,
397 pub generated_source_key_fields: Vec<String>,
398 pub repeated_authored_expansions_aggregate: bool,
399 pub distinct_synthetic_invocations_remain_distinct: bool,
400 pub ephemeral_paths_forbidden: bool,
401 pub collision_policy: String,
402}
403
404pub fn rust_coverage_v1_contract() -> Result<RustCoverageV1Contract, serde_json::Error> {
405 serde_json::from_str(include_str!("../assets/rust-coverage-v1/contract.json"))
406}
407
408#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
409#[serde(rename_all = "camelCase", deny_unknown_fields)]
410pub struct RustProbeTransportContract {
411 pub protocol_version: u32,
412 pub status: String,
413 pub magic: String,
414 pub byte_order: String,
415 pub header_size: usize,
416 pub descriptor_size: usize,
417 pub token_size: usize,
418 pub endian_marker: u32,
419 pub header_offsets: RustProbeTransportHeaderOffsets,
420 pub descriptor_offsets: RustProbeTransportDescriptorOffsets,
421 pub record_kinds: RustProbeTransportRecordKinds,
422 #[serde(default)]
423 pub thread_scope: Option<RustProbeTransportThreadScope>,
424 pub context: RustProbeTransportContext,
425 pub publication: RustProbeTransportPublication,
426 pub integrity: RustProbeTransportIntegrity,
427 pub completeness: RustProbeTransportCompleteness,
428 pub supported_targets: Vec<String>,
429 pub unsupported_target: String,
430}
431
432#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
436#[serde(rename_all = "camelCase", deny_unknown_fields)]
437pub struct RustProbeTransportThreadScope {
438 pub child_derivation: String,
439 pub domain: String,
440 pub thread_end_committed_when_start_routine_returns: bool,
441 pub test_boundary_committed_when_test_context_exits: bool,
442 pub acceptance: String,
443 pub escaped_thread_records_become_background: bool,
444 pub escaped_thread_limitation: String,
445 pub duplicate_thread_end_fatal: bool,
446 pub duplicate_test_boundary_fatal: bool,
447}
448
449#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
450#[serde(rename_all = "camelCase", deny_unknown_fields)]
451pub struct RustProbeTransportHeaderOffsets {
452 pub version: usize,
453 pub header_size: usize,
454 pub descriptor_size: usize,
455 pub descriptor_capacity: usize,
456 pub payload_capacity: usize,
457 pub endian_marker: usize,
458 pub next_descriptor: usize,
459 pub next_payload: usize,
460 pub dropped: usize,
461 pub token: usize,
462 pub attachments: usize,
463 #[serde(default)]
464 pub next_phase: Option<usize>,
465}
466
467#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
468#[serde(rename_all = "camelCase", deny_unknown_fields)]
469pub struct RustProbeTransportDescriptorOffsets {
470 pub commit: usize,
471 pub kind: usize,
472 pub outcome: usize,
473 pub flags: usize,
474 pub process_id: usize,
475 pub context_id: usize,
476 pub payload_offset: usize,
477 pub payload_length: usize,
478 pub id_length: usize,
479 pub value_length: usize,
480 pub checksum: usize,
481}
482
483#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
484#[serde(rename_all = "camelCase", deny_unknown_fields)]
485pub struct RustProbeTransportRecordKinds {
486 pub hit: u8,
487 pub decision: u8,
488 pub ordinal_hit: u8,
489 #[serde(default)]
490 pub phase: Option<u8>,
491 #[serde(default)]
492 pub thread_phase: Option<u8>,
493 #[serde(default)]
494 pub thread_end: Option<u8>,
495 #[serde(default)]
496 pub test_boundary: Option<u8>,
497}
498
499#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
500#[serde(rename_all = "camelCase", deny_unknown_fields)]
501pub struct RustProbeTransportContext {
502 pub zero: String,
503 pub max: String,
504 pub nonzero: String,
505 pub published_identity: Vec<String>,
506}
507
508#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
509#[serde(rename_all = "camelCase", deny_unknown_fields)]
510pub struct RustProbeTransportPublication {
511 pub reservation_order: Vec<String>,
512 pub commit_value: u8,
513 pub writer_ordering: String,
514 pub reader_ordering: String,
515 pub complete_descriptors_independently_recoverable: bool,
516}
517
518#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
519#[serde(rename_all = "camelCase", deny_unknown_fields)]
520pub struct RustProbeTransportIntegrity {
521 pub authentication: String,
522 pub checksum: String,
523 pub unknown_record_kind_fatal: bool,
524 pub nonzero_reserved_byte_fatal: bool,
525 pub symlink_transport_fatal: bool,
526}
527
528#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
529#[serde(rename_all = "camelCase", deny_unknown_fields)]
530pub struct RustProbeTransportCompleteness {
531 pub zero_attachments_blocks_terminal_passing_attempt: bool,
532 pub dropped_records_block_terminal_passing_attempt: bool,
533 pub incomplete_records_block_terminal_passing_attempt: bool,
534 pub context_zero_excluded_from_passed_per_test_coverage: bool,
535 pub malformed_record_fatal: bool,
536}
537
538pub fn rust_probe_transport_contract() -> Result<RustProbeTransportContract, serde_json::Error> {
539 serde_json::from_str(include_str!(
540 "../assets/rust-probe-transport-v1/contract.json"
541 ))
542}
543
544pub fn rust_probe_transport_v3_contract() -> Result<RustProbeTransportContract, serde_json::Error> {
545 serde_json::from_str(include_str!(
546 "../assets/rust-probe-transport-v3/contract.json"
547 ))
548}
549
550#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
551#[serde(rename_all = "kebab-case")]
552pub enum StructuralSource {
553 OwnedProbes,
554 NativeImport,
555 Mixed,
556}
557
558#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
559#[serde(rename_all = "kebab-case")]
560pub enum ExecutionModel {
561 ProcessPerTest,
562 SerialInProcess,
563 ParallelContextPropagated,
564 ParallelUnattributed,
565}
566
567#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
568#[serde(rename_all = "kebab-case")]
569pub enum AttributionPrecision {
570 Exact,
571 Aggregate,
572 Unavailable,
573}
574
575#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
576#[serde(rename_all = "kebab-case")]
577pub enum FrontendTransitionKind {
578 Setup,
579 Test,
580 Action,
581 Assertion,
582 Teardown,
583 Background,
584}
585
586#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
587#[serde(rename_all = "kebab-case")]
588pub enum FrontendLimitationScope {
589 Worker,
590 Test,
591 Retry,
592 Phase,
593 Action,
594 Assertion,
595}
596
597#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
598#[serde(rename_all = "camelCase", deny_unknown_fields)]
599pub struct FrontendAttribution {
600 pub run: AttributionPrecision,
601 pub worker: AttributionPrecision,
602 pub test: AttributionPrecision,
603 pub retry: AttributionPrecision,
604 pub phase: AttributionPrecision,
605 pub action: AttributionPrecision,
606 pub assertion: AttributionPrecision,
607}
608
609#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
610#[serde(rename_all = "camelCase", deny_unknown_fields)]
611pub struct FrontendLimitation {
612 pub id: String,
613 pub scopes: Vec<FrontendLimitationScope>,
614 pub reason: String,
615}
616
617#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
618#[serde(rename_all = "camelCase", deny_unknown_fields)]
619pub struct FrontendRunDeclaration {
620 pub protocol_version: u32,
621 pub frontend_id: String,
622 pub frontend_version: String,
623 pub language: String,
624 pub structural_source: StructuralSource,
625 pub runners: Vec<FrontendRunnerDeclaration>,
626 pub structural_limitations: Vec<String>,
627}
628
629#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
630#[serde(rename_all = "camelCase", deny_unknown_fields)]
631pub struct FrontendRunnerDeclaration {
632 pub runner: String,
633 pub execution_model: ExecutionModel,
634 pub attribution: FrontendAttribution,
635 pub limitations: Vec<FrontendLimitation>,
636}
637
638#[derive(Debug, Clone, PartialEq, Eq)]
639pub enum FrontendDeclarationError {
640 UnsupportedProtocolVersion(u32),
641 InvalidToken(&'static str),
642 NoRunners,
643 DuplicateRunner(String),
644 RunIdentityNotExact,
645 DuplicateLimitation(String),
646 DuplicateLimitationScope(String),
647 EmptyLimitationScopes(String),
648 InvalidLimitationReason(String),
649 DuplicateStructuralLimitation(String),
650 MissingDowngradeLimitation(FrontendLimitationScope),
651 ExactRetryRequiresExactTest,
652 ExactPhaseRequiresExactTest,
653 ExactAssertionRequiresExactTestAndPhase,
654 ExactActionRequiresExactTestAndPhase,
655 ParallelUnattributedCannotClaimExactCausality,
656}
657
658impl std::fmt::Display for FrontendDeclarationError {
659 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
660 match self {
661 Self::UnsupportedProtocolVersion(version) => {
662 write!(
663 formatter,
664 "unsupported language-frontend protocol version: {version}"
665 )
666 }
667 Self::InvalidToken(field) => write!(formatter, "invalid frontend {field}"),
668 Self::NoRunners => write!(formatter, "frontend declaration has no runners"),
669 Self::DuplicateRunner(runner) => {
670 write!(formatter, "duplicate frontend runner: {runner}")
671 }
672 Self::RunIdentityNotExact => write!(formatter, "frontend run identity must be exact"),
673 Self::DuplicateLimitation(id) => {
674 write!(formatter, "duplicate frontend limitation: {id}")
675 }
676 Self::EmptyLimitationScopes(id) => {
677 write!(formatter, "frontend limitation has no scopes: {id}")
678 }
679 Self::DuplicateLimitationScope(id) => {
680 write!(formatter, "frontend limitation has duplicate scopes: {id}")
681 }
682 Self::InvalidLimitationReason(id) => {
683 write!(formatter, "frontend limitation has an invalid reason: {id}")
684 }
685 Self::DuplicateStructuralLimitation(id) => {
686 write!(formatter, "duplicate structural limitation reference: {id}")
687 }
688 Self::MissingDowngradeLimitation(scope) => write!(
689 formatter,
690 "non-exact {scope:?} attribution has no matching limitation"
691 ),
692 Self::ExactRetryRequiresExactTest => {
693 write!(
694 formatter,
695 "exact retry attribution requires exact test identity"
696 )
697 }
698 Self::ExactPhaseRequiresExactTest => {
699 write!(
700 formatter,
701 "exact phase attribution requires exact test identity"
702 )
703 }
704 Self::ExactAssertionRequiresExactTestAndPhase => write!(
705 formatter,
706 "exact assertion attribution requires exact test and phase identity"
707 ),
708 Self::ExactActionRequiresExactTestAndPhase => write!(
709 formatter,
710 "exact action attribution requires exact test and phase identity"
711 ),
712 Self::ParallelUnattributedCannotClaimExactCausality => write!(
713 formatter,
714 "parallel-unattributed execution cannot claim exact test, retry, phase, action, or assertion causality"
715 ),
716 }
717 }
718}
719
720fn validate_frontend_limitations(
721 limitations: &[FrontendLimitation],
722 limitation_ids: &mut BTreeSet<String>,
723) -> Result<BTreeSet<FrontendLimitationScope>, FrontendDeclarationError> {
724 let mut limited_scopes = BTreeSet::new();
725 for limitation in limitations {
726 if !valid_frontend_token(&limitation.id) {
727 return Err(FrontendDeclarationError::InvalidToken("limitation ID"));
728 }
729 if !limitation_ids.insert(limitation.id.clone()) {
730 return Err(FrontendDeclarationError::DuplicateLimitation(
731 limitation.id.clone(),
732 ));
733 }
734 if limitation.scopes.is_empty() {
735 return Err(FrontendDeclarationError::EmptyLimitationScopes(
736 limitation.id.clone(),
737 ));
738 }
739 let unique_scopes = limitation.scopes.iter().copied().collect::<BTreeSet<_>>();
740 if unique_scopes.len() != limitation.scopes.len() {
741 return Err(FrontendDeclarationError::DuplicateLimitationScope(
742 limitation.id.clone(),
743 ));
744 }
745 if limitation.reason.trim().is_empty()
746 || limitation.reason.trim().len() != limitation.reason.len()
747 || limitation.reason.contains(['\n', '\r', '\0'])
748 {
749 return Err(FrontendDeclarationError::InvalidLimitationReason(
750 limitation.id.clone(),
751 ));
752 }
753 limited_scopes.extend(unique_scopes);
754 }
755 Ok(limited_scopes)
756}
757
758fn validate_frontend_runner(
759 runner: &FrontendRunnerDeclaration,
760 limitation_ids: &mut BTreeSet<String>,
761) -> Result<(), FrontendDeclarationError> {
762 if !valid_frontend_runner_token(&runner.runner) {
763 return Err(FrontendDeclarationError::InvalidToken("runner"));
764 }
765 if runner.attribution.run != AttributionPrecision::Exact {
766 return Err(FrontendDeclarationError::RunIdentityNotExact);
767 }
768 let limited_scopes = validate_frontend_limitations(&runner.limitations, limitation_ids)?;
769 if runner.attribution.retry == AttributionPrecision::Exact
770 && runner.attribution.test != AttributionPrecision::Exact
771 {
772 return Err(FrontendDeclarationError::ExactRetryRequiresExactTest);
773 }
774 if runner.attribution.phase == AttributionPrecision::Exact
775 && runner.attribution.test != AttributionPrecision::Exact
776 {
777 return Err(FrontendDeclarationError::ExactPhaseRequiresExactTest);
778 }
779 for (precision, scope) in [
780 (runner.attribution.worker, FrontendLimitationScope::Worker),
781 (runner.attribution.test, FrontendLimitationScope::Test),
782 (runner.attribution.retry, FrontendLimitationScope::Retry),
783 (runner.attribution.phase, FrontendLimitationScope::Phase),
784 (runner.attribution.action, FrontendLimitationScope::Action),
785 (
786 runner.attribution.assertion,
787 FrontendLimitationScope::Assertion,
788 ),
789 ] {
790 if precision != AttributionPrecision::Exact && !limited_scopes.contains(&scope) {
791 return Err(FrontendDeclarationError::MissingDowngradeLimitation(scope));
792 }
793 }
794 if runner.attribution.assertion == AttributionPrecision::Exact
795 && (runner.attribution.test != AttributionPrecision::Exact
796 || runner.attribution.phase != AttributionPrecision::Exact)
797 {
798 return Err(FrontendDeclarationError::ExactAssertionRequiresExactTestAndPhase);
799 }
800 if runner.attribution.action == AttributionPrecision::Exact
801 && (runner.attribution.test != AttributionPrecision::Exact
802 || runner.attribution.phase != AttributionPrecision::Exact)
803 {
804 return Err(FrontendDeclarationError::ExactActionRequiresExactTestAndPhase);
805 }
806 if runner.execution_model == ExecutionModel::ParallelUnattributed
807 && [
808 runner.attribution.test,
809 runner.attribution.retry,
810 runner.attribution.phase,
811 runner.attribution.action,
812 runner.attribution.assertion,
813 ]
814 .contains(&AttributionPrecision::Exact)
815 {
816 return Err(FrontendDeclarationError::ParallelUnattributedCannotClaimExactCausality);
817 }
818 Ok(())
819}
820
821impl std::error::Error for FrontendDeclarationError {}
822
823fn valid_frontend_token(value: &str) -> bool {
824 (1..=64).contains(&value.len())
825 && value.as_bytes()[0].is_ascii_lowercase()
826 && value.bytes().all(|byte| {
827 byte.is_ascii_lowercase() || byte.is_ascii_digit() || matches!(byte, b'.' | b'+' | b'-')
828 })
829}
830
831fn valid_frontend_runner_token(value: &str) -> bool {
832 (1..=64).contains(&value.len())
833 && value.as_bytes()[0].is_ascii_lowercase()
834 && value.bytes().all(|byte| {
835 byte.is_ascii_lowercase()
836 || byte.is_ascii_digit()
837 || matches!(byte, b'.' | b'+' | b'-' | b':')
838 })
839}
840
841fn valid_structural_limitation_reference(value: &str) -> bool {
842 (1..=512).contains(&value.len())
843 && value.trim().len() == value.len()
844 && !value.chars().any(char::is_control)
845}
846
847pub fn validate_frontend_run_declaration(
848 declaration: &FrontendRunDeclaration,
849) -> Result<(), FrontendDeclarationError> {
850 if declaration.protocol_version != LANGUAGE_FRONTEND_PROTOCOL_VERSION {
851 return Err(FrontendDeclarationError::UnsupportedProtocolVersion(
852 declaration.protocol_version,
853 ));
854 }
855 for (field, value) in [
856 ("ID", declaration.frontend_id.as_str()),
857 ("version", declaration.frontend_version.as_str()),
858 ("language", declaration.language.as_str()),
859 ] {
860 if !valid_frontend_token(value) {
861 return Err(FrontendDeclarationError::InvalidToken(field));
862 }
863 }
864 if declaration.runners.is_empty() {
865 return Err(FrontendDeclarationError::NoRunners);
866 }
867 let mut limitation_ids = BTreeSet::new();
868 for limitation in &declaration.structural_limitations {
869 if !valid_structural_limitation_reference(limitation) {
870 return Err(FrontendDeclarationError::InvalidToken(
871 "structural limitation ID",
872 ));
873 }
874 if !limitation_ids.insert(limitation.clone()) {
875 return Err(FrontendDeclarationError::DuplicateStructuralLimitation(
876 limitation.clone(),
877 ));
878 }
879 }
880 let mut runners = BTreeSet::new();
881 for runner in &declaration.runners {
882 if !runners.insert(runner.runner.clone()) {
883 return Err(FrontendDeclarationError::DuplicateRunner(
884 runner.runner.clone(),
885 ));
886 }
887 validate_frontend_runner(runner, &mut limitation_ids)?;
888 }
889 Ok(())
890}
891
892#[cfg(test)]
893mod tests {
894 use super::*;
895
896 #[test]
897 fn checked_in_registry_matches_rust_constants() {
898 let contract = registry().expect("contract registry must be valid JSON");
899 assert_eq!(contract.contract_version, CONTRACT_VERSION);
900 assert_eq!(contract.status, "frozen");
901 assert!(!contract.resident_process);
902 assert_eq!(
903 contract.evidence_archive.schema_version,
904 EVIDENCE_ARCHIVE_SCHEMA_VERSION
905 );
906 assert_eq!(contract.evidence_archive.magic, EVIDENCE_ARCHIVE_MAGIC);
907 assert_eq!(
908 contract.agent_json.schema_version,
909 AGENT_JSON_SCHEMA_VERSION
910 );
911 assert_eq!(contract.agent_json.max_bytes, AGENT_JSON_MAX_BYTES);
912 assert_eq!(contract.agent_json.default_page_size, DEFAULT_PAGE_SIZE);
913 assert_eq!(contract.agent_json.error_codes, ERROR_CODES);
914 assert_eq!(
915 contract.process_supervision.schema_version,
916 PROCESS_SUPERVISION_SCHEMA_VERSION
917 );
918 assert_eq!(
919 contract.process_supervision.diagnostic_interval_ms,
920 DEFAULT_DIAGNOSTIC_INTERVAL_MS
921 );
922 assert_eq!(
923 contract.process_supervision.timeout_exit_code,
924 COMMAND_TIMEOUT_EXIT_CODE
925 );
926 assert_eq!(
927 contract.process_supervision.termination_grace_ms,
928 COMMAND_TERMINATION_GRACE_MS
929 );
930 }
931
932 #[test]
933 fn checked_in_probe_v2_contract_matches_rust_constants() {
934 let contract = probe_v2_contract().expect("probe v2 contract must be valid JSON");
935 assert_eq!(contract.probe_version, PROBE_V2_VERSION);
936 assert_eq!(contract.semantics, "frozen");
937 assert_eq!(contract.implementation, "experimental");
938 assert_eq!(contract.decision_encoding.radix, PROBE_V2_RADIX);
939 assert_eq!(contract.decision_encoding.digits.unreached, 0);
940 assert_eq!(contract.decision_encoding.digits.r#false, 1);
941 assert_eq!(contract.decision_encoding.digits.r#true, 2);
942 assert!(contract.decision_encoding.outcome_stored_separately);
943 assert_eq!(
944 contract
945 .decision_encoding
946 .javascript_maximum_encoded_conditions,
947 PROBE_V2_JS_MAX_CONDITIONS
948 );
949 assert_eq!(contract.promotion.realistic_median_runtime_ratio_max, 1.10);
950 }
951
952 fn exact_frontend_declaration() -> FrontendRunDeclaration {
953 FrontendRunDeclaration {
954 protocol_version: LANGUAGE_FRONTEND_PROTOCOL_VERSION,
955 frontend_id: "javascript".into(),
956 frontend_version: "javascript-v1".into(),
957 language: "javascript".into(),
958 structural_source: StructuralSource::OwnedProbes,
959 runners: vec![FrontendRunnerDeclaration {
960 runner: "playwright".into(),
961 execution_model: ExecutionModel::ParallelContextPropagated,
962 attribution: FrontendAttribution {
963 run: AttributionPrecision::Exact,
964 worker: AttributionPrecision::Exact,
965 test: AttributionPrecision::Exact,
966 retry: AttributionPrecision::Exact,
967 phase: AttributionPrecision::Exact,
968 action: AttributionPrecision::Exact,
969 assertion: AttributionPrecision::Exact,
970 },
971 limitations: Vec::new(),
972 }],
973 structural_limitations: Vec::new(),
974 }
975 }
976
977 #[test]
978 fn checked_in_language_frontend_contract_matches_rust_types() {
979 let contract = language_frontend_protocol_contract()
980 .expect("language frontend protocol must be valid JSON");
981 assert_eq!(
982 contract.frontend_protocol_version,
983 LANGUAGE_FRONTEND_PROTOCOL_VERSION
984 );
985 assert_eq!(contract.status, "frozen");
986 assert_eq!(contract.manifest_model, "coverage-manifest-v1");
987 assert_eq!(contract.observation_model, "evidence-archive-v3");
988 assert_eq!(contract.probe_model, "ternary-decision-v2");
989 assert!(
990 contract
991 .requirements
992 .selected_unstarted_has_test_identity_only
993 );
994 assert_eq!(
995 contract.identity_axes,
996 ["run", "worker", "test", "retry", "phase"]
997 );
998 assert_eq!(
999 contract.structural_sources,
1000 [
1001 StructuralSource::OwnedProbes,
1002 StructuralSource::NativeImport,
1003 StructuralSource::Mixed,
1004 ]
1005 .map(|value| serde_json::to_value(value)
1006 .unwrap()
1007 .as_str()
1008 .unwrap()
1009 .to_owned())
1010 );
1011 assert_eq!(
1012 contract.execution_models,
1013 [
1014 ExecutionModel::ProcessPerTest,
1015 ExecutionModel::SerialInProcess,
1016 ExecutionModel::ParallelContextPropagated,
1017 ExecutionModel::ParallelUnattributed,
1018 ]
1019 .map(|value| serde_json::to_value(value)
1020 .unwrap()
1021 .as_str()
1022 .unwrap()
1023 .to_owned())
1024 );
1025 assert_eq!(
1026 contract.attribution_precisions,
1027 [
1028 AttributionPrecision::Exact,
1029 AttributionPrecision::Aggregate,
1030 AttributionPrecision::Unavailable,
1031 ]
1032 .map(|value| serde_json::to_value(value)
1033 .unwrap()
1034 .as_str()
1035 .unwrap()
1036 .to_owned())
1037 );
1038 assert_eq!(
1039 contract.transition_kinds,
1040 [
1041 FrontendTransitionKind::Setup,
1042 FrontendTransitionKind::Test,
1043 FrontendTransitionKind::Action,
1044 FrontendTransitionKind::Assertion,
1045 FrontendTransitionKind::Teardown,
1046 FrontendTransitionKind::Background,
1047 ]
1048 .map(|value| serde_json::to_value(value)
1049 .unwrap()
1050 .as_str()
1051 .unwrap()
1052 .to_owned())
1053 );
1054 assert_eq!(
1055 contract.limitation_scopes,
1056 [
1057 FrontendLimitationScope::Worker,
1058 FrontendLimitationScope::Test,
1059 FrontendLimitationScope::Retry,
1060 FrontendLimitationScope::Phase,
1061 FrontendLimitationScope::Action,
1062 FrontendLimitationScope::Assertion,
1063 ]
1064 .map(|value| serde_json::to_value(value)
1065 .unwrap()
1066 .as_str()
1067 .unwrap()
1068 .to_owned())
1069 );
1070 assert!(
1071 !contract
1072 .requirements
1073 .timestamp_attribution_may_claim_causality
1074 );
1075 assert!(!contract.requirements.frontend_may_compute_coverage_verdicts);
1076 assert!(contract.requirements.multiple_runners_per_frontend);
1077 assert!(
1078 contract
1079 .requirements
1080 .structural_limitations_reference_manifest_ids
1081 );
1082 assert!(contract.requirements.attribution_limitations_runner_scoped);
1083 assert!(contract.requirements.complete_manifest_before_execution);
1084 assert!(contract.requirements.unknown_obligation_fatal);
1085 assert!(contract.requirements.identity_downgrade_requires_limitation);
1086 assert!(contract.requirements.unknown_phase_reference_fatal);
1087 assert!(contract.requirements.phase_causality_acyclic);
1088 assert!(contract.requirements.engine_owns_manifest_merge);
1089 assert!(contract.requirements.engine_owns_evidence_validation);
1090 assert!(contract.requirements.engine_owns_attribution_merge);
1091 assert!(contract.requirements.engine_owns_coverage_analysis);
1092 assert!(contract.requirements.engine_owns_persistence_and_queries);
1093 }
1094
1095 #[test]
1096 fn validates_exact_and_explicitly_degraded_frontends() {
1097 validate_frontend_run_declaration(&exact_frontend_declaration()).unwrap();
1098 let mut degraded = exact_frontend_declaration();
1099 degraded.runners[0].runner = "opaque-runner".into();
1100 degraded.runners[0].attribution.action = AttributionPrecision::Unavailable;
1101 degraded.runners[0].limitations.push(FrontendLimitation {
1102 id: "no-action-lifecycle".into(),
1103 scopes: vec![FrontendLimitationScope::Action],
1104 reason: "The runner exposes assertions but no action lifecycle".into(),
1105 });
1106 validate_frontend_run_declaration(°raded).unwrap();
1107 }
1108
1109 #[test]
1110 fn accepts_the_canonical_node_test_runner_name() {
1111 let mut declaration = exact_frontend_declaration();
1112 declaration.runners[0].runner = "node:test".into();
1113 validate_frontend_run_declaration(&declaration).unwrap();
1114 }
1115
1116 #[test]
1117 fn rejects_unexplained_or_internally_impossible_attribution_claims() {
1118 let mut unexplained = exact_frontend_declaration();
1119 unexplained.runners[0].attribution.assertion = AttributionPrecision::Unavailable;
1120 assert_eq!(
1121 validate_frontend_run_declaration(&unexplained),
1122 Err(FrontendDeclarationError::MissingDowngradeLimitation(
1123 FrontendLimitationScope::Assertion
1124 ))
1125 );
1126
1127 let mut impossible = exact_frontend_declaration();
1128 impossible.runners[0].execution_model = ExecutionModel::ParallelUnattributed;
1129 assert_eq!(
1130 validate_frontend_run_declaration(&impossible),
1131 Err(FrontendDeclarationError::ParallelUnattributedCannotClaimExactCausality)
1132 );
1133
1134 let mut assertion_without_test = exact_frontend_declaration();
1135 assertion_without_test.runners[0].attribution.test = AttributionPrecision::Aggregate;
1136 assertion_without_test.runners[0].attribution.retry = AttributionPrecision::Aggregate;
1137 assertion_without_test.runners[0].attribution.phase = AttributionPrecision::Aggregate;
1138 assertion_without_test.runners[0]
1139 .limitations
1140 .push(FrontendLimitation {
1141 id: "aggregate-tests".into(),
1142 scopes: vec![
1143 FrontendLimitationScope::Test,
1144 FrontendLimitationScope::Retry,
1145 FrontendLimitationScope::Phase,
1146 ],
1147 reason: "The runner pools concurrent test observations".into(),
1148 });
1149 assert_eq!(
1150 validate_frontend_run_declaration(&assertion_without_test),
1151 Err(FrontendDeclarationError::ExactAssertionRequiresExactTestAndPhase)
1152 );
1153 }
1154
1155 #[test]
1156 fn supports_multiple_runners_but_rejects_duplicate_runner_claims() {
1157 let mut declaration = exact_frontend_declaration();
1158 let mut vitest = declaration.runners[0].clone();
1159 vitest.runner = "vitest".into();
1160 declaration.runners.push(vitest.clone());
1161 validate_frontend_run_declaration(&declaration).unwrap();
1162 declaration.runners.push(vitest);
1163 assert_eq!(
1164 validate_frontend_run_declaration(&declaration),
1165 Err(FrontendDeclarationError::DuplicateRunner("vitest".into()))
1166 );
1167 }
1168
1169 #[test]
1170 fn keeps_structural_limitation_references_unique() {
1171 let mut declaration = exact_frontend_declaration();
1172 declaration
1173 .structural_limitations
1174 .push("dynamic-python".into());
1175 validate_frontend_run_declaration(&declaration).unwrap();
1176 declaration
1177 .structural_limitations
1178 .push("dynamic-python".into());
1179 assert_eq!(
1180 validate_frontend_run_declaration(&declaration),
1181 Err(FrontendDeclarationError::DuplicateStructuralLimitation(
1182 "dynamic-python".into()
1183 ))
1184 );
1185 }
1186
1187 #[test]
1188 fn declaration_json_rejects_unknown_fields() {
1189 let mut value = serde_json::to_value(exact_frontend_declaration()).unwrap();
1190 value["verdict"] = serde_json::json!({ "mcdc": 100 });
1191 assert!(serde_json::from_value::<FrontendRunDeclaration>(value).is_err());
1192 }
1193
1194 #[test]
1195 fn checked_in_frontend_examples_are_strict_and_valid() {
1196 for source in [
1197 include_str!("../assets/frontend-v2/examples/javascript-mixed-runners.json"),
1198 include_str!("../assets/frontend-v2/examples/python-pytest-xdist.json"),
1199 ] {
1200 let declaration: FrontendRunDeclaration = serde_json::from_str(source).unwrap();
1201 validate_frontend_run_declaration(&declaration).unwrap();
1202 assert_eq!(
1203 serde_json::from_str::<serde_json::Value>(source).unwrap(),
1204 serde_json::to_value(declaration).unwrap()
1205 );
1206 }
1207 }
1208
1209 #[test]
1210 fn python_coverage_import_contract_keeps_the_oracle_at_the_fact_boundary() {
1211 let contract = python_coverage_import_contract().unwrap();
1212 assert_eq!(contract.schema_version, 1);
1213 assert_eq!(contract.status, "private-spike");
1214 assert_eq!(contract.producer, "coverage.py");
1215 assert_eq!(
1216 contract.supported_collector_cores_for_exact_contexts,
1217 ["ctrace", "pytrace"]
1218 );
1219 assert!(contract.requires_branch_measurement);
1220 assert_eq!(contract.database_access, "forbidden");
1221 assert!(!contract.frontend_computes_verdicts);
1222 assert!(contract.unknown_fields_fatal);
1223 assert!(contract.preserve_unrecognized_contexts_as_background);
1224 assert_eq!(
1225 contract.mcdc_availability,
1226 "unavailable-with-blocking-limitation"
1227 );
1228 assert_eq!(
1229 contract.column_locations,
1230 "unavailable-with-blocking-limitation"
1231 );
1232 }
1233
1234 #[test]
1235 fn evidence_v3_is_the_frozen_language_bound_archive() {
1236 let contract = evidence_v3_contract().unwrap();
1237 assert_eq!(contract.schema_version, EVIDENCE_ARCHIVE_SCHEMA_VERSION);
1238 assert_eq!(contract.status, "frozen");
1239 assert_eq!(contract.magic, EVIDENCE_ARCHIVE_MAGIC);
1240 assert_eq!(contract.framing, "canonical-sorted-length-framed-gzip");
1241 assert_eq!(
1242 contract.required_entries,
1243 ["coverage-model.json", "frontend.json", "manifest.json"]
1244 );
1245 assert_eq!(
1246 contract.frontend_protocol_version,
1247 LANGUAGE_FRONTEND_PROTOCOL_VERSION
1248 );
1249 assert_eq!(
1250 contract.coverage_model_schema_version,
1251 COVERAGE_MODEL_SCHEMA_VERSION
1252 );
1253 assert!(contract.unknown_frontend_fields_fatal);
1254 assert!(contract.unknown_coverage_model_fields_fatal);
1255 assert!(contract.frontend_language_must_match_coverage_model);
1256 assert!(contract.malformed_recognized_jsonl_fatal);
1257 assert!(contract.recognized_jsonl_requires_final_newline);
1258 assert_eq!(EVIDENCE_ARCHIVE_SCHEMA_VERSION, 3);
1259 assert_eq!(EVIDENCE_ARCHIVE_MAGIC, "SUPERCOV-EVIDENCE-3\n");
1260 }
1261
1262 #[test]
1263 fn coverage_model_v1_contract_is_frozen_and_bounded() {
1264 let contract = coverage_model_v1_contract().unwrap();
1265 assert_eq!(contract.schema_version, COVERAGE_MODEL_SCHEMA_VERSION);
1266 assert_eq!(contract.status, "frozen");
1267 assert_eq!(contract.persisted_entry, "coverage-model.json");
1268 assert_eq!(
1269 contract.required_fields,
1270 [
1271 "schemaVersion",
1272 "language",
1273 "variant",
1274 "name",
1275 "completenessMeaning",
1276 "measured",
1277 "notMeasured",
1278 ]
1279 );
1280 assert!(contract.unknown_fields_fatal);
1281 assert!(contract.frontend_language_must_match);
1282 assert!(contract.measured_must_be_nonempty);
1283 assert!(contract.surface_lists_must_be_unique);
1284 assert!(contract.surface_lists_must_be_disjoint);
1285 assert!(contract.strings_must_be_trimmed_single_line);
1286 assert_eq!(contract.max_identifier_bytes, 64);
1287 assert_eq!(contract.max_description_bytes, 4096);
1288 assert_eq!(contract.max_surfaces_per_list, 256);
1289 }
1290
1291 #[test]
1292 fn rust_coverage_v1_contract_fixes_the_complete_target_model() {
1293 let contract = rust_coverage_v1_contract().unwrap();
1294 assert_eq!(contract.model_version, 1);
1295 assert_eq!(contract.status, "frozen-private-frontend");
1296 assert_eq!(contract.language, "rust");
1297 assert_eq!(contract.variant, "rust-source-v1");
1298 assert_eq!(contract.decision_semantics, "masking-mcdc");
1299 assert_eq!(contract.condition_order, "source-evaluation-order");
1300 assert_eq!(contract.probe_model, "ternary-decision-v2");
1301 assert_eq!(contract.source_identity.version, 1);
1302 assert_eq!(contract.source_identity.digest, "sha256");
1303 assert_eq!(contract.source_identity.id_digest_bytes, 12);
1304 assert_eq!(contract.source_identity.separator, "nul");
1305 assert!(
1306 contract
1307 .source_identity
1308 .repeated_authored_expansions_aggregate
1309 );
1310 assert!(
1311 contract
1312 .source_identity
1313 .distinct_synthetic_invocations_remain_distinct
1314 );
1315 assert!(contract.source_identity.ephemeral_paths_forbidden);
1316 assert_eq!(contract.source_identity.collision_policy, "fatal");
1317 assert_eq!(contract.test_context_identity.version, 1);
1318 assert_eq!(contract.test_context_identity.algorithm, "fnv1a-64");
1319 assert_eq!(
1320 contract.test_context_identity.domain,
1321 "supercov-rust-test-v1\0"
1322 );
1323 assert_eq!(
1324 contract.test_context_identity.collision_policy,
1325 "fatal-before-launch"
1326 );
1327 assert_eq!(contract.runner_attempt_identity.version, 1);
1328 assert_eq!(contract.runner_attempt_identity.cargo_test.retry, 0);
1329 assert_eq!(
1330 contract.runner_attempt_identity.cargo_test.total_attempts,
1331 1
1332 );
1333 assert_eq!(contract.runner_attempt_identity.rustdoc.retry, 0);
1334 assert_eq!(contract.runner_attempt_identity.rustdoc.total_attempts, 1);
1335 assert_eq!(
1336 contract.libtest_event_transport.protocol_version,
1337 RUST_LIBTEST_EVENT_PROTOCOL_VERSION
1338 );
1339 assert_eq!(
1340 contract.libtest_event_transport.magic,
1341 RUST_LIBTEST_EVENT_MAGIC
1342 );
1343 assert_eq!(
1344 contract.libtest_event_transport.header_size,
1345 RUST_LIBTEST_EVENT_HEADER_SIZE
1346 );
1347 assert_eq!(
1348 contract.libtest_event_transport.record_header_size,
1349 RUST_LIBTEST_EVENT_RECORD_HEADER_SIZE
1350 );
1351 assert_eq!(
1352 contract.libtest_event_transport.token_size,
1353 RUST_LIBTEST_EVENT_TOKEN_SIZE
1354 );
1355 assert_eq!(
1356 contract.libtest_event_transport.maximum_name_bytes,
1357 RUST_LIBTEST_EVENT_MAX_NAME_BYTES
1358 );
1359 assert_eq!(
1360 contract.libtest_event_transport.event_kinds,
1361 ["filtered-out", "filtered", "started", "timeout", "finished"]
1362 );
1363 assert_eq!(
1364 contract.libtest_event_transport.terminal_results,
1365 ["passed", "failed", "ignored", "benchmarked"]
1366 );
1367 assert_eq!(
1368 contract.libtest_event_transport.process_model,
1369 "one-stock-libtest-process-per-artifact"
1370 );
1371 assert_eq!(
1372 contract.libtest_event_transport.output_authority,
1373 "unmodified-selected-toolchain-libtest"
1374 );
1375 assert_eq!(
1376 contract
1377 .libtest_event_transport
1378 .artifact_binding
1379 .schema_version,
1380 RUST_LIBTEST_COMPANION_BUNDLE_SCHEMA_VERSION
1381 );
1382 assert_eq!(
1383 contract
1384 .libtest_event_transport
1385 .artifact_binding
1386 .required_bindings,
1387 [
1388 "compilerCompanionBuildId",
1389 "rustcCommitHash",
1390 "hostTriple",
1391 "eventProtocolVersion",
1392 "originalSourceSha256",
1393 "eventRuntimeSha256",
1394 "patchedSourceSha256",
1395 "artifactSha256",
1396 ]
1397 );
1398 assert!(
1399 contract
1400 .libtest_event_transport
1401 .artifact_binding
1402 .unknown_fields_fatal
1403 );
1404 assert_eq!(
1405 contract.runner_attempt_identity.nextest.minimum_version,
1406 "0.9.138"
1407 );
1408 assert_eq!(
1409 contract.runner_attempt_identity.nextest.maximum_version,
1410 "0.9.140"
1411 );
1412 assert_eq!(
1413 contract
1414 .runner_attempt_identity
1415 .nextest
1416 .verified_released_versions,
1417 ["0.9.138", "0.9.140"]
1418 );
1419 assert_eq!(
1420 contract.runner_attempt_identity.nextest.execution_mode,
1421 "process-per-test"
1422 );
1423 assert_eq!(
1424 contract
1425 .runner_attempt_identity
1426 .nextest
1427 .identity_environment,
1428 [
1429 "NEXTEST",
1430 "NEXTEST_RUN_ID",
1431 "NEXTEST_VERSION",
1432 "NEXTEST_EXECUTION_MODE",
1433 "NEXTEST_BINARY_ID",
1434 "NEXTEST_TEST_NAME",
1435 "NEXTEST_ATTEMPT",
1436 "NEXTEST_TOTAL_ATTEMPTS",
1437 "NEXTEST_ATTEMPT_ID",
1438 "NEXTEST_STRESS_CURRENT",
1439 "NEXTEST_STRESS_TOTAL",
1440 ]
1441 );
1442 assert_eq!(
1443 contract.runner_attempt_identity.nextest.stress_iteration,
1444 "distinct-axis-unsupported-fail-closed"
1445 );
1446 assert_eq!(
1447 contract
1448 .runner_attempt_identity
1449 .nextest
1450 .selected_but_unstarted,
1451 "logical-test-identity-only"
1452 );
1453 assert_eq!(
1454 contract
1455 .runner_attempt_identity
1456 .nextest
1457 .selection_projection,
1458 "exact-pre-and-post-separator"
1459 );
1460 assert_eq!(
1461 contract.runner_attempt_identity.nextest.concurrent_attempts,
1462 "distinct-durable-ordinal-and-attempt-id"
1463 );
1464 assert_eq!(
1465 contract.runner_attempt_identity.nextest.target_runner_death,
1466 "unmatched-durable-reservation-fatal"
1467 );
1468 assert!(
1469 contract
1470 .source_identity
1471 .authored_canonical_fields
1472 .iter()
1473 .any(|field| field == "semantic-discriminator")
1474 );
1475 assert!(
1476 contract
1477 .source_identity
1478 .synthetic_expansion_canonical_fields
1479 .iter()
1480 .any(|field| field == "owner-local-ordinal")
1481 );
1482 assert_eq!(
1483 contract.required_identity_axes,
1484 ["run", "worker", "test", "retry", "phase"]
1485 );
1486 for surface in [
1487 "authored-source",
1488 "declarative-macro-expansion",
1489 "procedural-macro-expansion",
1490 "derive-expansion",
1491 "build-script-generated-source",
1492 "included-source",
1493 "const-evaluation",
1494 "doctest-source",
1495 ] {
1496 assert!(
1497 contract
1498 .required_owned_surfaces
1499 .iter()
1500 .any(|item| item == surface)
1501 );
1502 }
1503 assert!(!contract.external_coverage_in_product);
1504 }
1505
1506 #[test]
1507 fn rust_probe_transport_contract_fixes_layout_and_fail_closed_health() {
1508 let contract = rust_probe_transport_contract().unwrap();
1509 assert_eq!(
1510 contract.protocol_version,
1511 RUST_PROBE_TRANSPORT_PROTOCOL_VERSION
1512 );
1513 assert_eq!(contract.status, "frozen-private-frontend");
1514 assert_eq!(contract.magic, RUST_PROBE_TRANSPORT_MAGIC);
1515 assert_eq!(contract.byte_order, "little-endian");
1516 assert_eq!(contract.header_size, RUST_PROBE_TRANSPORT_HEADER_SIZE);
1517 assert_eq!(
1518 contract.descriptor_size,
1519 RUST_PROBE_TRANSPORT_DESCRIPTOR_SIZE
1520 );
1521 assert_eq!(contract.token_size, RUST_PROBE_TRANSPORT_TOKEN_SIZE);
1522 assert_eq!(contract.endian_marker, 0x0102_0304);
1523 assert_eq!(contract.header_offsets.next_descriptor, 32);
1524 assert_eq!(contract.header_offsets.next_payload, 40);
1525 assert_eq!(contract.header_offsets.dropped, 48);
1526 assert_eq!(contract.header_offsets.token, 56);
1527 assert_eq!(contract.header_offsets.attachments, 72);
1528 assert_eq!(contract.header_offsets.next_phase, None);
1529 assert_eq!(contract.descriptor_offsets.commit, 0);
1530 assert_eq!(contract.descriptor_offsets.process_id, 4);
1531 assert_eq!(contract.descriptor_offsets.context_id, 8);
1532 assert_eq!(contract.descriptor_offsets.payload_offset, 16);
1533 assert_eq!(contract.descriptor_offsets.checksum, 32);
1534 assert_eq!(contract.record_kinds.hit, 1);
1535 assert_eq!(contract.record_kinds.decision, 2);
1536 assert_eq!(contract.record_kinds.ordinal_hit, 3);
1537 assert_eq!(contract.record_kinds.phase, None);
1538 assert_eq!(contract.publication.commit_value, 1);
1539 assert_eq!(contract.publication.writer_ordering, "release");
1540 assert_eq!(contract.publication.reader_ordering, "acquire");
1541 assert!(
1542 contract
1543 .publication
1544 .complete_descriptors_independently_recoverable
1545 );
1546 assert_eq!(
1547 contract.context.published_identity,
1548 ["run", "worker", "test", "retry", "phase"]
1549 );
1550 assert_eq!(contract.context.zero, "background-or-unattributed");
1551 assert_eq!(contract.context.max, "reserved-runtime-sentinel");
1552 assert!(
1553 contract
1554 .completeness
1555 .zero_attachments_blocks_terminal_passing_attempt
1556 );
1557 assert!(
1558 contract
1559 .completeness
1560 .dropped_records_block_terminal_passing_attempt
1561 );
1562 assert!(
1563 contract
1564 .completeness
1565 .incomplete_records_block_terminal_passing_attempt
1566 );
1567 assert!(
1568 contract
1569 .completeness
1570 .context_zero_excluded_from_passed_per_test_coverage
1571 );
1572 assert!(contract.integrity.symlink_transport_fatal);
1573 assert_eq!(
1574 contract.supported_targets,
1575 [
1576 "aarch64-apple-darwin",
1577 "x86_64-apple-darwin",
1578 "aarch64-unknown-linux-gnu",
1579 "aarch64-unknown-linux-musl",
1580 "x86_64-unknown-linux-gnu",
1581 "x86_64-unknown-linux-musl",
1582 ]
1583 );
1584 assert_eq!(contract.unsupported_target, "fail-closed");
1585 }
1586
1587 #[test]
1588 fn rust_probe_transport_v3_adds_join_bounded_thread_phases_only() {
1589 let v1 = rust_probe_transport_contract().unwrap();
1590 let v3 = rust_probe_transport_v3_contract().unwrap();
1591 assert_eq!(v1.protocol_version, RUST_PROBE_TRANSPORT_PROTOCOL_VERSION);
1592 assert_eq!(v1.magic, RUST_PROBE_TRANSPORT_MAGIC);
1593 assert_eq!(v1.record_kinds.phase, None);
1594 assert_eq!(v1.thread_scope, None);
1595 assert_eq!(
1596 v3.protocol_version,
1597 RUST_PROBE_TRANSPORT_V3_PROTOCOL_VERSION
1598 );
1599 assert_eq!(v3.status, "candidate-private-frontend");
1600 assert_eq!(v3.magic, RUST_PROBE_TRANSPORT_V3_MAGIC);
1601 assert_eq!(v3.record_kinds.phase, Some(4));
1602 assert_eq!(v3.record_kinds.thread_phase, Some(5));
1603 assert_eq!(v3.record_kinds.thread_end, Some(6));
1604 assert_eq!(v3.record_kinds.test_boundary, Some(7));
1605 assert_eq!(v3.header_offsets.next_phase, Some(80));
1606 let thread_scope = v3.thread_scope.as_ref().expect("v3 freezes threadScope");
1607 assert_eq!(thread_scope.domain, "supercov-rust-thread-phase-v1\0");
1608 assert_eq!(
1609 thread_scope.escaped_thread_limitation,
1610 "RUST_THREAD_OUTLIVED_TEST"
1611 );
1612 assert!(thread_scope.escaped_thread_records_become_background);
1613 assert!(thread_scope.duplicate_thread_end_fatal);
1614 assert!(thread_scope.duplicate_test_boundary_fatal);
1615 assert!(thread_scope.thread_end_committed_when_start_routine_returns);
1616 assert!(thread_scope.test_boundary_committed_when_test_context_exits);
1617 assert_eq!(v3.header_size, v1.header_size);
1618 assert_eq!(v3.descriptor_size, v1.descriptor_size);
1619 assert_eq!(v3.token_size, v1.token_size);
1620 let mut v3_header = v3.header_offsets.clone();
1621 v3_header.next_phase = None;
1622 assert_eq!(v3_header, v1.header_offsets);
1623 assert_eq!(v3.descriptor_offsets, v1.descriptor_offsets);
1624 assert_eq!(v3.publication, v1.publication);
1625 assert_eq!(v3.integrity, v1.integrity);
1626 assert_eq!(v3.completeness, v1.completeness);
1627 assert_eq!(v3.supported_targets, v1.supported_targets);
1628 assert_eq!(v3.unsupported_target, v1.unsupported_target);
1629 }
1630}