ci_engine/model.rs
1// SPDX-License-Identifier: Apache-2.0
2//! Public executor inputs and outputs.
3
4use std::path::Path;
5
6use ci_config::Trigger;
7use crypto::{Basis, CiVerdictBody, Conclusion, StateRef};
8use serde::{Deserialize, Serialize};
9
10use crate::{
11 HermeticEnv, ProcGroupRegistry, ServiceProvider,
12 result_cache::{ResultCache, SpotCheck},
13};
14
15/// Facts about the exact state/tree being evaluated.
16#[derive(Debug, Clone)]
17pub struct ExecutionContext {
18 /// Repository identifier.
19 pub repo: String,
20 /// State reference carried in the verdict body.
21 pub state: StateRef,
22 /// Exact evaluated tree and basis.
23 pub basis: Basis,
24 /// Typed-blob digest of the raw definition.
25 pub definition_digest: String,
26 /// Optional toolchain string.
27 pub toolchain: Option<String>,
28 /// Hosted pick id; absent locally.
29 pub pick_id: Option<String>,
30 /// One-based run attempt.
31 pub attempt: u32,
32 /// Hosted runner identity; absent locally.
33 pub runner: Option<String>,
34 /// Immutable image digest, when used.
35 pub image_digest: Option<String>,
36}
37
38/// Stable options for an executor invocation.
39pub struct RunOptions<'a> {
40 /// Directory in which argv executes.
41 pub workdir: &'a Path,
42 /// Service lifecycle provider.
43 pub services: &'a dyn ServiceProvider,
44 /// Injected RFC3339 clock.
45 pub now_rfc3339: &'a dyn Fn() -> String,
46}
47
48/// Additive execution controls.
49#[derive(Default)]
50pub struct RunControls<'a> {
51 /// Optional trigger filter.
52 pub trigger: Option<Trigger>,
53 /// Cache root outside the evaluated source tree.
54 pub cache_root: Option<&'a Path>,
55 /// Optional injected hermetic environment.
56 pub hermetic_env: Option<&'a HermeticEnv>,
57 /// Optional group registry for runner drain.
58 pub proc_groups: Option<ProcGroupRegistry>,
59 /// Optional content-addressed result cache.
60 pub result_cache: Option<&'a dyn ResultCache>,
61 /// Spot-check policy applied to cache hits. Ignored when no cache is set.
62 pub spot_check: SpotCheck,
63}
64
65/// Operational record for one flake-retry attempt.
66#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
67pub struct AttemptRecord {
68 /// One-based attempt index.
69 pub attempt: u32,
70 /// Attempt conclusion.
71 pub conclusion: Conclusion,
72 /// Wall time in milliseconds.
73 pub duration_ms: u64,
74 /// Whether output matched a flake signature.
75 pub flake_matched: bool,
76}
77
78/// One check's verdict body and operational sidecars.
79#[derive(Debug, Clone)]
80pub struct CheckResult {
81 /// Signable verdict-v2 body.
82 pub body: CiVerdictBody,
83 /// ANSI-stripped combined output.
84 pub combined_output: String,
85 /// Attempts performed.
86 pub attempts: u32,
87 /// Attempt records, not part of the signed body.
88 pub attempt_records: Vec<AttemptRecord>,
89}
90
91impl CheckResult {
92 /// Terminal conclusion.
93 #[must_use]
94 pub fn conclusion(&self) -> Conclusion {
95 self.body.outcome.conclusion
96 }
97
98 /// Whether a successful result needed a flake retry.
99 #[must_use]
100 pub fn recovered_after_flake(&self) -> bool {
101 self.conclusion() == Conclusion::Success
102 && self
103 .attempt_records
104 .iter()
105 .any(|attempt| attempt.flake_matched)
106 }
107}