Skip to main content

supercov_engine/
coverage_report.rs

1//! Language-neutral reconstruction of coverage views from frozen obligations
2//! and per-attempt evidence.
3//!
4//! This module deliberately knows nothing about JavaScript or any test runner.
5//! Language frontends provide the manifest and normalized evidence records;
6//! Rust owns merging, attempt outcomes, attribution confidence, filtering and
7//! every structural coverage verdict.
8
9use std::collections::{BTreeMap, BTreeSet, HashMap};
10use std::path::{Path, PathBuf};
11
12use serde::{Deserialize, Serialize};
13use serde_json::Value;
14use supercov_contracts::{COVERAGE_MODEL_SCHEMA_VERSION, FrontendRunDeclaration};
15
16use crate::coverage_analysis::{
17    AnalysisError, BranchCoverage, CoverageCoreInput, CoverageSummary, DecisionCoverage,
18    McdcVector, PointCoverage, PointKind, analyze_core, find_witnesses_for_conditions,
19};
20use crate::evidence_archive::{EvidenceArchiveEntry, read_archive};
21
22#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
23#[serde(rename_all = "camelCase", deny_unknown_fields)]
24pub struct DecisionMeta {
25    pub id: String,
26    pub file: String,
27    pub line: usize,
28    pub column: usize,
29    pub source: String,
30    pub conditions: Vec<String>,
31    pub kind: String,
32}
33
34#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
35#[serde(rename_all = "camelCase", deny_unknown_fields)]
36pub struct PointMeta {
37    pub id: String,
38    pub kind: PointKind,
39    pub file: String,
40    pub line: usize,
41    pub column: usize,
42    pub source: String,
43    #[serde(skip_serializing_if = "Option::is_none")]
44    pub label: Option<String>,
45}
46
47#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
48#[serde(rename_all = "camelCase", deny_unknown_fields)]
49pub struct BranchAlternativeMeta {
50    pub id: String,
51    pub label: String,
52}
53
54#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
55#[serde(rename_all = "camelCase", deny_unknown_fields)]
56pub struct BranchMeta {
57    pub id: String,
58    pub kind: String,
59    pub file: String,
60    pub line: usize,
61    pub column: usize,
62    pub source: String,
63    pub alternatives: Vec<BranchAlternativeMeta>,
64}
65
66#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
67#[serde(rename_all = "camelCase", deny_unknown_fields)]
68pub struct CoverageManifest {
69    pub decisions: Vec<DecisionMeta>,
70    pub points: Vec<PointMeta>,
71    pub branches: Vec<BranchMeta>,
72    #[serde(default, skip_serializing_if = "Vec::is_empty")]
73    pub limitations: Vec<Value>,
74    /// Obligation IDs the frontend declined to measure exactly. They stay in
75    /// the manifest so the report can say what was not measured, but they must
76    /// never be counted as uncovered.
77    #[serde(default, skip_serializing_if = "Vec::is_empty")]
78    pub unmeasured: Vec<String>,
79    #[serde(skip_serializing_if = "Option::is_none")]
80    pub scope: Option<Value>,
81}
82
83#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
84#[serde(rename_all = "camelCase", deny_unknown_fields)]
85pub struct DecisionSnapshot {
86    pub meta: DecisionMeta,
87    pub vectors: Vec<McdcVector>,
88}
89
90#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
91#[serde(rename_all = "camelCase", deny_unknown_fields)]
92pub struct RuntimeEvent {
93    #[serde(rename = "type")]
94    pub event_type: String,
95    pub id: String,
96    #[serde(skip_serializing_if = "Option::is_none")]
97    pub vector: Option<McdcVector>,
98    pub timestamp_ms: i64,
99    #[serde(skip_serializing_if = "Option::is_none")]
100    pub phase_id: Option<String>,
101    /// Legacy test-statement location retained for reading historical archives.
102    /// The assertion-map workflow does not emit test-statement markers.
103    #[serde(default, skip_serializing_if = "Option::is_none")]
104    pub statement_id: Option<String>,
105    pub environment: String,
106}
107
108/// One observed (side selected, result truthy) pair of a value-position logical expression.
109#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
110#[serde(rename_all = "camelCase", deny_unknown_fields)]
111pub struct LogicalVector {
112    /// The right operand was evaluated (the left did not short-circuit).
113    pub right: bool,
114    /// The selected result was truthy.
115    pub truthy: bool,
116}
117
118/// Distinct outcomes of one `logical-value` branch within a test; gives per-operand outcomes
119/// once combined with the operator recorded in the manifest.
120#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
121#[serde(rename_all = "camelCase", deny_unknown_fields)]
122pub struct LogicalSnapshot {
123    pub id: String,
124    pub vectors: Vec<LogicalVector>,
125}
126
127#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
128#[serde(rename_all = "camelCase", deny_unknown_fields)]
129pub struct RuntimeSnapshot {
130    #[serde(default)]
131    pub decisions: Vec<DecisionSnapshot>,
132    #[serde(default)]
133    pub hits: Vec<String>,
134    #[serde(default)]
135    pub events: Vec<RuntimeEvent>,
136    #[serde(default, skip_serializing_if = "Vec::is_empty")]
137    pub logicals: Vec<LogicalSnapshot>,
138}
139
140#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
141#[serde(rename_all = "camelCase", deny_unknown_fields)]
142pub struct ExecutionScope {
143    pub version: usize,
144    pub run_id: String,
145    pub worker_id: String,
146    pub test_id: String,
147    pub test_key: String,
148    pub retry: usize,
149    pub attempt_id: String,
150}
151
152#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
153#[serde(rename_all = "camelCase", deny_unknown_fields)]
154pub struct ServerRecord {
155    #[serde(rename = "type")]
156    pub record_type: String,
157    #[serde(skip_serializing_if = "Option::is_none")]
158    pub meta: Option<DecisionMeta>,
159    #[serde(skip_serializing_if = "Option::is_none")]
160    pub vector: Option<McdcVector>,
161    #[serde(skip_serializing_if = "Option::is_none")]
162    pub id: Option<String>,
163    #[serde(skip_serializing_if = "Option::is_none")]
164    pub timestamp_ms: Option<i64>,
165    #[serde(skip_serializing_if = "Option::is_none")]
166    pub phase_id: Option<String>,
167    /// The producing test statement, under this record's exact execution scope.
168    #[serde(default, skip_serializing_if = "Option::is_none")]
169    pub statement_id: Option<String>,
170    #[serde(skip_serializing_if = "Option::is_none")]
171    pub scope: Option<ExecutionScope>,
172}
173
174#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
175#[serde(rename_all = "camelCase", deny_unknown_fields)]
176pub struct CoveragePhase {
177    pub id: String,
178    pub kind: String,
179    pub operation: String,
180    #[serde(skip_serializing_if = "Option::is_none")]
181    pub source: Option<String>,
182    #[serde(skip_serializing_if = "Option::is_none")]
183    pub caused_by_phase_id: Option<String>,
184    pub started_at_ms: i64,
185    #[serde(skip_serializing_if = "Option::is_none")]
186    pub ended_at_ms: Option<i64>,
187    #[serde(skip_serializing_if = "Option::is_none")]
188    pub status: Option<String>,
189    #[serde(skip_serializing_if = "Option::is_none")]
190    pub error: Option<String>,
191}
192
193#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
194#[serde(rename_all = "camelCase", deny_unknown_fields)]
195pub struct TestProvenance {
196    pub runner: String,
197    pub kind: String,
198    #[serde(skip_serializing_if = "Option::is_none")]
199    pub project: Option<String>,
200    pub source: String,
201}
202
203impl Default for TestProvenance {
204    fn default() -> Self {
205        Self {
206            runner: "unknown".into(),
207            kind: "unknown".into(),
208            project: None,
209            source: "unknown".into(),
210        }
211    }
212}
213
214#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
215#[serde(rename_all = "camelCase", deny_unknown_fields)]
216pub struct RawTestResult {
217    #[serde(skip_serializing_if = "Option::is_none")]
218    pub test_id: Option<String>,
219    #[serde(skip_serializing_if = "Option::is_none")]
220    pub scope: Option<ExecutionScope>,
221    pub test: String,
222    #[serde(skip_serializing_if = "Option::is_none")]
223    pub test_file: Option<String>,
224    #[serde(skip_serializing_if = "Option::is_none")]
225    pub title: Option<String>,
226    #[serde(skip_serializing_if = "Option::is_none")]
227    pub retry: Option<usize>,
228    #[serde(skip_serializing_if = "Option::is_none")]
229    pub status: Option<String>,
230    #[serde(skip_serializing_if = "Option::is_none")]
231    pub expected_status: Option<String>,
232    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
233    pub flaky: bool,
234    #[serde(default)]
235    pub provenance: TestProvenance,
236    #[serde(default = "default_test_role")]
237    pub role: String,
238    /// How exactly this test's coverage could be credited to it.
239    ///
240    /// `exact` is the ordinary case and the default, so a frontend that has
241    /// not been taught the difference keeps its behaviour. `run-wide` says the
242    /// test ran and reached code, and that nothing can say which code: a Go
243    /// test that called `t.Parallel()` stores into the same probe array as the
244    /// tests running beside it.
245    ///
246    /// The distinction is the whole point of the field. A test that reached
247    /// nothing and a test whose reach cannot be narrowed both have an empty
248    /// hit set, and reporting the second as the first is a wrong number
249    /// wearing the shape of a right one.
250    #[serde(default = "default_attribution")]
251    pub attribution: String,
252    #[serde(default)]
253    pub phases: Vec<CoveragePhase>,
254    #[serde(default)]
255    pub runtime: Vec<RuntimeSnapshot>,
256    #[serde(default)]
257    pub browser: Vec<RuntimeSnapshot>,
258    #[serde(default)]
259    pub server: Vec<ServerRecord>,
260}
261
262fn default_test_role() -> String {
263    "test".into()
264}
265
266fn default_attribution() -> String {
267    ATTRIBUTION_EXACT.into()
268}
269
270/// This test's coverage is its own: what it reached was credited to it.
271pub const ATTRIBUTION_EXACT: &str = "exact";
272
273/// This test ran, and what it reached was recorded against the run rather than
274/// against it. Its own reach is unknown, and bounded above by the run's.
275pub const ATTRIBUTION_RUN_WIDE: &str = "run-wide";
276
277/// What this test is recorded as reaching is really its own, and is not all of
278/// it: a lower bound, with the run's coverage as the upper one.
279///
280/// Ruby records a line against the first test that reaches it and never again,
281/// which is what makes its collection cheap. Every later test that runs the
282/// same line is recorded as having reached nothing there -- so a test's hits
283/// prove what it ran and its silence proves nothing.
284pub const ATTRIBUTION_PARTIAL: &str = "partial";
285
286/// Whether what this test is recorded as reaching is its own.
287///
288/// True for `exact` and for `partial`: both record real coverage of this
289/// test's, and a union over them is a union of things that happened. False
290/// only where the coverage went to the run instead, and counting the test in a
291/// percentage would divide by a test that contributes nothing.
292pub fn coverage_is_its_own(attribution: &str) -> bool {
293    attribution != ATTRIBUTION_RUN_WIDE
294}
295
296/// Whether this test's silence means it did not run the code.
297///
298/// Only `exact` earns that. Under `partial` a test that is recorded as
299/// reaching nothing may have run the same lines as the test that was credited
300/// with them, and under `run-wide` nothing was recorded at all -- so reading
301/// an absence as proof is how a change to code a test exercised comes back as
302/// "this test is unaffected".
303pub fn coverage_is_complete(attribution: &str) -> bool {
304    attribution == ATTRIBUTION_EXACT || attribution.is_empty()
305}
306
307#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
308#[serde(rename_all = "camelCase")]
309pub struct TestAttempt {
310    pub retry: usize,
311    pub status: String,
312    #[serde(skip_serializing_if = "Option::is_none")]
313    pub expected_status: Option<String>,
314}
315
316#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
317#[serde(rename_all = "camelCase")]
318pub struct CoverageConfidence {
319    pub level: String,
320    pub setup_only: bool,
321    pub background_only: bool,
322    pub asserted: bool,
323    pub tests: Vec<String>,
324    pub asserted_tests: Vec<String>,
325    pub runners: Vec<String>,
326    pub kinds: Vec<String>,
327    pub e2e: bool,
328}
329
330#[derive(Debug, Clone, PartialEq, Serialize)]
331#[serde(rename_all = "camelCase")]
332pub struct VectorObservation {
333    pub vector: McdcVector,
334    pub tests: Vec<String>,
335    #[serde(skip_serializing_if = "Vec::is_empty")]
336    pub phases: Vec<String>,
337    #[serde(skip_serializing_if = "Vec::is_empty")]
338    pub explicit_phases: Vec<String>,
339    pub confidence: CoverageConfidence,
340}
341
342#[derive(Debug, Clone, PartialEq, Serialize)]
343#[serde(rename_all = "camelCase")]
344pub struct ConditionResult {
345    pub index: usize,
346    pub source: String,
347    pub covered: bool,
348    pub assertion_covered: bool,
349    #[serde(skip_serializing_if = "Option::is_none")]
350    pub witness: Option<[McdcVector; 2]>,
351    #[serde(skip_serializing_if = "Option::is_none")]
352    pub witness_tests: Option<[Vec<String>; 2]>,
353}
354
355#[derive(Debug, Clone, PartialEq, Serialize)]
356#[serde(rename_all = "camelCase")]
357pub struct DecisionResult {
358    pub meta: DecisionMeta,
359    pub executed: bool,
360    pub covered: bool,
361    pub vectors: Vec<McdcVector>,
362    pub vector_observations: Vec<VectorObservation>,
363    pub conditions: Vec<ConditionResult>,
364    pub tests: Vec<String>,
365    pub confidence: CoverageConfidence,
366}
367
368#[derive(Debug, Clone, PartialEq, Serialize)]
369#[serde(rename_all = "camelCase")]
370pub struct PointResult {
371    pub meta: PointMeta,
372    pub covered: bool,
373    #[serde(skip)]
374    pub measured: bool,
375    pub tests: Vec<String>,
376    pub phases: Vec<String>,
377    pub confidence: CoverageConfidence,
378}
379
380#[derive(Debug, Clone, PartialEq, Serialize)]
381#[serde(rename_all = "camelCase")]
382pub struct AlternativeResult {
383    pub id: String,
384    pub label: String,
385    pub covered: bool,
386    pub tests: Vec<String>,
387    pub phases: Vec<String>,
388    pub confidence: CoverageConfidence,
389}
390
391#[derive(Debug, Clone, PartialEq, Serialize)]
392#[serde(rename_all = "camelCase")]
393pub struct BranchResult {
394    pub meta: BranchMeta,
395    pub covered: bool,
396    pub alternatives: Vec<AlternativeResult>,
397}
398
399#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize)]
400pub struct SourceLine {
401    pub file: String,
402    pub line: usize,
403}
404
405/// One line's share of the points that sit on it, while the view is built.
406#[derive(Debug, Default)]
407struct LineAggregate {
408    covered: bool,
409    measured: bool,
410    tests: BTreeSet<String>,
411    phases: BTreeSet<String>,
412    explicit_phases: BTreeSet<String>,
413}
414
415#[derive(Debug, Clone, PartialEq, Serialize)]
416#[serde(rename_all = "camelCase")]
417pub struct LineResult {
418    pub file: String,
419    pub line: usize,
420    pub covered: bool,
421    /// False when every obligation on this line was declined, which keeps the
422    /// line addressable while leaving it out of the covered/total counts. Not
423    /// serialized: it decides a total, it is not a fact about the line worth
424    /// publishing, and the limitation records already say why.
425    #[serde(skip)]
426    pub measured: bool,
427    pub tests: Vec<String>,
428    pub runners: Vec<String>,
429    pub kinds: Vec<String>,
430    #[serde(skip_serializing_if = "Option::is_none")]
431    pub exclusive_kind: Option<String>,
432    pub phases: Vec<String>,
433    pub confidence: CoverageConfidence,
434}
435
436#[derive(Debug, Clone, PartialEq, Serialize)]
437#[serde(rename_all = "camelCase")]
438pub struct TestDecisionResult {
439    pub id: String,
440    pub vectors: Vec<McdcVector>,
441}
442
443#[derive(Debug, Clone, PartialEq, Serialize)]
444#[serde(rename_all = "camelCase")]
445pub struct TestCoverageResult {
446    pub id: String,
447    pub name: String,
448    #[serde(skip_serializing_if = "Option::is_none")]
449    pub file: Option<String>,
450    #[serde(skip_serializing_if = "Option::is_none")]
451    pub title: Option<String>,
452    pub retries: Vec<usize>,
453    pub attempts: Vec<TestAttempt>,
454    pub outcome: String,
455    pub provenance: TestProvenance,
456    pub role: String,
457    /// `exact`, or `run-wide` when the test ran but nothing can say what it
458    /// reached. An empty `hits` means "reached nothing" only under `exact`.
459    pub attribution: String,
460    pub hits: Vec<String>,
461    pub decisions: Vec<TestDecisionResult>,
462    pub lines: Vec<SourceLine>,
463}
464
465#[derive(Debug, Clone, PartialEq, Serialize)]
466#[serde(rename_all = "camelCase")]
467pub struct TestFileResult {
468    pub file: String,
469    pub tests: Vec<String>,
470    pub runners: Vec<String>,
471    pub kinds: Vec<String>,
472    pub lines: Vec<SourceLine>,
473}
474
475#[derive(Debug, Clone, PartialEq, Serialize)]
476#[serde(rename_all = "camelCase")]
477pub struct PhaseResult {
478    #[serde(flatten)]
479    pub phase: CoveragePhase,
480    pub test: String,
481    pub hits: Vec<String>,
482    pub decisions: Vec<TestDecisionResult>,
483    pub lines: Vec<SourceLine>,
484    pub browser_events: usize,
485    pub server_events: usize,
486    pub explicit_events: usize,
487    pub inferred_events: usize,
488    pub explicit_browser_events: usize,
489    pub inferred_browser_events: usize,
490    pub explicit_server_events: usize,
491    pub inferred_server_events: usize,
492}
493
494#[derive(Debug, Clone, PartialEq, Serialize)]
495#[serde(rename_all = "camelCase")]
496pub struct DimensionCoverage {
497    #[serde(skip_serializing_if = "Option::is_none")]
498    pub kind: Option<String>,
499    #[serde(skip_serializing_if = "Option::is_none")]
500    pub runner: Option<String>,
501    pub tests: usize,
502    pub setups: usize,
503    /// How many of `tests` have coverage of their own. The summary below is
504    /// computed over exactly these: a test whose reach nothing can narrow
505    /// contributes no hits, and counting it would report a suite that covers
506    /// everything as covering nothing.
507    pub attributed: usize,
508    pub summary: CoverageSummary,
509}
510
511#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
512#[serde(rename_all = "camelCase")]
513pub struct CoverageModel {
514    pub language: String,
515    pub name: String,
516    pub completeness_meaning: String,
517    pub measured: Vec<String>,
518    pub not_measured: Vec<String>,
519}
520
521#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
522#[serde(rename_all = "camelCase", deny_unknown_fields)]
523pub struct CoverageModelDeclaration {
524    pub language: String,
525    pub variant: String,
526    pub name: String,
527    pub completeness_meaning: String,
528    pub measured: Vec<String>,
529    pub not_measured: Vec<String>,
530}
531
532#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
533#[serde(rename_all = "camelCase", deny_unknown_fields)]
534pub struct PersistedCoverageModel {
535    pub schema_version: u32,
536    pub language: String,
537    pub variant: String,
538    pub name: String,
539    pub completeness_meaning: String,
540    pub measured: Vec<String>,
541    pub not_measured: Vec<String>,
542}
543
544impl PersistedCoverageModel {
545    pub fn from_declaration(value: &CoverageModelDeclaration) -> Result<Self, &'static str> {
546        let persisted = Self {
547            schema_version: COVERAGE_MODEL_SCHEMA_VERSION,
548            language: value.language.clone(),
549            variant: value.variant.clone(),
550            name: value.name.clone(),
551            completeness_meaning: value.completeness_meaning.clone(),
552            measured: value.measured.clone(),
553            not_measured: value.not_measured.clone(),
554        };
555        persisted.clone().into_declaration()?;
556        Ok(persisted)
557    }
558
559    fn into_declaration(self) -> Result<CoverageModelDeclaration, &'static str> {
560        if self.schema_version != COVERAGE_MODEL_SCHEMA_VERSION {
561            return Err("unsupported coverage model schema");
562        }
563        let valid_identifier = |value: &str| {
564            (1..=supercov_contracts::COVERAGE_MODEL_MAX_IDENTIFIER_BYTES).contains(&value.len())
565                && value.as_bytes()[0].is_ascii_lowercase()
566                && value.bytes().all(|byte| {
567                    byte.is_ascii_lowercase()
568                        || byte.is_ascii_digit()
569                        || matches!(byte, b'.' | b'+' | b'-')
570                })
571        };
572        let valid_description = |value: &str| {
573            !value.is_empty()
574                && value.trim().len() == value.len()
575                && value.len() <= supercov_contracts::COVERAGE_MODEL_MAX_DESCRIPTION_BYTES
576                && !value.chars().any(char::is_control)
577        };
578        if !valid_identifier(&self.language)
579            || !valid_identifier(&self.variant)
580            || !valid_description(&self.name)
581            || !valid_description(&self.completeness_meaning)
582            || self.measured.is_empty()
583            || self.measured.len() > supercov_contracts::COVERAGE_MODEL_MAX_SURFACES_PER_LIST
584            || self.not_measured.len() > supercov_contracts::COVERAGE_MODEL_MAX_SURFACES_PER_LIST
585            || self
586                .measured
587                .iter()
588                .chain(&self.not_measured)
589                .any(|item| !valid_description(item))
590        {
591            return Err("invalid coverage model declaration");
592        }
593        let measured = self.measured.iter().collect::<BTreeSet<_>>();
594        let not_measured = self.not_measured.iter().collect::<BTreeSet<_>>();
595        if measured.len() != self.measured.len()
596            || not_measured.len() != self.not_measured.len()
597            || !measured.is_disjoint(&not_measured)
598        {
599            return Err("invalid coverage model surface partition");
600        }
601        Ok(CoverageModelDeclaration {
602            language: self.language,
603            variant: self.variant,
604            name: self.name,
605            completeness_meaning: self.completeness_meaning,
606            measured: self.measured,
607            not_measured: self.not_measured,
608        })
609    }
610}
611
612#[derive(Debug, Clone, PartialEq, Serialize)]
613#[serde(rename_all = "camelCase")]
614pub struct CoverageView {
615    pub generated_at: String,
616    pub variant: String,
617    #[serde(skip_serializing_if = "Option::is_none")]
618    pub scope: Option<Value>,
619    pub model: CoverageModel,
620    #[serde(skip_serializing_if = "Option::is_none")]
621    pub integrity: Option<Value>,
622    pub limitations: Vec<Value>,
623    #[serde(skip_serializing_if = "Option::is_none")]
624    pub transport: Option<TransportStats>,
625    pub summary: CoverageSummary,
626    pub coverage_by_kind: Vec<DimensionCoverage>,
627    pub coverage_by_runner: Vec<DimensionCoverage>,
628    pub decisions: Vec<DecisionResult>,
629    pub points: Vec<PointResult>,
630    pub branches: Vec<BranchResult>,
631    pub tests: Vec<TestCoverageResult>,
632    pub test_files: Vec<TestFileResult>,
633    pub phases: Vec<PhaseResult>,
634    pub lines: Vec<LineResult>,
635}
636
637#[derive(Debug, Clone, PartialEq, Serialize)]
638#[serde(rename_all = "camelCase")]
639pub struct CoverageFilters {
640    pub passed: CoverageView,
641    pub failed: CoverageView,
642}
643
644#[derive(Debug, Clone, PartialEq, Serialize)]
645#[serde(rename_all = "camelCase")]
646pub struct CoverageReport {
647    #[serde(flatten)]
648    pub view: CoverageView,
649    #[serde(skip_serializing_if = "Option::is_none")]
650    pub execution: Option<ExecutionResult>,
651    pub filters: CoverageFilters,
652}
653
654#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
655#[serde(rename_all = "camelCase")]
656pub struct ExecutionResult {
657    pub test_exit_code: Option<i32>,
658    pub valid: bool,
659}
660
661#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
662#[serde(rename_all = "camelCase")]
663pub struct TransportStats {
664    pub processes: usize,
665    pub child_launches: usize,
666    pub remote_launches: usize,
667    pub workspace_capabilities: usize,
668    pub scoped_server_records: usize,
669    pub background_server_records: usize,
670    pub corrupt_records: usize,
671    pub corrupt_files: usize,
672}
673
674#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
675#[serde(untagged)]
676enum ExecutionSafeArgument {
677    Text(String),
678    Digest(ExecutionArgumentDigest),
679}
680
681#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
682#[serde(rename_all = "camelCase", deny_unknown_fields)]
683struct ExecutionArgumentDigest {
684    bytes: usize,
685    sha256: String,
686}
687
688#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
689#[serde(rename_all = "camelCase", deny_unknown_fields)]
690struct ExecutionCommandSummary {
691    #[serde(default)]
692    executable: Option<ExecutionSafeArgument>,
693    arguments: Vec<ExecutionSafeArgument>,
694    argument_count: usize,
695}
696
697#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
698#[serde(
699    tag = "event",
700    rename_all = "kebab-case",
701    rename_all_fields = "camelCase",
702    deny_unknown_fields
703)]
704enum ExecutionTraceEvent {
705    Process {
706        at: String,
707        pid: u32,
708        ppid: u32,
709        cwd: String,
710        command: ExecutionCommandSummary,
711        #[serde(default)]
712        entrypoint: Option<String>,
713    },
714    ChildLaunch {
715        at: String,
716        pid: u32,
717        ppid: u32,
718        method: String,
719        command: ExecutionSafeArgument,
720    },
721    RemoteLaunch {
722        at: String,
723        pid: u32,
724        ppid: u32,
725        command: ExecutionCommandSummary,
726        guest_root: String,
727    },
728    WorkspaceCapability {
729        at: String,
730        pid: u32,
731        ppid: u32,
732        host_root: String,
733        guest_root: String,
734        cache_identities: Vec<String>,
735    },
736}
737
738impl ExecutionTraceEvent {
739    fn kind(&self) -> &'static str {
740        match self {
741            Self::Process { .. } => "process",
742            Self::ChildLaunch { .. } => "child-launch",
743            Self::RemoteLaunch { .. } => "remote-launch",
744            Self::WorkspaceCapability { .. } => "workspace-capability",
745        }
746    }
747}
748
749#[derive(Debug, Clone, PartialEq, Deserialize)]
750#[serde(rename_all = "camelCase", deny_unknown_fields)]
751pub struct CoverageReportRequest {
752    pub run_id: String,
753    pub manifest: CoverageManifest,
754    pub raw_results: Vec<RawTestResult>,
755    pub generated_at: String,
756    #[serde(default, skip_serializing_if = "Option::is_none")]
757    pub coverage_model: Option<CoverageModelDeclaration>,
758    #[serde(skip_serializing_if = "Option::is_none")]
759    pub integrity: Option<Value>,
760    #[serde(default, deserialize_with = "deserialize_exit_code")]
761    pub test_exit_code: ExitCodeInput,
762}
763
764#[derive(Debug, Clone, PartialEq, Deserialize)]
765#[serde(rename_all = "camelCase", deny_unknown_fields)]
766pub struct ArchiveReportRequest {
767    pub archive_path: PathBuf,
768    pub run_id: String,
769    pub generated_at: String,
770    #[serde(skip_serializing_if = "Option::is_none")]
771    pub integrity: Option<Value>,
772    #[serde(default, deserialize_with = "deserialize_exit_code")]
773    pub test_exit_code: ExitCodeInput,
774}
775
776#[derive(Debug, Clone, PartialEq, Eq, Default)]
777pub enum ExitCodeInput {
778    #[default]
779    Missing,
780    Present(Option<i32>),
781}
782
783fn deserialize_exit_code<'de, D>(deserializer: D) -> Result<ExitCodeInput, D::Error>
784where
785    D: serde::Deserializer<'de>,
786{
787    Option::<i32>::deserialize(deserializer).map(ExitCodeInput::Present)
788}
789
790#[derive(Debug, Clone, PartialEq, Eq)]
791pub enum ReportError {
792    Analysis(AnalysisError),
793    DecisionAnalysis {
794        decision_id: String,
795        error: AnalysisError,
796    },
797    InvalidEvent(String),
798    InvalidServerRecord(String),
799    InvalidArchive(String),
800    MissingManifest,
801    InvalidJson {
802        path: String,
803        reason: String,
804    },
805    ScopeMismatch {
806        expected: String,
807        actual: String,
808    },
809    NoEvidence(String),
810}
811
812impl From<AnalysisError> for ReportError {
813    fn from(value: AnalysisError) -> Self {
814        Self::Analysis(value)
815    }
816}
817
818#[derive(Clone, Default)]
819struct OrderedVectors {
820    values: Vec<McdcVector>,
821    indexes: HashMap<String, usize>,
822}
823
824impl OrderedVectors {
825    fn insert(&mut self, vector: &McdcVector) -> usize {
826        let key = vector_key(vector);
827        if let Some(index) = self.indexes.get(&key) {
828            return *index;
829        }
830        let index = self.values.len();
831        self.values.push(vector.clone());
832        self.indexes.insert(key, index);
833        index
834    }
835}
836
837#[derive(Clone)]
838struct MutableObservation {
839    vector: McdcVector,
840    tests: BTreeSet<String>,
841    phases: BTreeSet<String>,
842    explicit_phases: BTreeSet<String>,
843}
844
845#[derive(Clone)]
846struct MutableTest {
847    id: String,
848    name: String,
849    file: Option<String>,
850    title: Option<String>,
851    retries: BTreeSet<usize>,
852    attempts: BTreeMap<usize, TestAttempt>,
853    unstarted: bool,
854    runner_reported_flaky: bool,
855    provenance: TestProvenance,
856    role: String,
857    attribution: String,
858    hits: BTreeSet<String>,
859    decisions: BTreeMap<String, OrderedVectors>,
860}
861
862#[derive(Clone)]
863struct MutablePhase {
864    phase: CoveragePhase,
865    test: String,
866    hits: BTreeSet<String>,
867    decisions: BTreeMap<String, OrderedVectors>,
868    browser_events: usize,
869    server_events: usize,
870    explicit_events: usize,
871    inferred_events: usize,
872    explicit_browser_events: usize,
873    inferred_browser_events: usize,
874    explicit_server_events: usize,
875    inferred_server_events: usize,
876}
877
878fn vector_key(vector: &McdcVector) -> String {
879    let mut key = String::with_capacity(vector.values.len() + 2);
880    for value in &vector.values {
881        key.push(match value {
882            None => '-',
883            Some(false) => 'F',
884            Some(true) => 'T',
885        });
886    }
887    key.push(':');
888    key.push(if vector.outcome { 'T' } else { 'F' });
889    key
890}
891
892fn sorted<T: Clone + Ord>(values: &BTreeSet<T>) -> Vec<T> {
893    values.iter().cloned().collect()
894}
895
896fn record_attempt(test: &mut MutableTest, raw: &RawTestResult) {
897    let (Some(retry), Some(raw_status)) = (raw.retry, raw.status.as_ref()) else {
898        return;
899    };
900    let previous = test.attempts.get(&retry);
901    let status = if raw_status == "unknown" {
902        previous.map_or_else(|| raw_status.clone(), |attempt| attempt.status.clone())
903    } else {
904        raw_status.clone()
905    };
906    let expected_status = raw
907        .expected_status
908        .clone()
909        .or_else(|| previous.and_then(|attempt| attempt.expected_status.clone()));
910    test.attempts.insert(
911        retry,
912        TestAttempt {
913            retry,
914            status,
915            expected_status,
916        },
917    );
918}
919
920fn effective_attempt_status(attempt: &TestAttempt) -> &str {
921    if attempt.expected_status.as_deref() == Some("failed") {
922        match attempt.status.as_str() {
923            "failed" => "passed",
924            "passed" => "failed",
925            status => status,
926        }
927    } else {
928        attempt.status.as_str()
929    }
930}
931
932fn test_outcome(test: &MutableTest) -> String {
933    let Some(terminal) = test.attempts.values().next_back() else {
934        return if test.unstarted {
935            "unstarted".into()
936        } else {
937            "unknown".into()
938        };
939    };
940    let terminal_status = effective_attempt_status(terminal);
941    if terminal_status == "passed"
942        && (test.runner_reported_flaky
943            || test
944                .attempts
945                .values()
946                .take(test.attempts.len().saturating_sub(1))
947                .any(|attempt| effective_attempt_status(attempt) != "passed"))
948    {
949        "flaky".into()
950    } else {
951        terminal_status.into()
952    }
953}
954
955fn raw_test_id(raw: &RawTestResult) -> &str {
956    raw.test_id.as_deref().unwrap_or(&raw.test)
957}
958
959pub fn passing_coverage_results(raw_results: &[RawTestResult]) -> Vec<RawTestResult> {
960    let mut attempts: BTreeMap<(String, usize), (BTreeSet<String>, bool)> = BTreeMap::new();
961    for raw in raw_results {
962        let Some(retry) = raw.retry else {
963            continue;
964        };
965        let entry = attempts
966            .entry((raw_test_id(raw).into(), retry))
967            .or_default();
968        if let Some(status) = &raw.status {
969            entry.0.insert(status.clone());
970        }
971        entry.1 |= raw.expected_status.as_deref() == Some("failed");
972    }
973    let mut terminal_retries = BTreeMap::<String, usize>::new();
974    for (test, retry) in attempts.keys() {
975        terminal_retries
976            .entry(test.clone())
977            .and_modify(|value| *value = (*value).max(*retry))
978            .or_insert(*retry);
979    }
980    let accepted = terminal_retries
981        .into_iter()
982        .filter_map(|(test, retry)| {
983            let (statuses, expected_failure) = attempts.get(&(test.clone(), retry))?;
984            (statuses.contains("passed") && !expected_failure).then_some((test, retry))
985        })
986        .collect::<BTreeSet<_>>();
987    raw_results
988        .iter()
989        .filter(|raw| {
990            raw.retry
991                .is_some_and(|retry| accepted.contains(&(raw_test_id(raw).into(), retry)))
992        })
993        .cloned()
994        .collect()
995}
996
997pub fn failed_coverage_results(raw_results: &[RawTestResult]) -> Vec<RawTestResult> {
998    let mut attempts = BTreeMap::<(String, usize), Vec<&RawTestResult>>::new();
999    for raw in raw_results {
1000        if let Some(retry) = raw.retry {
1001            attempts
1002                .entry((raw_test_id(raw).to_owned(), retry))
1003                .or_default()
1004                .push(raw);
1005        }
1006    }
1007    let failed = attempts
1008        .into_iter()
1009        .filter_map(|(identity, records)| {
1010            // Runner reporters carry expected-status semantics while hook
1011            // evidence records often do not. Treat reporter records as the
1012            // authority for the attempt outcome, then retain every companion
1013            // evidence record only after the attempt is classified.
1014            let authoritative = records
1015                .iter()
1016                .copied()
1017                .filter(|raw| raw.expected_status.is_some())
1018                .collect::<Vec<_>>();
1019            let authoritative = if authoritative.is_empty() {
1020                records
1021            } else {
1022                authoritative
1023            };
1024            authoritative
1025                .iter()
1026                .any(
1027                    |raw| match (raw.status.as_deref(), raw.expected_status.as_deref()) {
1028                        (Some("failed"), Some("failed")) => false,
1029                        (Some("passed"), Some("failed")) => true,
1030                        (Some("failed"), _) => true,
1031                        _ => false,
1032                    },
1033                )
1034                .then_some(identity)
1035        })
1036        .collect::<BTreeSet<_>>();
1037    raw_results
1038        .iter()
1039        .filter(|raw| {
1040            raw.retry
1041                .is_some_and(|retry| failed.contains(&(raw_test_id(raw).into(), retry)))
1042        })
1043        .cloned()
1044        .collect()
1045}
1046
1047pub(crate) fn javascript_coverage_model() -> CoverageModelDeclaration {
1048    CoverageModelDeclaration {
1049        language: "javascript".into(),
1050        variant: "masking-short-circuit".into(),
1051        name: "coverage-completeness-v2".into(),
1052        completeness_meaning: "Every obligation in the measured model was observed by at least one existing test; test assertions and product correctness are separate assumptions.".into(),
1053        measured: [
1054            "executable source lines",
1055            "executable statements",
1056            "function entries",
1057            "true and false outcomes of if, ternary, while, do/while, and classic for decisions",
1058            "true and false outcomes of every atomic condition in those decisions",
1059            "masking MC/DC independence for every atomic condition in those decisions",
1060            "short-circuit and right-evaluated selections for &&, ||, and ?? value expressions, including JSX",
1061            "short-circuit and evaluated alternatives for logical assignments and optional chains",
1062            "provided and default-evaluated parameter and destructuring values",
1063            "try success and catch entry",
1064            "zero and entered for-in/for-of loops",
1065            "entered switch cases, defaults, and implicit no-match alternatives",
1066        ]
1067        .into_iter()
1068        .map(str::to_owned)
1069        .collect(),
1070        not_measured: [
1071            "all input values or semantic input partitions",
1072            "all execution paths or ordering/concurrency interleavings",
1073            "destructuring defaults in classic for initializers (reported as blockers when discovered)",
1074            "the internal statements and decisions of runtime-generated eval/Function source",
1075            "mutation score or assertion fault-detection strength",
1076        ]
1077        .into_iter()
1078        .map(str::to_owned)
1079        .collect(),
1080    }
1081}
1082
1083/// One file's counts, derived by the same function that produced the run's
1084/// totals.
1085///
1086/// Every feature that judges a run -- a threshold gate, a changed-line check,
1087/// an export, an HTML report -- has to agree with the summary it sits beside.
1088/// Re-deriving a file's denominator independently is how a gate and a report
1089/// come to disagree about the same run, so this filters the same results and
1090/// calls the same core.
1091pub fn coverage_summary_for_file(
1092    view: &CoverageView,
1093    file: &str,
1094) -> Result<CoverageSummary, ReportError> {
1095    let decisions = view
1096        .decisions
1097        .iter()
1098        .filter(|decision| decision.meta.file == file)
1099        .cloned()
1100        .collect::<Vec<_>>();
1101    let points = view
1102        .points
1103        .iter()
1104        .filter(|point| point.meta.file == file)
1105        .cloned()
1106        .collect::<Vec<_>>();
1107    let branches = view
1108        .branches
1109        .iter()
1110        .filter(|branch| branch.meta.file == file)
1111        .cloned()
1112        .collect::<Vec<_>>();
1113    let lines = view
1114        .lines
1115        .iter()
1116        .filter(|line| line.file == file)
1117        .cloned()
1118        .collect::<Vec<_>>();
1119    summary_for_results(&decisions, &points, &branches, &lines, None)
1120}
1121
1122fn summary_for_results(
1123    decisions: &[DecisionResult],
1124    points: &[PointResult],
1125    branches: &[BranchResult],
1126    lines: &[LineResult],
1127    test_ids: Option<&BTreeSet<String>>,
1128) -> Result<CoverageSummary, ReportError> {
1129    let includes = |tests: &[String], covered: bool| {
1130        test_ids.map_or(covered, |selected| {
1131            tests.iter().any(|test| selected.contains(test))
1132        })
1133    };
1134    let input = CoverageCoreInput {
1135        decisions: decisions
1136            .iter()
1137            .map(|decision| DecisionCoverage {
1138                condition_count: decision.meta.conditions.len(),
1139                vectors: decision
1140                    .vector_observations
1141                    .iter()
1142                    .filter(|observation| includes(&observation.tests, true))
1143                    .map(|observation| observation.vector.clone())
1144                    .collect(),
1145            })
1146            .collect(),
1147        points: points
1148            .iter()
1149            .map(|point| PointCoverage {
1150                kind: point.meta.kind.clone(),
1151                covered: includes(&point.tests, point.covered),
1152            })
1153            .collect(),
1154        branches: branches
1155            .iter()
1156            .map(|branch| BranchCoverage {
1157                kind: branch.meta.kind.clone(),
1158                alternatives: branch
1159                    .alternatives
1160                    .iter()
1161                    .map(|alternative| includes(&alternative.tests, alternative.covered))
1162                    .collect(),
1163            })
1164            .collect(),
1165        // A line every frontend declined carries no coverage question, so it
1166        // is absent from the total rather than counted as uncovered.
1167        lines: lines
1168            .iter()
1169            .filter(|line| line.measured)
1170            .map(|line| includes(&line.tests, line.covered))
1171            .collect(),
1172    };
1173    Ok(analyze_core(&input)?.summary)
1174}
1175
1176/// Recompute every structural coverage metric for an arbitrary set of test,
1177/// setup, and background evidence identities. MC/DC witnesses are rebuilt
1178/// from the selected observations; existing aggregate verdicts are never
1179/// reused.
1180pub fn coverage_summary_for_tests(
1181    view: &CoverageView,
1182    test_ids: &BTreeSet<String>,
1183) -> Result<CoverageSummary, ReportError> {
1184    summary_for_results(
1185        &view.decisions,
1186        &view.points,
1187        &view.branches,
1188        &view.lines,
1189        Some(test_ids),
1190    )
1191}
1192
1193/// Whether a manifest limitation blocks measurement of the denominator, as
1194/// opposed to declaring a boundary of it. Absent means blocking.
1195pub fn blocking_limitation(limitation: &Value) -> bool {
1196    limitation
1197        .get("blocking")
1198        .and_then(Value::as_bool)
1199        .unwrap_or(true)
1200}
1201
1202fn confidence_for(
1203    test_ids: impl IntoIterator<Item = String>,
1204    phase_ids: impl IntoIterator<Item = String>,
1205    explicit_phase_ids: impl IntoIterator<Item = String>,
1206    tests: &HashMap<String, MutableTest>,
1207    phases: &HashMap<String, MutablePhase>,
1208) -> CoverageConfidence {
1209    let test_ids = test_ids.into_iter().collect::<BTreeSet<_>>();
1210    let phase_ids = phase_ids.into_iter().collect::<BTreeSet<_>>();
1211    let explicit_phase_ids = explicit_phase_ids.into_iter().collect::<BTreeSet<_>>();
1212    let provenances = test_ids
1213        .iter()
1214        .filter_map(|id| tests.get(id).map(|test| &test.provenance))
1215        .collect::<Vec<_>>();
1216    let roles = test_ids
1217        .iter()
1218        .filter_map(|id| tests.get(id).map(|test| test.role.as_str()))
1219        .collect::<Vec<_>>();
1220    let phase_kinds = phase_ids
1221        .iter()
1222        .filter_map(|id| phases.get(id).map(|phase| phase.phase.kind.as_str()))
1223        .collect::<Vec<_>>();
1224    let only = |phase_kind: &str, role: &str| {
1225        if phase_kinds.is_empty() {
1226            !roles.is_empty() && roles.iter().all(|value| *value == role)
1227        } else {
1228            phase_kinds.iter().all(|value| *value == phase_kind)
1229        }
1230    };
1231    let has_action = explicit_phase_ids.iter().any(|id| {
1232        phases
1233            .get(id)
1234            .is_some_and(|phase| phase.phase.kind == "action")
1235    });
1236    let level = if test_ids.is_empty() {
1237        "unexecuted"
1238    } else if has_action {
1239        "action"
1240    } else {
1241        "executed"
1242    };
1243    let runners = provenances
1244        .iter()
1245        .map(|provenance| provenance.runner.clone())
1246        .collect::<BTreeSet<_>>();
1247    let kinds = provenances
1248        .iter()
1249        .map(|provenance| provenance.kind.clone())
1250        .collect::<BTreeSet<_>>();
1251    CoverageConfidence {
1252        level: level.into(),
1253        setup_only: only("setup", "setup"),
1254        background_only: only("background", "background"),
1255        asserted: false,
1256        tests: sorted(&test_ids),
1257        asserted_tests: vec![],
1258        runners: sorted(&runners),
1259        e2e: kinds.contains("e2e"),
1260        kinds: sorted(&kinds),
1261    }
1262}
1263
1264fn add_reference(map: &mut HashMap<String, BTreeSet<String>>, id: &str, value: &str) {
1265    map.entry(id.into()).or_default().insert(value.into());
1266}
1267
1268pub fn create_coverage_view(
1269    manifest: &CoverageManifest,
1270    raw_results: &[RawTestResult],
1271    generated_at: &str,
1272) -> Result<CoverageView, ReportError> {
1273    create_coverage_view_with_model(
1274        manifest,
1275        raw_results,
1276        generated_at,
1277        &javascript_coverage_model(),
1278    )
1279}
1280
1281fn create_coverage_view_with_model(
1282    manifest: &CoverageManifest,
1283    raw_results: &[RawTestResult],
1284    generated_at: &str,
1285    coverage_model: &CoverageModelDeclaration,
1286) -> Result<CoverageView, ReportError> {
1287    let mut decision_metadata = manifest.decisions.clone();
1288    let decision_indexes = decision_metadata
1289        .iter()
1290        .enumerate()
1291        .map(|(index, meta)| (meta.id.clone(), index))
1292        .collect::<HashMap<_, _>>();
1293    let manifest_files = manifest
1294        .decisions
1295        .iter()
1296        .map(|meta| meta.file.clone())
1297        .chain(manifest.points.iter().map(|meta| meta.file.clone()))
1298        .chain(manifest.branches.iter().map(|meta| meta.file.clone()))
1299        .chain(
1300            manifest
1301                .scope
1302                .as_ref()
1303                .and_then(|scope| scope.get("entries"))
1304                .and_then(Value::as_array)
1305                .into_iter()
1306                .flatten()
1307                .filter(|entry| entry.get("status").and_then(Value::as_str) == Some("included"))
1308                .filter_map(|entry| entry.get("file").and_then(Value::as_str).map(str::to_owned)),
1309        )
1310        .collect::<BTreeSet<_>>();
1311    let mut vectors_by_decision = HashMap::<String, Vec<MutableObservation>>::new();
1312    let mut vector_indexes = HashMap::<String, HashMap<String, usize>>::new();
1313    let mut tests_by_decision = HashMap::<String, BTreeSet<String>>::new();
1314    let mut tests_by_hit = HashMap::<String, BTreeSet<String>>::new();
1315    let mut tests_by_id = HashMap::<String, MutableTest>::new();
1316    let mut test_order = Vec::<String>::new();
1317    let mut phases_by_id = HashMap::<String, MutablePhase>::new();
1318    let mut phases_by_hit = HashMap::<String, BTreeSet<String>>::new();
1319    let mut explicit_phases_by_hit = HashMap::<String, BTreeSet<String>>::new();
1320
1321    for raw in raw_results {
1322        let id = raw_test_id(raw).to_owned();
1323        if !tests_by_id.contains_key(&id) {
1324            test_order.push(id.clone());
1325            tests_by_id.insert(
1326                id.clone(),
1327                MutableTest {
1328                    id: id.clone(),
1329                    name: raw.test.clone(),
1330                    file: raw.test_file.clone(),
1331                    title: raw.title.clone(),
1332                    retries: raw.retry.into_iter().collect(),
1333                    attempts: BTreeMap::new(),
1334                    unstarted: raw.status.as_deref() == Some("unstarted"),
1335                    runner_reported_flaky: raw.flaky,
1336                    provenance: raw.provenance.clone(),
1337                    role: raw.role.clone(),
1338                    attribution: raw.attribution.clone(),
1339                    hits: BTreeSet::new(),
1340                    decisions: BTreeMap::new(),
1341                },
1342            );
1343        }
1344        let test = tests_by_id.get_mut(&id).expect("test was inserted");
1345        test.unstarted |= raw.status.as_deref() == Some("unstarted");
1346        if let Some(retry) = raw.retry {
1347            test.retries.insert(retry);
1348        }
1349        test.runner_reported_flaky |= raw.flaky;
1350        record_attempt(test, raw);
1351
1352        let mut ordered_phases = raw.phases.clone();
1353        ordered_phases.sort_by_key(|phase| phase.started_at_ms);
1354        for phase in &ordered_phases {
1355            phases_by_id.insert(
1356                phase.id.clone(),
1357                MutablePhase {
1358                    phase: phase.clone(),
1359                    test: id.clone(),
1360                    hits: BTreeSet::new(),
1361                    decisions: BTreeMap::new(),
1362                    browser_events: 0,
1363                    server_events: 0,
1364                    explicit_events: 0,
1365                    inferred_events: 0,
1366                    explicit_browser_events: 0,
1367                    inferred_browser_events: 0,
1368                    explicit_server_events: 0,
1369                    inferred_server_events: 0,
1370                },
1371            );
1372        }
1373
1374        let correlate = |event: &RuntimeEvent| {
1375            event.phase_id.clone().or_else(|| {
1376                ordered_phases
1377                    .iter()
1378                    .take_while(|phase| phase.started_at_ms <= event.timestamp_ms)
1379                    .last()
1380                    .map(|phase| phase.id.clone())
1381            })
1382        };
1383
1384        let snapshots = raw.runtime.iter().chain(&raw.browser);
1385        for snapshot in snapshots {
1386            for decision in &snapshot.decisions {
1387                let Some(index) = decision_indexes.get(&decision.meta.id).copied() else {
1388                    if manifest_files.contains(&decision.meta.file) {
1389                        return Err(ReportError::InvalidServerRecord(format!(
1390                            "decision {} is absent from the frozen manifest",
1391                            decision.meta.id
1392                        )));
1393                    }
1394                    continue;
1395                };
1396                if decision_metadata[index] != decision.meta {
1397                    return Err(ReportError::InvalidServerRecord(format!(
1398                        "decision {} metadata differs from the frozen manifest",
1399                        decision.meta.id
1400                    )));
1401                }
1402                for vector in &decision.vectors {
1403                    let key = vector_key(vector);
1404                    let indexes = vector_indexes.entry(decision.meta.id.clone()).or_default();
1405                    let observations = vectors_by_decision
1406                        .entry(decision.meta.id.clone())
1407                        .or_default();
1408                    let observation_index = *indexes.entry(key.clone()).or_insert_with(|| {
1409                        observations.push(MutableObservation {
1410                            vector: vector.clone(),
1411                            tests: BTreeSet::new(),
1412                            phases: BTreeSet::new(),
1413                            explicit_phases: BTreeSet::new(),
1414                        });
1415                        observations.len() - 1
1416                    });
1417                    observations[observation_index].tests.insert(id.clone());
1418                    tests_by_id
1419                        .get_mut(&id)
1420                        .expect("registered test")
1421                        .decisions
1422                        .entry(decision.meta.id.clone())
1423                        .or_default()
1424                        .insert(vector);
1425                }
1426                if !decision.vectors.is_empty() {
1427                    add_reference(&mut tests_by_decision, &decision.meta.id, &id);
1428                }
1429            }
1430            for hit in &snapshot.hits {
1431                add_reference(&mut tests_by_hit, hit, &id);
1432                tests_by_id
1433                    .get_mut(&id)
1434                    .expect("registered test")
1435                    .hits
1436                    .insert(hit.clone());
1437            }
1438            for event in &snapshot.events {
1439                let explicit = event.phase_id.is_some();
1440                let Some(phase_id) = correlate(event) else {
1441                    continue;
1442                };
1443                let Some(phase) = phases_by_id.get_mut(&phase_id) else {
1444                    continue;
1445                };
1446                if event.environment == "browser" {
1447                    phase.browser_events += 1;
1448                    if explicit {
1449                        phase.explicit_browser_events += 1;
1450                    } else {
1451                        phase.inferred_browser_events += 1;
1452                    }
1453                } else {
1454                    phase.server_events += 1;
1455                    if explicit {
1456                        phase.explicit_server_events += 1;
1457                    } else {
1458                        phase.inferred_server_events += 1;
1459                    }
1460                }
1461                if explicit {
1462                    phase.explicit_events += 1;
1463                } else {
1464                    phase.inferred_events += 1;
1465                }
1466                if event.event_type == "hit" {
1467                    phase.hits.insert(event.id.clone());
1468                    add_reference(&mut phases_by_hit, &event.id, &phase_id);
1469                    if explicit {
1470                        add_reference(&mut explicit_phases_by_hit, &event.id, &phase_id);
1471                    }
1472                } else if event.event_type == "decision" {
1473                    let vector = event
1474                        .vector
1475                        .as_ref()
1476                        .ok_or_else(|| ReportError::InvalidEvent(event.id.clone()))?;
1477                    phase
1478                        .decisions
1479                        .entry(event.id.clone())
1480                        .or_default()
1481                        .insert(vector);
1482                    if let Some(index) = vector_indexes
1483                        .get(&event.id)
1484                        .and_then(|indexes| indexes.get(&vector_key(vector)))
1485                        .copied()
1486                        && let Some(observation) = vectors_by_decision
1487                            .get_mut(&event.id)
1488                            .and_then(|observations| observations.get_mut(index))
1489                    {
1490                        observation.phases.insert(phase_id.clone());
1491                        if explicit {
1492                            observation.explicit_phases.insert(phase_id.clone());
1493                        }
1494                    }
1495                } else {
1496                    return Err(ReportError::InvalidEvent(event.event_type.clone()));
1497                }
1498            }
1499        }
1500
1501        for record in &raw.server {
1502            let (record_id, decision) = if record.record_type == "decision" {
1503                let meta = record
1504                    .meta
1505                    .as_ref()
1506                    .ok_or_else(|| ReportError::InvalidServerRecord("missing meta".into()))?;
1507                let vector = record
1508                    .vector
1509                    .as_ref()
1510                    .ok_or_else(|| ReportError::InvalidServerRecord("missing vector".into()))?;
1511                let Some(index) = decision_indexes.get(&meta.id).copied() else {
1512                    if manifest_files.contains(&meta.file) {
1513                        return Err(ReportError::InvalidServerRecord(format!(
1514                            "decision {} is absent from the frozen manifest",
1515                            meta.id
1516                        )));
1517                    }
1518                    continue;
1519                };
1520                if decision_metadata[index] != *meta {
1521                    return Err(ReportError::InvalidServerRecord(format!(
1522                        "decision {} metadata differs from the frozen manifest",
1523                        meta.id
1524                    )));
1525                }
1526                let key = vector_key(vector);
1527                let indexes = vector_indexes.entry(meta.id.clone()).or_default();
1528                let observations = vectors_by_decision.entry(meta.id.clone()).or_default();
1529                let index = *indexes.entry(key).or_insert_with(|| {
1530                    observations.push(MutableObservation {
1531                        vector: vector.clone(),
1532                        tests: BTreeSet::new(),
1533                        phases: BTreeSet::new(),
1534                        explicit_phases: BTreeSet::new(),
1535                    });
1536                    observations.len() - 1
1537                });
1538                observations[index].tests.insert(id.clone());
1539                tests_by_id
1540                    .get_mut(&id)
1541                    .expect("registered test")
1542                    .decisions
1543                    .entry(meta.id.clone())
1544                    .or_default()
1545                    .insert(vector);
1546                add_reference(&mut tests_by_decision, &meta.id, &id);
1547                (meta.id.clone(), Some(vector.clone()))
1548            } else if record.record_type == "hit" {
1549                let hit = record
1550                    .id
1551                    .as_ref()
1552                    .ok_or_else(|| ReportError::InvalidServerRecord("missing hit id".into()))?;
1553                add_reference(&mut tests_by_hit, hit, &id);
1554                tests_by_id
1555                    .get_mut(&id)
1556                    .expect("registered test")
1557                    .hits
1558                    .insert(hit.clone());
1559                (hit.clone(), None)
1560            } else {
1561                return Err(ReportError::InvalidServerRecord(record.record_type.clone()));
1562            };
1563            let Some(timestamp_ms) = record.timestamp_ms else {
1564                continue;
1565            };
1566            let event = RuntimeEvent {
1567                event_type: record.record_type.clone(),
1568                id: record_id,
1569                vector: decision,
1570                timestamp_ms,
1571                statement_id: record.statement_id.clone(),
1572                phase_id: record.phase_id.clone(),
1573                environment: "server".into(),
1574            };
1575            let explicit = event.phase_id.is_some();
1576            let phase_id = correlate(&event);
1577            let Some(phase_id) = phase_id else { continue };
1578            let Some(phase) = phases_by_id.get_mut(&phase_id) else {
1579                continue;
1580            };
1581            phase.server_events += 1;
1582            if explicit {
1583                phase.explicit_events += 1;
1584                phase.explicit_server_events += 1;
1585            } else {
1586                phase.inferred_events += 1;
1587                phase.inferred_server_events += 1;
1588            }
1589            if event.event_type == "hit" {
1590                phase.hits.insert(event.id.clone());
1591                add_reference(&mut phases_by_hit, &event.id, &phase_id);
1592                if explicit {
1593                    add_reference(&mut explicit_phases_by_hit, &event.id, &phase_id);
1594                }
1595            } else if let Some(vector) = &event.vector {
1596                phase
1597                    .decisions
1598                    .entry(event.id.clone())
1599                    .or_default()
1600                    .insert(vector);
1601                if let Some(index) = vector_indexes
1602                    .get(&event.id)
1603                    .and_then(|indexes| indexes.get(&vector_key(vector)))
1604                    .copied()
1605                    && let Some(observation) = vectors_by_decision
1606                        .get_mut(&event.id)
1607                        .and_then(|observations| observations.get_mut(index))
1608                {
1609                    observation.phases.insert(phase_id.clone());
1610                    if explicit {
1611                        observation.explicit_phases.insert(phase_id.clone());
1612                    }
1613                }
1614            }
1615        }
1616    }
1617
1618    // Legacy confidence fields remain readable for archive compatibility.
1619    // Execution inside/before a passing assertion does not establish that
1620    // an assertion checks a statement. Only the agent-authored map awards it.
1621
1622    decision_metadata.sort_by(|left, right| {
1623        left.file
1624            .cmp(&right.file)
1625            .then(left.line.cmp(&right.line))
1626            .then(left.column.cmp(&right.column))
1627    });
1628    let mut decisions = Vec::with_capacity(decision_metadata.len());
1629    for meta in decision_metadata {
1630        let mutable = vectors_by_decision.remove(&meta.id).unwrap_or_default();
1631        let mut observations = Vec::with_capacity(mutable.len());
1632        for observation in mutable {
1633            let confidence = confidence_for(
1634                sorted(&observation.tests),
1635                sorted(&observation.phases),
1636                sorted(&observation.explicit_phases),
1637                &tests_by_id,
1638                &phases_by_id,
1639            );
1640            observations.push(VectorObservation {
1641                vector: observation.vector,
1642                tests: sorted(&observation.tests),
1643                phases: sorted(&observation.phases),
1644                explicit_phases: sorted(&observation.explicit_phases),
1645                confidence,
1646            });
1647        }
1648        let vectors = observations
1649            .iter()
1650            .map(|observation| observation.vector.clone())
1651            .collect::<Vec<_>>();
1652        let witnesses =
1653            find_witnesses_for_conditions(&vectors, meta.conditions.len()).map_err(|error| {
1654                ReportError::DecisionAnalysis {
1655                    decision_id: meta.id.clone(),
1656                    error,
1657                }
1658            })?;
1659        let mut conditions = Vec::with_capacity(meta.conditions.len());
1660        for (index, source) in meta.conditions.iter().enumerate() {
1661            let witness = witnesses[index].map(|witness| {
1662                [
1663                    vectors[witness.first].clone(),
1664                    vectors[witness.second].clone(),
1665                ]
1666            });
1667            let witness_tests = witnesses[index].map(|witness| {
1668                [
1669                    observations[witness.first].tests.clone(),
1670                    observations[witness.second].tests.clone(),
1671                ]
1672            });
1673            let assertion_covered = false;
1674            conditions.push(ConditionResult {
1675                index,
1676                source: source.clone(),
1677                covered: witness.is_some(),
1678                assertion_covered,
1679                witness,
1680                witness_tests,
1681            });
1682        }
1683        let decision_tests = tests_by_decision.remove(&meta.id).unwrap_or_default();
1684        let confidence = confidence_for(
1685            sorted(&decision_tests),
1686            observations
1687                .iter()
1688                .flat_map(|observation| observation.phases.clone()),
1689            observations
1690                .iter()
1691                .flat_map(|observation| observation.explicit_phases.clone()),
1692            &tests_by_id,
1693            &phases_by_id,
1694        );
1695        decisions.push(DecisionResult {
1696            executed: !vectors.is_empty(),
1697            covered: conditions.iter().all(|condition| condition.covered),
1698            meta,
1699            vectors,
1700            vector_observations: observations,
1701            conditions,
1702            tests: sorted(&decision_tests),
1703            confidence,
1704        });
1705    }
1706
1707    let points = manifest
1708        .points
1709        .iter()
1710        .cloned()
1711        .map(|meta| {
1712            let tests = tests_by_hit.get(&meta.id).cloned().unwrap_or_default();
1713            let phases = phases_by_hit.get(&meta.id).cloned().unwrap_or_default();
1714            let explicit = explicit_phases_by_hit
1715                .get(&meta.id)
1716                .cloned()
1717                .unwrap_or_default();
1718            PointResult {
1719                measured: !manifest.unmeasured.contains(&meta.id),
1720                covered: tests_by_hit.contains_key(&meta.id),
1721                confidence: confidence_for(
1722                    sorted(&tests),
1723                    sorted(&phases),
1724                    sorted(&explicit),
1725                    &tests_by_id,
1726                    &phases_by_id,
1727                ),
1728                meta,
1729                tests: sorted(&tests),
1730                phases: sorted(&phases),
1731            }
1732        })
1733        .collect::<Vec<_>>();
1734
1735    let branches = manifest
1736        .branches
1737        .iter()
1738        .cloned()
1739        .map(|meta| {
1740            let alternatives = meta
1741                .alternatives
1742                .iter()
1743                .map(|alternative| {
1744                    let tests = tests_by_hit
1745                        .get(&alternative.id)
1746                        .cloned()
1747                        .unwrap_or_default();
1748                    let phases = phases_by_hit
1749                        .get(&alternative.id)
1750                        .cloned()
1751                        .unwrap_or_default();
1752                    let explicit = explicit_phases_by_hit
1753                        .get(&alternative.id)
1754                        .cloned()
1755                        .unwrap_or_default();
1756                    AlternativeResult {
1757                        id: alternative.id.clone(),
1758                        label: alternative.label.clone(),
1759                        covered: tests_by_hit.contains_key(&alternative.id),
1760                        tests: sorted(&tests),
1761                        phases: sorted(&phases),
1762                        confidence: confidence_for(
1763                            sorted(&tests),
1764                            sorted(&phases),
1765                            sorted(&explicit),
1766                            &tests_by_id,
1767                            &phases_by_id,
1768                        ),
1769                    }
1770                })
1771                .collect::<Vec<_>>();
1772            BranchResult {
1773                covered: alternatives.iter().all(|alternative| alternative.covered),
1774                meta,
1775                alternatives,
1776            }
1777        })
1778        .collect::<Vec<_>>();
1779
1780    // Obligations the frontend declined to measure leave the covered/uncovered
1781    // denominator entirely. Counting them as uncovered would report a
1782    // measurement gap as a coverage gap — a wrong number, and wrong numbers get
1783    // trusted. They are reported separately instead, alongside the share of
1784    // obligations that were measured exactly.
1785    let declined = manifest.unmeasured.iter().collect::<BTreeSet<_>>();
1786
1787    // Lines are folded from every point, declined ones included, so a file
1788    // Supercov could not measure still has addressable lines to report a
1789    // limitation against. Only what was measured decides the line's state and
1790    // whether it counts.
1791    let mut line_aggregates = BTreeMap::<SourceLine, LineAggregate>::new();
1792    for point in &points {
1793        let aggregate = line_aggregates
1794            .entry(SourceLine {
1795                file: point.meta.file.clone(),
1796                line: point.meta.line,
1797            })
1798            .or_default();
1799        if declined.contains(&point.meta.id) {
1800            continue;
1801        }
1802        aggregate.measured = true;
1803        aggregate.covered |= point.covered;
1804        aggregate.tests.extend(point.tests.clone());
1805        aggregate.phases.extend(point.phases.clone());
1806        aggregate.explicit_phases.extend(
1807            explicit_phases_by_hit
1808                .get(&point.meta.id)
1809                .into_iter()
1810                .flatten()
1811                .cloned(),
1812        );
1813    }
1814    let lines = line_aggregates
1815        .into_iter()
1816        .map(|(location, aggregate)| {
1817            let LineAggregate {
1818                covered,
1819                measured,
1820                tests: test_ids,
1821                phases: phase_ids,
1822                explicit_phases: explicit_ids,
1823            } = aggregate;
1824            let provenances = test_ids
1825                .iter()
1826                .filter_map(|id| tests_by_id.get(id).map(|test| &test.provenance))
1827                .collect::<Vec<_>>();
1828            let runners = provenances
1829                .iter()
1830                .map(|provenance| provenance.runner.clone())
1831                .collect::<BTreeSet<_>>();
1832            let kinds = provenances
1833                .iter()
1834                .map(|provenance| provenance.kind.clone())
1835                .collect::<BTreeSet<_>>();
1836            LineResult {
1837                file: location.file,
1838                line: location.line,
1839                covered,
1840                measured,
1841                tests: sorted(&test_ids),
1842                runners: sorted(&runners),
1843                exclusive_kind: (kinds.len() == 1).then(|| kinds.first().unwrap().clone()),
1844                phases: sorted(&phase_ids),
1845                confidence: confidence_for(
1846                    sorted(&test_ids),
1847                    sorted(&phase_ids),
1848                    sorted(&explicit_ids),
1849                    &tests_by_id,
1850                    &phases_by_id,
1851                ),
1852                kinds: sorted(&kinds),
1853            }
1854        })
1855        .collect::<Vec<_>>();
1856
1857    let point_locations = manifest
1858        .points
1859        .iter()
1860        .map(|point| {
1861            (
1862                point.id.clone(),
1863                SourceLine {
1864                    file: point.file.clone(),
1865                    line: point.line,
1866                },
1867            )
1868        })
1869        .collect::<HashMap<_, _>>();
1870    test_order.sort_by(|left, right| tests_by_id[left].name.cmp(&tests_by_id[right].name));
1871    let tests = test_order
1872        .into_iter()
1873        .map(|id| {
1874            let test = tests_by_id.get(&id).expect("test order references test");
1875            let lines = test
1876                .hits
1877                .iter()
1878                .filter_map(|hit| point_locations.get(hit).cloned())
1879                .collect::<BTreeSet<_>>();
1880            TestCoverageResult {
1881                id: test.id.clone(),
1882                name: test.name.clone(),
1883                file: test.file.clone(),
1884                title: test.title.clone(),
1885                retries: sorted(&test.retries),
1886                attempts: test.attempts.values().cloned().collect(),
1887                outcome: test_outcome(test),
1888                provenance: test.provenance.clone(),
1889                role: test.role.clone(),
1890                attribution: test.attribution.clone(),
1891                hits: sorted(&test.hits),
1892                decisions: test
1893                    .decisions
1894                    .iter()
1895                    .map(|(id, vectors)| TestDecisionResult {
1896                        id: id.clone(),
1897                        vectors: vectors.values.clone(),
1898                    })
1899                    .collect(),
1900                lines: lines.into_iter().collect(),
1901            }
1902        })
1903        .collect::<Vec<_>>();
1904
1905    let mut test_files = BTreeMap::<
1906        String,
1907        (
1908            BTreeSet<String>,
1909            BTreeSet<String>,
1910            BTreeSet<String>,
1911            BTreeSet<SourceLine>,
1912        ),
1913    >::new();
1914    for test in &tests {
1915        let aggregate = test_files
1916            .entry(
1917                test.file
1918                    .clone()
1919                    .unwrap_or_else(|| "(unknown test file)".into()),
1920            )
1921            .or_default();
1922        aggregate.0.insert(test.id.clone());
1923        aggregate.1.insert(test.provenance.runner.clone());
1924        aggregate.2.insert(test.provenance.kind.clone());
1925        aggregate.3.extend(test.lines.clone());
1926    }
1927    let test_files = test_files
1928        .into_iter()
1929        .map(|(file, (tests, runners, kinds, lines))| TestFileResult {
1930            file,
1931            tests: sorted(&tests),
1932            runners: sorted(&runners),
1933            kinds: sorted(&kinds),
1934            lines: lines.into_iter().collect(),
1935        })
1936        .collect::<Vec<_>>();
1937
1938    let mut phases = phases_by_id.values().cloned().collect::<Vec<_>>();
1939    phases.sort_by(|left, right| {
1940        left.phase
1941            .started_at_ms
1942            .cmp(&right.phase.started_at_ms)
1943            .then(left.phase.id.cmp(&right.phase.id))
1944    });
1945    let phases = phases
1946        .into_iter()
1947        .map(|phase| {
1948            let lines = phase
1949                .hits
1950                .iter()
1951                .filter_map(|hit| point_locations.get(hit).cloned())
1952                .collect::<BTreeSet<_>>();
1953            PhaseResult {
1954                phase: phase.phase,
1955                test: phase.test,
1956                hits: sorted(&phase.hits),
1957                decisions: phase
1958                    .decisions
1959                    .into_iter()
1960                    .map(|(id, vectors)| TestDecisionResult {
1961                        id,
1962                        vectors: vectors.values,
1963                    })
1964                    .collect(),
1965                lines: lines.into_iter().collect(),
1966                browser_events: phase.browser_events,
1967                server_events: phase.server_events,
1968                explicit_events: phase.explicit_events,
1969                inferred_events: phase.inferred_events,
1970                explicit_browser_events: phase.explicit_browser_events,
1971                inferred_browser_events: phase.inferred_browser_events,
1972                explicit_server_events: phase.explicit_server_events,
1973                inferred_server_events: phase.inferred_server_events,
1974            }
1975        })
1976        .collect::<Vec<_>>();
1977
1978    let total_obligations = decisions.len() + points.len() + branches.len();
1979    let (decisions, points, branches) = if declined.is_empty() {
1980        (decisions, points, branches)
1981    } else {
1982        (
1983            decisions
1984                .into_iter()
1985                .filter(|result| !declined.contains(&result.meta.id))
1986                .collect::<Vec<_>>(),
1987            points
1988                .into_iter()
1989                .filter(|result| !declined.contains(&result.meta.id))
1990                .collect::<Vec<_>>(),
1991            branches
1992                .into_iter()
1993                .filter(|result| !declined.contains(&result.meta.id))
1994                .collect::<Vec<_>>(),
1995        )
1996    };
1997    let measured_obligations = decisions.len() + points.len() + branches.len();
1998    let mut summary = summary_for_results(&decisions, &points, &branches, &lines, None)?;
1999    if total_obligations > measured_obligations {
2000        summary.unmeasured_obligations = Some(total_obligations - measured_obligations);
2001        summary.exact_fraction_pct = Some(if total_obligations == 0 {
2002            100.0
2003        } else {
2004            (measured_obligations as f64) * 100.0 / (total_obligations as f64)
2005        });
2006    }
2007    // A limitation that says `"blocking": false` is a declared boundary of
2008    // the denominator -- a macro the compiler expands, a const context no
2009    // probe can run in -- not a failure to measure what is inside it. Only a
2010    // blocking one makes the run incomplete. A limitation that says nothing
2011    // is blocking, so a frontend that has not been taught the difference
2012    // keeps its behaviour.
2013    if manifest.limitations.iter().any(blocking_limitation) {
2014        summary.coverage_complete = false;
2015        summary.completeness_blocked = Some(true);
2016    }
2017
2018    let dimension_coverage = |field: &str| -> Result<Vec<DimensionCoverage>, ReportError> {
2019        let values = tests
2020            .iter()
2021            .map(|test| {
2022                if field == "kind" {
2023                    test.provenance.kind.clone()
2024                } else {
2025                    test.provenance.runner.clone()
2026                }
2027            })
2028            .collect::<BTreeSet<_>>();
2029        values
2030            .into_iter()
2031            .map(|value| {
2032                let in_dimension = |test: &&TestCoverageResult| {
2033                    if field == "kind" {
2034                        test.provenance.kind == value
2035                    } else {
2036                        test.provenance.runner == value
2037                    }
2038                };
2039                // Coverage is summarised over the tests that have coverage of
2040                // their own. A Go test that called `t.Parallel()` reached real
2041                // code and contributes no hits, because nothing can say which
2042                // code -- so counting it here reported a package covered
2043                // entirely by parallel tests as covered 0%, which is a wrong
2044                // number rather than a missing one.
2045                let selected = tests
2046                    .iter()
2047                    .filter(in_dimension)
2048                    .filter(|test| coverage_is_its_own(&test.attribution))
2049                    .map(|test| test.id.clone())
2050                    .collect::<BTreeSet<_>>();
2051                let counted = tests
2052                    .iter()
2053                    .filter(in_dimension)
2054                    .map(|test| test.id.clone())
2055                    .collect::<BTreeSet<_>>();
2056                Ok(DimensionCoverage {
2057                    kind: (field == "kind").then(|| value.clone()),
2058                    runner: (field == "runner").then(|| value.clone()),
2059                    // Counted whether or not anything could be attributed to
2060                    // them: they ran.
2061                    tests: tests
2062                        .iter()
2063                        .filter(|test| counted.contains(&test.id) && test.role == "test")
2064                        .count(),
2065                    setups: tests
2066                        .iter()
2067                        .filter(|test| counted.contains(&test.id) && test.role == "setup")
2068                        .count(),
2069                    attributed: tests
2070                        .iter()
2071                        .filter(|test| selected.contains(&test.id) && test.role == "test")
2072                        .count(),
2073                    summary: summary_for_results(
2074                        &decisions,
2075                        &points,
2076                        &branches,
2077                        &lines,
2078                        Some(&selected),
2079                    )?,
2080                })
2081            })
2082            .collect()
2083    };
2084
2085    Ok(CoverageView {
2086        generated_at: generated_at.into(),
2087        variant: coverage_model.variant.clone(),
2088        scope: manifest.scope.clone(),
2089        model: CoverageModel {
2090            language: coverage_model.language.clone(),
2091            name: coverage_model.name.clone(),
2092            completeness_meaning: coverage_model.completeness_meaning.clone(),
2093            measured: coverage_model.measured.clone(),
2094            not_measured: coverage_model.not_measured.clone(),
2095        },
2096        integrity: None,
2097        limitations: manifest.limitations.clone(),
2098        transport: None,
2099        summary,
2100        coverage_by_kind: dimension_coverage("kind")?,
2101        coverage_by_runner: dimension_coverage("runner")?,
2102        decisions,
2103        points,
2104        branches,
2105        tests,
2106        test_files,
2107        phases,
2108        lines,
2109    })
2110}
2111
2112pub fn analyze_coverage_results(
2113    request: &CoverageReportRequest,
2114) -> Result<CoverageReport, ReportError> {
2115    if let Some(scope) = request
2116        .raw_results
2117        .iter()
2118        .filter_map(|raw| raw.scope.as_ref())
2119        .find(|scope| scope.run_id != request.run_id)
2120    {
2121        return Err(ReportError::ScopeMismatch {
2122            expected: request.run_id.clone(),
2123            actual: scope.run_id.clone(),
2124        });
2125    }
2126    if request.raw_results.is_empty() {
2127        return Err(ReportError::NoEvidence(request.run_id.clone()));
2128    }
2129    let default_model = javascript_coverage_model();
2130    let coverage_model = request.coverage_model.as_ref().unwrap_or(&default_model);
2131    PersistedCoverageModel::from_declaration(coverage_model).map_err(|reason| {
2132        ReportError::InvalidJson {
2133            path: "coverage-model.json".into(),
2134            reason: reason.into(),
2135        }
2136    })?;
2137    let view = create_coverage_view_with_model(
2138        &request.manifest,
2139        &request.raw_results,
2140        &request.generated_at,
2141        coverage_model,
2142    )?;
2143    let passed = create_coverage_view_with_model(
2144        &request.manifest,
2145        &passing_coverage_results(&request.raw_results),
2146        &request.generated_at,
2147        coverage_model,
2148    )?;
2149    let failed = create_coverage_view_with_model(
2150        &request.manifest,
2151        &failed_coverage_results(&request.raw_results),
2152        &request.generated_at,
2153        coverage_model,
2154    )?;
2155    let execution = match request.test_exit_code {
2156        ExitCodeInput::Missing => None,
2157        ExitCodeInput::Present(test_exit_code) => Some(ExecutionResult {
2158            valid: test_exit_code == Some(0),
2159            test_exit_code,
2160        }),
2161    };
2162    let mut view = view;
2163    let mut passed = passed;
2164    let mut failed = failed;
2165    if let Some(integrity) = &request.integrity {
2166        view.integrity = Some(integrity.clone());
2167        passed.integrity = Some(integrity.clone());
2168        failed.integrity = Some(integrity.clone());
2169    }
2170    Ok(CoverageReport {
2171        view,
2172        execution,
2173        filters: CoverageFilters { passed, failed },
2174    })
2175}
2176
2177fn parse_entry<T: for<'de> Deserialize<'de>>(
2178    entry: &EvidenceArchiveEntry,
2179) -> Result<T, ReportError> {
2180    serde_json::from_slice(&entry.contents).map_err(|error| ReportError::InvalidJson {
2181        path: entry.path.clone(),
2182        reason: error.to_string(),
2183    })
2184}
2185
2186fn parse_json_lines<'a, T: for<'de> Deserialize<'de>>(
2187    entries: impl Iterator<Item = &'a EvidenceArchiveEntry>,
2188) -> Result<Vec<T>, ReportError> {
2189    let mut records = Vec::new();
2190    for entry in entries {
2191        let Some(contents) = entry.contents.strip_suffix(b"\n") else {
2192            return Err(ReportError::InvalidJson {
2193                path: entry.path.clone(),
2194                reason: "recognized JSONL evidence must end with a newline".into(),
2195            });
2196        };
2197        if contents.is_empty() {
2198            return Err(ReportError::InvalidJson {
2199                path: entry.path.clone(),
2200                reason: "recognized JSONL evidence must contain at least one record".into(),
2201            });
2202        }
2203        for (index, line) in contents.split(|byte| *byte == b'\n').enumerate() {
2204            if line.is_empty() {
2205                return Err(ReportError::InvalidJson {
2206                    path: entry.path.clone(),
2207                    reason: format!("blank JSONL record at line {}", index + 1),
2208                });
2209            }
2210            records.push(serde_json::from_slice(line).map_err(|error| {
2211                ReportError::InvalidJson {
2212                    path: entry.path.clone(),
2213                    reason: format!("invalid JSONL record at line {}: {error}", index + 1),
2214                }
2215            })?);
2216        }
2217    }
2218    Ok(records)
2219}
2220
2221/// Server evidence is appended by application processes Supercov does not
2222/// control — including pool VMs restored from one snapshot, whose clones can
2223/// tear a shared shard. A torn line is one lost record, not a lost run: it is
2224/// skipped and counted, and the report says so through the existing
2225/// CORRUPT_EVIDENCE_RECORDS diagnostic and the blocking-limitation total.
2226struct TolerantJsonLines<T> {
2227    records: Vec<T>,
2228    corrupt_records: usize,
2229    corrupt_files: usize,
2230}
2231
2232fn parse_server_json_lines<'a, T: for<'de> Deserialize<'de>>(
2233    entries: impl Iterator<Item = &'a EvidenceArchiveEntry>,
2234) -> TolerantJsonLines<T> {
2235    let mut parsed = TolerantJsonLines {
2236        records: Vec::new(),
2237        corrupt_records: 0,
2238        corrupt_files: 0,
2239    };
2240    for entry in entries {
2241        let mut corrupt_here = 0;
2242        for line in entry.contents.split(|byte| *byte == b'\n') {
2243            if line.is_empty() {
2244                continue;
2245            }
2246            match serde_json::from_slice(line) {
2247                Ok(record) => parsed.records.push(record),
2248                Err(_) => corrupt_here += 1,
2249            }
2250        }
2251        if corrupt_here > 0 {
2252            parsed.corrupt_records += corrupt_here;
2253            parsed.corrupt_files += 1;
2254        }
2255    }
2256    parsed
2257}
2258
2259fn is_mcdc_result(path: &str) -> bool {
2260    path == "mcdc.json" || path.ends_with("/mcdc.json")
2261}
2262
2263fn is_mcdc_journal(path: &str) -> bool {
2264    path == "mcdc.jsonl" || path.ends_with(".mcdc.jsonl")
2265}
2266
2267fn validate_rust_compiler_scope(manifest: &CoverageManifest) -> Result<(), ReportError> {
2268    let scope = manifest
2269        .scope
2270        .as_ref()
2271        .and_then(Value::as_object)
2272        .ok_or_else(|| ReportError::InvalidArchive("missing Rust compiler source scope".into()))?;
2273    let mut expected = BTreeSet::from([
2274        "crate",
2275        "language",
2276        "measurementComplete",
2277        "model",
2278        "sourceFingerprint",
2279    ]);
2280    // Historical experimental metadata is ignored, never used for credit.
2281    if scope.contains_key("assertionIdentities") {
2282        expected.insert("assertionIdentities");
2283    }
2284    if scope.keys().map(String::as_str).collect::<BTreeSet<_>>() != expected
2285        || scope.get("language").and_then(Value::as_str) != Some("rust")
2286        || scope.get("model").and_then(Value::as_str) != Some("rust-source-v1")
2287        || scope
2288            .get("crate")
2289            .and_then(Value::as_str)
2290            .is_none_or(str::is_empty)
2291        || !scope
2292            .get("measurementComplete")
2293            .is_some_and(Value::is_boolean)
2294    {
2295        return Err(ReportError::InvalidArchive(
2296            "malformed Rust compiler source scope".into(),
2297        ));
2298    }
2299    let fingerprint = scope
2300        .get("sourceFingerprint")
2301        .and_then(Value::as_object)
2302        .ok_or_else(|| {
2303            ReportError::InvalidArchive("missing Rust compiler source fingerprint".into())
2304        })?;
2305    let expected_fingerprint = BTreeSet::from(["algorithm", "digest", "files", "generatedFiles"]);
2306    let digest = fingerprint.get("digest").and_then(Value::as_str);
2307    let files = fingerprint.get("files").and_then(Value::as_u64);
2308    let generated = fingerprint.get("generatedFiles").and_then(Value::as_u64);
2309    if fingerprint
2310        .keys()
2311        .map(String::as_str)
2312        .collect::<BTreeSet<_>>()
2313        != expected_fingerprint
2314        || fingerprint.get("algorithm").and_then(Value::as_str) != Some("sha256")
2315        || !digest.is_some_and(|digest| {
2316            digest.len() == 64
2317                && digest
2318                    .bytes()
2319                    .all(|byte| byte.is_ascii_digit() || (b'a'..=b'f').contains(&byte))
2320        })
2321        || files.is_none_or(|files| files == 0)
2322        || generated
2323            .zip(files)
2324            .is_none_or(|(generated, files)| generated > files)
2325    {
2326        return Err(ReportError::InvalidArchive(
2327            "malformed Rust compiler source fingerprint".into(),
2328        ));
2329    }
2330    Ok(())
2331}
2332
2333pub fn analyze_coverage_archive(
2334    request: &ArchiveReportRequest,
2335) -> Result<CoverageReport, ReportError> {
2336    let entries = read_archive(Path::new(&request.archive_path))
2337        .map_err(|error| ReportError::InvalidArchive(error.to_string()))?;
2338    let manifest = entries
2339        .iter()
2340        .find(|entry| entry.path == "manifest.json")
2341        .ok_or(ReportError::MissingManifest)
2342        .and_then(parse_entry::<CoverageManifest>)?;
2343    let mut raw_results = entries
2344        .iter()
2345        .filter(|entry| is_mcdc_result(&entry.path))
2346        .map(parse_entry::<RawTestResult>)
2347        .collect::<Result<Vec<_>, _>>()?;
2348    let journal_results = parse_json_lines::<RawTestResult>(
2349        entries.iter().filter(|entry| is_mcdc_journal(&entry.path)),
2350    )?;
2351    raw_results.extend(journal_results);
2352
2353    let scoped = parse_server_json_lines::<ServerRecord>(entries.iter().filter(|entry| {
2354        entry.path.starts_with("server/")
2355            && !entry.path.starts_with("server/background/")
2356            && entry.path.ends_with(".jsonl")
2357    }));
2358    let scoped_records = scoped.records;
2359    for record in &scoped_records {
2360        let Some(scope) = &record.scope else { continue };
2361        let Some(raw) = raw_results
2362            .iter_mut()
2363            .find(|raw| raw.scope.as_ref() == Some(scope))
2364        else {
2365            continue;
2366        };
2367        if !raw.server.contains(record) {
2368            raw.server.push(record.clone());
2369        }
2370    }
2371
2372    let background = parse_server_json_lines::<ServerRecord>(entries.iter().filter(|entry| {
2373        entry.path.starts_with("server/background/") && entry.path.ends_with(".jsonl")
2374    }));
2375    let background_records = background.records;
2376    if !background_records.is_empty() {
2377        raw_results.push(RawTestResult {
2378            test_id: Some(format!("background:{}", request.run_id)),
2379            scope: None,
2380            test: "Background / unattributed".into(),
2381            test_file: None,
2382            title: Some("Background / unattributed".into()),
2383            retry: None,
2384            status: Some("unknown".into()),
2385            expected_status: None,
2386            flaky: false,
2387            provenance: TestProvenance {
2388                runner: "background".into(),
2389                kind: "background".into(),
2390                project: None,
2391                source: "explicit".into(),
2392            },
2393            role: "background".into(),
2394            attribution: ATTRIBUTION_EXACT.into(),
2395            phases: vec![],
2396            runtime: vec![],
2397            browser: vec![],
2398            server: background_records.clone(),
2399        });
2400    }
2401
2402    // Execution traces are appended by the launch observer inside application
2403    // processes, so they share the clone hazard of server evidence.
2404    let execution =
2405        parse_server_json_lines::<ExecutionTraceEvent>(entries.iter().filter(|entry| {
2406            entry.path.starts_with("execution.") && entry.path.ends_with(".jsonl")
2407        }));
2408    let execution_events = execution.records;
2409    let count_event = |name: &str| {
2410        execution_events
2411            .iter()
2412            .filter(|event| event.kind() == name)
2413            .count()
2414    };
2415    let transport = TransportStats {
2416        processes: count_event("process"),
2417        child_launches: count_event("child-launch"),
2418        remote_launches: count_event("remote-launch"),
2419        workspace_capabilities: count_event("workspace-capability"),
2420        scoped_server_records: scoped_records.len(),
2421        background_server_records: background_records.len(),
2422        corrupt_records: scoped.corrupt_records
2423            + background.corrupt_records
2424            + execution.corrupt_records,
2425        corrupt_files: scoped.corrupt_files + background.corrupt_files + execution.corrupt_files,
2426    };
2427    let frontend = entries
2428        .iter()
2429        .find(|entry| entry.path == "frontend.json")
2430        .ok_or_else(|| ReportError::InvalidArchive("missing frontend.json".into()))
2431        .and_then(parse_entry::<FrontendRunDeclaration>)?;
2432    let persisted = entries
2433        .iter()
2434        .find(|entry| entry.path == "coverage-model.json")
2435        .ok_or_else(|| ReportError::InvalidArchive("missing coverage-model.json".into()))
2436        .and_then(parse_entry::<PersistedCoverageModel>)?;
2437    let coverage_model =
2438        persisted
2439            .into_declaration()
2440            .map_err(|reason| ReportError::InvalidJson {
2441                path: "coverage-model.json".into(),
2442                reason: reason.into(),
2443            })?;
2444    if frontend.language != coverage_model.language {
2445        return Err(ReportError::InvalidArchive(format!(
2446            "frontend language {} differs from coverage model language {}",
2447            frontend.language, coverage_model.language
2448        )));
2449    }
2450    if frontend.frontend_version == "rust-compiler-v1" {
2451        validate_rust_compiler_scope(&manifest)?;
2452    }
2453    let normalized = CoverageReportRequest {
2454        run_id: request.run_id.clone(),
2455        manifest,
2456        raw_results,
2457        generated_at: request.generated_at.clone(),
2458        coverage_model: Some(coverage_model),
2459        integrity: request.integrity.clone(),
2460        test_exit_code: request.test_exit_code.clone(),
2461    };
2462    let mut report = crate::frontend_protocol::analyze_frontend_results(&frontend, &normalized)
2463        .map_err(|error| ReportError::InvalidArchive(error.to_string()))?;
2464    report.view.transport = Some(transport.clone());
2465    report.filters.passed.transport = Some(transport.clone());
2466    report.filters.failed.transport = Some(transport);
2467    Ok(report)
2468}
2469
2470#[cfg(test)]
2471mod tests {
2472    use std::{
2473        fs,
2474        sync::atomic::{AtomicU64, Ordering},
2475        time::SystemTime,
2476    };
2477
2478    use crate::evidence_archive::{EvidenceArchiveEntry, write_archive};
2479
2480    use super::*;
2481
2482    static ARCHIVE_ID: AtomicU64 = AtomicU64::new(0);
2483
2484    fn point(id: &str, line: usize) -> PointMeta {
2485        PointMeta {
2486            id: id.into(),
2487            kind: PointKind::Statement,
2488            file: "src/app.js".into(),
2489            line,
2490            column: 0,
2491            source: "work();".into(),
2492            label: None,
2493        }
2494    }
2495
2496    fn raw(id: &str, retry: usize, status: &str, hits: &[&str]) -> RawTestResult {
2497        RawTestResult {
2498            test_id: Some(id.into()),
2499            scope: None,
2500            test: id.into(),
2501            test_file: Some("tests/app.test.js".into()),
2502            title: None,
2503            retry: Some(retry),
2504            status: Some(status.into()),
2505            expected_status: None,
2506            flaky: false,
2507            provenance: TestProvenance {
2508                runner: "node:test".into(),
2509                kind: "unit".into(),
2510                project: None,
2511                source: "runner-default".into(),
2512            },
2513            role: "test".into(),
2514            attribution: crate::coverage_report::ATTRIBUTION_EXACT.into(),
2515            phases: vec![],
2516            runtime: vec![RuntimeSnapshot {
2517                decisions: vec![],
2518                hits: hits.iter().map(|hit| (*hit).into()).collect(),
2519                events: vec![],
2520                logicals: vec![],
2521            }],
2522            browser: vec![],
2523            server: vec![],
2524        }
2525    }
2526
2527    #[test]
2528    fn declined_obligations_are_unmeasured_never_uncovered() {
2529        // Two statements, neither executed. One of them Supercov declined to
2530        // measure. The declined one must not appear as a coverage gap: it
2531        // leaves the denominator and is reported as unmeasured instead.
2532        let point = |id: &str, line: usize| PointMeta {
2533            id: id.into(),
2534            kind: PointKind::Statement,
2535            file: "src/app.js".into(),
2536            line,
2537            column: 0,
2538            source: "work();".into(),
2539            label: None,
2540        };
2541        let measured_only = CoverageManifest {
2542            decisions: vec![],
2543            points: vec![point("measured", 1)],
2544            branches: vec![],
2545            limitations: vec![],
2546            unmeasured: Vec::new(),
2547            scope: None,
2548        };
2549        let with_declined = CoverageManifest {
2550            decisions: vec![],
2551            points: vec![point("measured", 1), point("declined", 2)],
2552            branches: vec![],
2553            limitations: vec![],
2554            unmeasured: vec!["declined".into()],
2555            scope: None,
2556        };
2557
2558        let baseline =
2559            create_coverage_view(&measured_only, &[raw("test", 0, "passed", &[])], "time").unwrap();
2560        let view =
2561            create_coverage_view(&with_declined, &[raw("test", 0, "passed", &[])], "time").unwrap();
2562
2563        // The declined statement never inflates the uncovered count.
2564        assert_eq!(
2565            view.summary.statements.total, baseline.summary.statements.total,
2566            "a declined obligation stayed in the covered/uncovered denominator"
2567        );
2568        assert_eq!(view.summary.unmeasured_obligations, Some(1));
2569        assert_eq!(view.summary.exact_fraction_pct, Some(50.0));
2570
2571        // Its line leaves the line total too, but not the report: the line is
2572        // still there to hang a limitation on, and it is neither covered nor
2573        // uncovered.
2574        assert_eq!(
2575            view.summary.lines.total, baseline.summary.lines.total,
2576            "a declined obligation kept its line in the line denominator"
2577        );
2578        let declined_line = view
2579            .lines
2580            .iter()
2581            .find(|line| line.line == 2)
2582            .expect("the declined line stays addressable");
2583        assert!(!declined_line.measured);
2584        assert!(!declined_line.covered);
2585
2586        // A fully measured run keeps its previous output exactly: the new
2587        // fields are absent, not zero, so existing consumers see no change.
2588        assert_eq!(baseline.summary.unmeasured_obligations, None);
2589        assert_eq!(baseline.summary.exact_fraction_pct, None);
2590        let encoded = serde_json::to_string(&baseline.summary).unwrap();
2591        assert!(
2592            !encoded.contains("unmeasured") && !encoded.contains("exactFraction"),
2593            "a fully measured summary must serialize unchanged: {encoded}"
2594        );
2595    }
2596
2597    #[test]
2598    fn a_declined_line_that_executed_is_neither_covered_nor_uncovered() {
2599        // The subtle case: the statement ran, but Supercov could not measure
2600        // it, so its line has no coverage question to answer. Counting it as
2601        // covered would inflate the ratio with a line nothing verified.
2602        let point = |id: &str| PointMeta {
2603            id: id.into(),
2604            kind: PointKind::Statement,
2605            file: "src/app.js".into(),
2606            line: 1,
2607            column: 0,
2608            source: "work();".into(),
2609            label: None,
2610        };
2611        let manifest = CoverageManifest {
2612            decisions: vec![],
2613            points: vec![point("declined")],
2614            branches: vec![],
2615            limitations: vec![],
2616            unmeasured: vec!["declined".into()],
2617            scope: None,
2618        };
2619        let view = create_coverage_view(
2620            &manifest,
2621            &[raw("test", 0, "passed", &["declined"])],
2622            "time",
2623        )
2624        .unwrap();
2625
2626        assert_eq!(view.summary.lines.total, 0);
2627        assert_eq!(view.summary.lines.covered, 0);
2628        let line = view.lines.first().expect("the line stays addressable");
2629        assert!(!line.measured);
2630        assert!(!line.covered, "an unmeasured line is not covered either");
2631    }
2632
2633    #[test]
2634    fn report_retains_unexecuted_manifest_conditions() {
2635        let manifest = CoverageManifest {
2636            unmeasured: Vec::new(),
2637            decisions: vec![DecisionMeta {
2638                id: "decision".into(),
2639                file: "src/app.js".into(),
2640                line: 1,
2641                column: 0,
2642                source: "left && right".into(),
2643                conditions: vec!["left".into(), "right".into()],
2644                kind: "if".into(),
2645            }],
2646            points: vec![],
2647            branches: vec![],
2648            limitations: vec![],
2649            scope: None,
2650        };
2651        let view =
2652            create_coverage_view(&manifest, &[raw("test", 0, "passed", &[])], "time").unwrap();
2653        assert_eq!(view.summary.conditions, 2);
2654        assert_eq!(view.summary.covered_conditions, 0);
2655        assert_eq!(view.decisions[0].conditions.len(), 2);
2656    }
2657
2658    #[test]
2659    fn frozen_manifest_ignores_out_of_scope_synthetic_decisions() {
2660        let manifest = CoverageManifest {
2661            unmeasured: Vec::new(),
2662            decisions: vec![DecisionMeta {
2663                id: "application-decision".into(),
2664                file: "src/app.js".into(),
2665                line: 1,
2666                column: 0,
2667                source: "left && right".into(),
2668                conditions: vec!["left".into(), "right".into()],
2669                kind: "if".into(),
2670            }],
2671            points: vec![],
2672            branches: vec![],
2673            limitations: vec![],
2674            scope: None,
2675        };
2676        let mut attempt = raw("test", 0, "passed", &[]);
2677        attempt.runtime[0].decisions.push(DecisionSnapshot {
2678            meta: DecisionMeta {
2679                id: "synthetic-fixture".into(),
2680                file: "fixtures/generated.js".into(),
2681                line: 1,
2682                column: 0,
2683                source: "a && b && c".into(),
2684                conditions: vec!["a".into(), "b".into(), "c".into()],
2685                kind: "if".into(),
2686            },
2687            vectors: vec![McdcVector {
2688                values: vec![Some(true), Some(true), Some(true)],
2689                outcome: true,
2690            }],
2691        });
2692
2693        let view = create_coverage_view(&manifest, &[attempt], "time").unwrap();
2694        assert_eq!(view.decisions.len(), 1);
2695        assert_eq!(view.decisions[0].meta.id, "application-decision");
2696        assert!(view.decisions[0].vectors.is_empty());
2697    }
2698
2699    #[test]
2700    fn frozen_manifest_rejects_in_scope_unknown_or_changed_decisions() {
2701        let expected = DecisionMeta {
2702            id: "application-decision".into(),
2703            file: "src/app.js".into(),
2704            line: 1,
2705            column: 0,
2706            source: "left && right".into(),
2707            conditions: vec!["left".into(), "right".into()],
2708            kind: "if".into(),
2709        };
2710        let manifest = CoverageManifest {
2711            unmeasured: Vec::new(),
2712            decisions: vec![expected.clone()],
2713            points: vec![],
2714            branches: vec![],
2715            limitations: vec![],
2716            scope: Some(serde_json::json!({
2717                "entries": [{ "file": "src/empty.js", "status": "included" }]
2718            })),
2719        };
2720        let vector = McdcVector {
2721            values: vec![Some(true), Some(true)],
2722            outcome: true,
2723        };
2724
2725        let mut unknown = raw("test", 0, "passed", &[]);
2726        unknown.runtime[0].decisions.push(DecisionSnapshot {
2727            meta: DecisionMeta {
2728                id: "unknown-decision".into(),
2729                file: "src/empty.js".into(),
2730                ..expected.clone()
2731            },
2732            vectors: vec![vector.clone()],
2733        });
2734        assert!(matches!(
2735            create_coverage_view(&manifest, &[unknown], "time"),
2736            Err(ReportError::InvalidServerRecord(reason))
2737                if reason.contains("absent from the frozen manifest")
2738        ));
2739
2740        let mut changed = raw("test", 0, "passed", &[]);
2741        changed.runtime[0].decisions.push(DecisionSnapshot {
2742            meta: DecisionMeta {
2743                source: "left || right".into(),
2744                ..expected
2745            },
2746            vectors: vec![vector],
2747        });
2748        assert!(matches!(
2749            create_coverage_view(&manifest, &[changed], "time"),
2750            Err(ReportError::InvalidServerRecord(reason))
2751                if reason.contains("differs from the frozen manifest")
2752        ));
2753    }
2754
2755    #[test]
2756    fn neither_timestamp_nor_explicit_phase_links_award_assertion_credit() {
2757        let manifest = CoverageManifest {
2758            unmeasured: Vec::new(),
2759            decisions: vec![],
2760            points: vec![point("hit", 1)],
2761            branches: vec![],
2762            limitations: vec![],
2763            scope: None,
2764        };
2765        let phase = CoveragePhase {
2766            id: "assertion".into(),
2767            kind: "assertion".into(),
2768            operation: "equal".into(),
2769            source: None,
2770            caused_by_phase_id: None,
2771            started_at_ms: 100,
2772            ended_at_ms: Some(120),
2773            status: Some("passed".into()),
2774            error: None,
2775        };
2776        let mut attempt = raw("test", 0, "passed", &["hit"]);
2777        attempt.phases.push(phase.clone());
2778        attempt.runtime[0].events.push(RuntimeEvent {
2779            event_type: "hit".into(),
2780            id: "hit".into(),
2781            vector: None,
2782            timestamp_ms: 110,
2783            phase_id: None,
2784            statement_id: None,
2785            environment: "server".into(),
2786        });
2787        let inferred = create_coverage_view(&manifest, &[attempt.clone()], "time").unwrap();
2788        assert_eq!(inferred.points[0].confidence.level, "executed");
2789        assert!(!inferred.points[0].confidence.asserted);
2790
2791        attempt.runtime[0].events[0].phase_id = Some(phase.id);
2792        let explicit = create_coverage_view(&manifest, &[attempt], "time").unwrap();
2793        assert_eq!(explicit.points[0].confidence.level, "executed");
2794        assert!(!explicit.points[0].confidence.asserted);
2795    }
2796
2797    #[test]
2798    fn verified_view_uses_only_the_terminal_successful_attempt() {
2799        let manifest = CoverageManifest {
2800            unmeasured: Vec::new(),
2801            decisions: vec![],
2802            points: vec![point("failed", 1), point("passed", 2), point("expected", 3)],
2803            branches: vec![],
2804            limitations: vec![],
2805            scope: None,
2806        };
2807        let failed = raw("flaky", 0, "failed", &["failed"]);
2808        let mut passed = raw("flaky", 1, "passed", &["passed"]);
2809        passed.flaky = true;
2810        let mut expected = raw("expected-failure", 0, "passed", &["expected"]);
2811        expected.expected_status = Some("failed".into());
2812        let request = CoverageReportRequest {
2813            run_id: "run".into(),
2814            manifest,
2815            raw_results: vec![failed, passed, expected],
2816            generated_at: "time".into(),
2817            coverage_model: None,
2818            integrity: None,
2819            test_exit_code: ExitCodeInput::Missing,
2820        };
2821        let report = analyze_coverage_results(&request).unwrap();
2822        assert!(!report.filters.passed.points[0].covered);
2823        assert!(report.filters.passed.points[1].covered);
2824        assert!(!report.filters.passed.points[2].covered);
2825        assert!(report.filters.failed.points[0].covered);
2826        assert_eq!(
2827            report
2828                .view
2829                .tests
2830                .iter()
2831                .find(|test| test.name == "expected-failure")
2832                .unwrap()
2833                .outcome,
2834            "failed"
2835        );
2836        assert_eq!(report.view.tests[1].outcome, "flaky");
2837    }
2838
2839    #[test]
2840    fn expected_failure_is_a_green_outcome_but_not_verified_or_failed_coverage() {
2841        let mut expected = raw("expected-failure", 0, "failed", &["expected"]);
2842        expected.expected_status = Some("failed".into());
2843        let companion = raw("expected-failure", 0, "failed", &["expected"]);
2844        let request = CoverageReportRequest {
2845            run_id: "run".into(),
2846            manifest: CoverageManifest {
2847                unmeasured: Vec::new(),
2848                decisions: vec![],
2849                points: vec![point("expected", 1)],
2850                branches: vec![],
2851                limitations: vec![],
2852                scope: None,
2853            },
2854            raw_results: vec![companion, expected],
2855            generated_at: "time".into(),
2856            coverage_model: None,
2857            integrity: None,
2858            test_exit_code: ExitCodeInput::Missing,
2859        };
2860        let report = analyze_coverage_results(&request).unwrap();
2861        assert_eq!(report.view.tests[0].outcome, "passed");
2862        assert!(!report.filters.passed.points[0].covered);
2863        assert!(!report.filters.failed.points[0].covered);
2864    }
2865
2866    #[test]
2867    fn selected_but_unstarted_test_is_not_an_invented_attempt() {
2868        let mut unstarted = raw("unstarted", 0, "unstarted", &[]);
2869        unstarted.scope = None;
2870        unstarted.retry = None;
2871        unstarted.runtime.clear();
2872        let request = CoverageReportRequest {
2873            run_id: "run".into(),
2874            manifest: CoverageManifest {
2875                unmeasured: Vec::new(),
2876                decisions: vec![],
2877                points: vec![],
2878                branches: vec![],
2879                limitations: vec![],
2880                scope: None,
2881            },
2882            raw_results: vec![unstarted],
2883            generated_at: "time".into(),
2884            coverage_model: None,
2885            integrity: None,
2886            test_exit_code: ExitCodeInput::Present(Some(100)),
2887        };
2888        let report = analyze_coverage_results(&request).unwrap();
2889        assert_eq!(report.view.tests[0].outcome, "unstarted");
2890        assert!(report.view.tests[0].attempts.is_empty());
2891        assert!(report.filters.passed.tests.is_empty());
2892        assert!(report.filters.failed.tests.is_empty());
2893    }
2894
2895    fn archive(mut entries: Vec<EvidenceArchiveEntry>) -> PathBuf {
2896        let nonce = SystemTime::now()
2897            .duration_since(SystemTime::UNIX_EPOCH)
2898            .unwrap()
2899            .as_nanos();
2900        let root = std::env::temp_dir().join(format!(
2901            "supercov-rust-report-{}-{nonce}-{}",
2902            std::process::id(),
2903            ARCHIVE_ID.fetch_add(1, Ordering::Relaxed)
2904        ));
2905        fs::create_dir_all(&root).unwrap();
2906        let path = root.join("evidence.raw.gz");
2907        if !entries
2908            .iter()
2909            .any(|entry| entry.path == "coverage-model.json")
2910        {
2911            entries.push(EvidenceArchiveEntry {
2912                path: "coverage-model.json".into(),
2913                contents: serde_json::to_vec(
2914                    &PersistedCoverageModel::from_declaration(&javascript_coverage_model())
2915                        .unwrap(),
2916                )
2917                .unwrap(),
2918            });
2919        }
2920        if !entries.iter().any(|entry| entry.path == "frontend.json") {
2921            entries.push(EvidenceArchiveEntry {
2922                path: "frontend.json".into(),
2923                contents: serde_json::to_vec(&serde_json::json!({
2924                    "protocolVersion": 2,
2925                    "frontendId": "javascript",
2926                    "frontendVersion": "fixture-v1",
2927                    "language": "javascript",
2928                    "structuralSource": "owned-probes",
2929                    "runners": [{
2930                        "runner": "node:test",
2931                        "executionModel": "serial-in-process",
2932                        "attribution": {
2933                            "run": "exact",
2934                            "worker": "unavailable",
2935                            "test": "exact",
2936                            "retry": "exact",
2937                            "phase": "exact",
2938                            "action": "exact",
2939                            "assertion": "exact"
2940                        },
2941                        "limitations": [{
2942                            "id": "fixture-worker-unavailable",
2943                            "scopes": ["worker"],
2944                            "reason": "The fixture does not require worker identity"
2945                        }]
2946                    }],
2947                    "structuralLimitations": []
2948                }))
2949                .unwrap(),
2950            });
2951        }
2952        write_archive(entries, &path).unwrap();
2953        path
2954    }
2955
2956    #[test]
2957    fn archive_analysis_rejects_any_malformed_recognized_jsonl() {
2958        let manifest = CoverageManifest {
2959            unmeasured: Vec::new(),
2960            decisions: vec![],
2961            points: vec![point("background-hit", 1), point("test-hit", 2)],
2962            branches: vec![],
2963            limitations: vec![],
2964            scope: None,
2965        };
2966        // Declare exactly the observed runners: the journal's node:test and the
2967        // synthesized background runner, shaped as the JavaScript run declares it.
2968        let unattributed = |axis: &str| {
2969            serde_json::json!({
2970                "id": format!("background-no-{axis}"),
2971                "scopes": [axis],
2972                "reason": format!("Runner background did not expose exact {axis} identity for every result")
2973            })
2974        };
2975        let frontend = serde_json::json!({
2976            "protocolVersion": 2,
2977            "frontendId": "javascript",
2978            "frontendVersion": "fixture-v1",
2979            "language": "javascript",
2980            "structuralSource": "owned-probes",
2981            "runners": [
2982                {
2983                    "runner": "node:test",
2984                    "executionModel": "serial-in-process",
2985                    "attribution": {
2986                        "run": "exact", "worker": "unavailable", "test": "exact", "retry": "exact",
2987                        "phase": "exact", "action": "exact", "assertion": "exact"
2988                    },
2989                    "limitations": [{
2990                        "id": "fixture-worker-unavailable",
2991                        "scopes": ["worker"],
2992                        "reason": "The fixture does not require worker identity"
2993                    }]
2994                },
2995                {
2996                    "runner": "background",
2997                    "executionModel": "parallel-unattributed",
2998                    "attribution": {
2999                        "run": "exact", "worker": "unavailable", "test": "unavailable", "retry": "unavailable",
3000                        "phase": "unavailable", "action": "unavailable", "assertion": "unavailable"
3001                    },
3002                    "limitations": [
3003                        unattributed("worker"), unattributed("test"), unattributed("retry"),
3004                        unattributed("phase"), unattributed("action"), unattributed("assertion")
3005                    ]
3006                }
3007            ],
3008            "structuralLimitations": []
3009        });
3010        let path = archive(vec![
3011            EvidenceArchiveEntry {
3012                path: "manifest.json".into(),
3013                contents: serde_json::to_vec(&manifest).unwrap(),
3014            },
3015            EvidenceArchiveEntry {
3016                path: "frontend.json".into(),
3017                contents: serde_json::to_vec(&frontend).unwrap(),
3018            },
3019            EvidenceArchiveEntry {
3020                path: "playwright-worker-1.mcdc.jsonl".into(),
3021                contents: {
3022                    let mut contents =
3023                        serde_json::to_vec(&raw("journal-test", 0, "passed", &["test-hit"]))
3024                            .unwrap();
3025                    contents.extend_from_slice(b"\npartial-final-line");
3026                    contents
3027                },
3028            },
3029            EvidenceArchiveEntry {
3030                path: "server/background/worker.jsonl".into(),
3031                contents: b"{\"type\":\"hit\",\"id\":\"background-hit\"}\nnot-json\n".to_vec(),
3032            },
3033        ]);
3034        let result = analyze_coverage_archive(&ArchiveReportRequest {
3035            archive_path: path.clone(),
3036            run_id: "run".into(),
3037            generated_at: "time".into(),
3038            integrity: None,
3039            test_exit_code: ExitCodeInput::Missing,
3040        });
3041        assert!(matches!(
3042            result,
3043            Err(ReportError::InvalidJson { path, .. })
3044                if path == "playwright-worker-1.mcdc.jsonl"
3045        ));
3046        fs::remove_dir_all(path.parent().unwrap()).unwrap();
3047    }
3048
3049    #[test]
3050    fn a_torn_background_record_is_counted_not_fatal() {
3051        // Pool VMs restored from one snapshot are clones with the same pid and
3052        // cached shard path; their appends over a shared mount tear lines. One
3053        // torn line is one lost record: the report must still build, count it,
3054        // and surface it through the corrupt-evidence accounting.
3055        let manifest = CoverageManifest {
3056            unmeasured: Vec::new(),
3057            decisions: vec![],
3058            points: vec![],
3059            branches: vec![],
3060            limitations: vec![],
3061            scope: None,
3062        };
3063        // Declare exactly the observed runners: the journal's node:test and the
3064        // synthesized background runner, shaped as the JavaScript run declares it.
3065        let unattributed = |axis: &str| {
3066            serde_json::json!({
3067                "id": format!("background-no-{axis}"),
3068                "scopes": [axis],
3069                "reason": format!("Runner background did not expose exact {axis} identity for every result")
3070            })
3071        };
3072        let frontend = serde_json::json!({
3073            "protocolVersion": 2,
3074            "frontendId": "javascript",
3075            "frontendVersion": "fixture-v1",
3076            "language": "javascript",
3077            "structuralSource": "owned-probes",
3078            "runners": [
3079                {
3080                    "runner": "node:test",
3081                    "executionModel": "serial-in-process",
3082                    "attribution": {
3083                        "run": "exact", "worker": "unavailable", "test": "exact", "retry": "exact",
3084                        "phase": "exact", "action": "exact", "assertion": "exact"
3085                    },
3086                    "limitations": [{
3087                        "id": "fixture-worker-unavailable",
3088                        "scopes": ["worker"],
3089                        "reason": "The fixture does not require worker identity"
3090                    }]
3091                },
3092                {
3093                    "runner": "background",
3094                    "executionModel": "parallel-unattributed",
3095                    "attribution": {
3096                        "run": "exact", "worker": "unavailable", "test": "unavailable", "retry": "unavailable",
3097                        "phase": "unavailable", "action": "unavailable", "assertion": "unavailable"
3098                    },
3099                    "limitations": [
3100                        unattributed("worker"), unattributed("test"), unattributed("retry"),
3101                        unattributed("phase"), unattributed("action"), unattributed("assertion")
3102                    ]
3103                }
3104            ],
3105            "structuralLimitations": []
3106        });
3107        let path = archive(vec![
3108            EvidenceArchiveEntry {
3109                path: "manifest.json".into(),
3110                contents: serde_json::to_vec(&manifest).unwrap(),
3111            },
3112            EvidenceArchiveEntry {
3113                path: "frontend.json".into(),
3114                contents: serde_json::to_vec(&frontend).unwrap(),
3115            },
3116            EvidenceArchiveEntry {
3117                path: "playwright-worker-1.mcdc.jsonl".into(),
3118                contents: {
3119                    let mut contents =
3120                        serde_json::to_vec(&raw("journal-test", 0, "passed", &["test-hit"]))
3121                            .unwrap();
3122                    contents.push(b'\n');
3123                    contents
3124                },
3125            },
3126            EvidenceArchiveEntry {
3127                path: "execution.37360-1.168.jsonl".into(),
3128                contents: b"418836ceb3ac9b6277f\"}}\n".to_vec(),
3129            },
3130            EvidenceArchiveEntry {
3131                path: "server/background/process-5118-a1b2c3d4-0.jsonl".into(),
3132                contents: concat!(
3133                    "{\"type\":\"hit\",\"id\":\"background-hit\"}\n",
3134                    "{\"type\":\"decision\",\"meta\":{\"id\":\"torn\",\"file\":\"app/rou{\"type\":\"hit\",\"id\":\"other-clone\"}\n",
3135                    "{\"type\":\"hit\",\"id\":\"after-tear\"}\n",
3136                )
3137                .as_bytes()
3138                .to_vec(),
3139            },
3140        ]);
3141        let report = analyze_coverage_archive(&ArchiveReportRequest {
3142            archive_path: path.clone(),
3143            run_id: "run".into(),
3144            generated_at: "time".into(),
3145            integrity: None,
3146            test_exit_code: ExitCodeInput::Missing,
3147        })
3148        .expect("a torn evidence line must not fail the whole run");
3149        let transport = report.view.transport.expect("transport stats");
3150        assert_eq!(transport.corrupt_records, 2);
3151        assert_eq!(transport.corrupt_files, 2);
3152        assert_eq!(transport.background_server_records, 2);
3153        fs::remove_dir_all(path.parent().unwrap()).unwrap();
3154    }
3155
3156    #[test]
3157    fn archive_analysis_rejects_cross_run_evidence() {
3158        let manifest = CoverageManifest {
3159            unmeasured: Vec::new(),
3160            decisions: vec![],
3161            points: vec![],
3162            branches: vec![],
3163            limitations: vec![],
3164            scope: None,
3165        };
3166        let mut result = raw("test", 0, "passed", &[]);
3167        result.scope = Some(ExecutionScope {
3168            version: 1,
3169            run_id: "other-run".into(),
3170            worker_id: "worker".into(),
3171            test_id: "test".into(),
3172            test_key: "key".into(),
3173            retry: 0,
3174            attempt_id: "attempt".into(),
3175        });
3176        let path = archive(vec![
3177            EvidenceArchiveEntry {
3178                path: "manifest.json".into(),
3179                contents: serde_json::to_vec(&manifest).unwrap(),
3180            },
3181            EvidenceArchiveEntry {
3182                path: "worker/mcdc.json".into(),
3183                contents: serde_json::to_vec(&result).unwrap(),
3184            },
3185        ]);
3186        assert!(matches!(
3187            analyze_coverage_archive(&ArchiveReportRequest {
3188                archive_path: path.clone(),
3189                run_id: "run".into(),
3190                generated_at: "time".into(),
3191                integrity: None,
3192                test_exit_code: ExitCodeInput::Missing,
3193            }),
3194            Err(ReportError::InvalidArchive(reason))
3195                if reason.contains("expected=run actual=other-run")
3196        ));
3197        fs::remove_dir_all(path.parent().unwrap()).unwrap();
3198    }
3199
3200    #[test]
3201    fn coverage_model_vectors_are_strict_and_language_binding_is_fatal() {
3202        let vectors: Value = serde_json::from_str(include_str!(
3203            "../test-assets/coverage-model-v1/vectors.json"
3204        ))
3205        .unwrap();
3206        for value in vectors["valid"].as_array().unwrap() {
3207            let model: PersistedCoverageModel = serde_json::from_value(value.clone()).unwrap();
3208            model.into_declaration().unwrap();
3209        }
3210        for vector in vectors["invalid"].as_array().unwrap() {
3211            let value = vector["value"].clone();
3212            if let Ok(model) = serde_json::from_value::<PersistedCoverageModel>(value) {
3213                assert!(
3214                    model.into_declaration().is_err(),
3215                    "accepted invalid model vector: {}",
3216                    vector["reason"]
3217                );
3218            }
3219        }
3220
3221        let manifest = CoverageManifest {
3222            unmeasured: Vec::new(),
3223            decisions: vec![],
3224            points: vec![],
3225            branches: vec![],
3226            limitations: vec![],
3227            scope: None,
3228        };
3229        let path = archive(vec![
3230            EvidenceArchiveEntry {
3231                path: "coverage-model.json".into(),
3232                contents: serde_json::to_vec(&PersistedCoverageModel {
3233                    schema_version: COVERAGE_MODEL_SCHEMA_VERSION,
3234                    language: "rust".into(),
3235                    variant: "rust-source-v1".into(),
3236                    name: "Rust source coverage".into(),
3237                    completeness_meaning: "Every Rust obligation was satisfied.".into(),
3238                    measured: vec!["Rust statements".into()],
3239                    not_measured: vec![],
3240                })
3241                .unwrap(),
3242            },
3243            EvidenceArchiveEntry {
3244                path: "manifest.json".into(),
3245                contents: serde_json::to_vec(&manifest).unwrap(),
3246            },
3247        ]);
3248        assert!(matches!(
3249            analyze_coverage_archive(&ArchiveReportRequest {
3250                archive_path: path.clone(),
3251                run_id: "run".into(),
3252                generated_at: "time".into(),
3253                integrity: None,
3254                test_exit_code: ExitCodeInput::Missing,
3255            }),
3256            Err(ReportError::InvalidArchive(reason))
3257                if reason.contains("javascript differs from coverage model language rust")
3258        ));
3259        fs::remove_dir_all(path.parent().unwrap()).unwrap();
3260    }
3261
3262    #[test]
3263    fn rust_compiler_scope_requires_an_exact_full_source_fingerprint() {
3264        let mut manifest = CoverageManifest {
3265            unmeasured: Vec::new(),
3266            decisions: vec![],
3267            points: vec![],
3268            branches: vec![],
3269            limitations: vec![],
3270            scope: Some(serde_json::json!({
3271                "language": "rust",
3272                "model": "rust-source-v1",
3273                "crate": "fixture",
3274                "measurementComplete": false,
3275                "sourceFingerprint": {
3276                    "algorithm": "sha256",
3277                    "digest": "1".repeat(64),
3278                    "files": 2,
3279                    "generatedFiles": 1,
3280                },
3281            })),
3282        };
3283        validate_rust_compiler_scope(&manifest).unwrap();
3284
3285        // The optional extension does not open the scope to arbitrary keys or
3286        // unvalidated records, and legacy archives still need no such field.
3287        let mut extended = manifest.clone();
3288        extended.scope.as_mut().unwrap()["assertionIdentities"] = serde_json::json!({
3289            "schema":"supercov-rust-assertion-identities-v1", "records":[]
3290        });
3291        validate_rust_compiler_scope(&extended).unwrap();
3292
3293        manifest.scope.as_mut().unwrap()["sourceFingerprint"]["digest"] =
3294            Value::String("not-a-digest".into());
3295        assert!(matches!(
3296            validate_rust_compiler_scope(&manifest),
3297            Err(ReportError::InvalidArchive(reason))
3298                if reason == "malformed Rust compiler source fingerprint"
3299        ));
3300
3301        manifest.scope.as_mut().unwrap()["sourceFingerprint"]["digest"] =
3302            Value::String("1".repeat(64));
3303        manifest.scope.as_mut().unwrap()["sourceFingerprint"]["unexpected"] = Value::Bool(true);
3304        assert!(validate_rust_compiler_scope(&manifest).is_err());
3305    }
3306
3307    #[test]
3308    fn explicit_null_exit_code_remains_distinct_from_an_absent_exit_code() {
3309        let request: CoverageReportRequest = serde_json::from_value(serde_json::json!({
3310            "runId": "run",
3311            "manifest": { "decisions": [], "points": [], "branches": [] },
3312            "rawResults": [{
3313                "test": "test",
3314                "status": "passed",
3315                "browser": [],
3316                "server": []
3317            }],
3318            "generatedAt": "time",
3319            "testExitCode": null
3320        }))
3321        .unwrap();
3322        let report = analyze_coverage_results(&request).unwrap();
3323        assert_eq!(
3324            report.execution,
3325            Some(ExecutionResult {
3326                test_exit_code: None,
3327                valid: false,
3328            })
3329        );
3330    }
3331    #[test]
3332    fn a_dimension_is_summarised_over_the_tests_it_can_describe() {
3333        // A test whose reach nothing recorded contributes no hits. Counting it
3334        // in the denominator of a coverage percentage reported a package
3335        // covered entirely by parallel tests as covered 0.00% -- a wrong
3336        // number, which is worse than a missing one, because it reads as a
3337        // suite that tests nothing.
3338        let exact = |id: &str, kind: &str| TestCoverageResult {
3339            id: id.into(),
3340            name: id.into(),
3341            file: None,
3342            title: None,
3343            retries: vec![],
3344            attempts: vec![],
3345            outcome: "passed".into(),
3346            provenance: TestProvenance {
3347                runner: "go-test".into(),
3348                kind: kind.into(),
3349                project: None,
3350                source: String::new(),
3351            },
3352            role: "test".into(),
3353            attribution: ATTRIBUTION_EXACT.into(),
3354            hits: vec![],
3355            decisions: vec![],
3356            lines: vec![],
3357        };
3358        let mut run_wide = exact("parallel", "unit");
3359        run_wide.attribution = ATTRIBUTION_RUN_WIDE.into();
3360
3361        // Whose coverage it is, and whether all of it is there, are separate
3362        // questions, and conflating them gets one of them wrong.
3363        //
3364        // `partial` coverage is the test's own -- Ruby credits a line to the
3365        // first test that reaches it, and that crediting is true -- so it
3366        // counts in a percentage, and a union over such tests is a union of
3367        // things that happened. What it cannot support is the opposite
3368        // inference: that a test not recorded against a line did not run it.
3369        assert!(coverage_is_its_own(&exact("serial", "unit").attribution));
3370        assert!(coverage_is_its_own(ATTRIBUTION_PARTIAL));
3371        assert!(!coverage_is_its_own(&run_wide.attribution));
3372
3373        assert!(coverage_is_complete(ATTRIBUTION_EXACT));
3374        assert!(!coverage_is_complete(ATTRIBUTION_PARTIAL));
3375        assert!(!coverage_is_complete(ATTRIBUTION_RUN_WIDE));
3376
3377        // And a frontend that has never heard of the field keeps its meaning:
3378        // absent is exact, so nothing already measured changes shape.
3379        assert!(coverage_is_its_own(""));
3380        assert!(coverage_is_complete(""));
3381    }
3382}