Skip to main content

mbx_cache_core/agent/
wire.rs

1use super::{FileDigestResolution, FileDigestScope, FileIdentity, RecordedFileDigest};
2use crate::{ActionPrediction, CacheDigest, RemoteActionResult};
3use serde::{Deserialize, Serialize};
4use std::collections::BTreeMap;
5use std::path::PathBuf;
6
7/// Wire protocol version used between an in-process cache agent and its shims.
8pub const AGENT_PROTOCOL_VERSION: u8 = 7;
9/// Largest single protocol request the agent will read.
10///
11/// Requests are small JSON objects; the largest legitimate ones carry an output
12/// tree or a batch of digests, which stay far below this.
13pub(super) const MAX_REQUEST_BYTES: usize = 16 * 1024 * 1024;
14
15/// A request accepted by the task-scoped cache agent.
16#[derive(Debug, Clone, Serialize, Deserialize)]
17#[serde(tag = "type", rename_all = "snake_case")]
18pub enum AgentRequest {
19    /// Negotiate protocol and application versions.
20    Hello {
21        /// Agent protocol version understood by the caller.
22        protocol: u8,
23        /// Human-readable mbx client version.
24        client_version: String,
25    },
26    /// Begin a prediction-manifest run for a stable Cargo or task identity.
27    BeginTask {
28        /// Stable 64-character lowercase hexadecimal task identity.
29        task: String,
30    },
31    /// Commit predictions collected by an earlier [`Self::BeginTask`].
32    CommitTask {
33        /// Opaque run identifier returned by the agent.
34        run: String,
35    },
36    /// Resolve a blob to a session-verified local CAS path.
37    FindBlob {
38        /// Blob to resolve.
39        digest: CacheDigest,
40    },
41    /// Resolve blobs to session-verified local CAS paths.
42    FindBlobs {
43        /// Blobs to resolve, preserving request order in the response.
44        digests: Vec<CacheDigest>,
45    },
46    /// Import a file into the local content-addressed store.
47    StoreBlob {
48        /// Digest the source must match.
49        digest: CacheDigest,
50        /// File to verify and import.
51        source: PathBuf,
52    },
53    /// Look up an action-result record.
54    FindActionResult {
55        /// Action digest to resolve.
56        action: CacheDigest,
57    },
58    /// Account for a successfully restored cache hit.
59    RecordActionHit {
60        /// Action that supplied the outputs.
61        action: CacheDigest,
62        /// Restoration work performed by the adapter.
63        restore: RestoreStats,
64        /// Compiler crate name, when the invocation supplied one.
65        crate_name: Option<String>,
66    },
67    /// A compilation the adapter declined to cache, grouped by reason.
68    RecordBypass {
69        /// Stable, low-cardinality bypass-reason name.
70        kind: String,
71    },
72    /// A compilation the adapter could not look up, having no key to look up
73    /// with. Distinct from a bypass: these are cached once compiled.
74    RecordUnconsulted,
75    /// Account for one real compiler invocation performed by the adapter.
76    RecordCompilerInvocation {
77        /// Stable outcome category such as `miss`, `unconsulted`, `bypass`, or
78        /// `incremental` for a compilation the adapter deliberately ran with
79        /// incremental state instead of publishing.
80        outcome: String,
81        /// Compiler crate name, when the invocation supplied one.
82        crate_name: Option<String>,
83        /// Wall time spent running the compiler.
84        duration_ns: u64,
85    },
86    /// Account for a cache hit that was rebuilt for correctness verification.
87    RecordActionVerification {
88        /// Whether rebuilt and cached outputs matched.
89        matched: bool,
90        /// Restoration work performed before rebuilding.
91        restore: RestoreStats,
92    },
93    /// Store an action-result record locally and enqueue remote publication.
94    StoreActionResult {
95        /// Action-result record to store.
96        result: RemoteActionResult,
97    },
98    /// Find an earlier input prediction for a task and invocation.
99    FindActionPrediction {
100        /// Stable task identity.
101        task: String,
102        /// Digest of the compiler invocation without discovered inputs.
103        invocation: CacheDigest,
104    },
105    /// Record an input prediction after a successful compilation.
106    RecordActionPrediction {
107        /// Stable task identity.
108        task: String,
109        /// Adapter-owned prediction record.
110        prediction: ActionPrediction,
111    },
112    /// Find cached identity output for an executable and environment.
113    FindExecutableIdentity {
114        /// Executable whose identity command would run.
115        executable: PathBuf,
116        /// Environment variables affecting identity output.
117        environment: BTreeMap<String, Option<String>>,
118    },
119    /// Cache identity output for an executable and environment.
120    StoreExecutableIdentity {
121        /// Executable whose identity command ran.
122        executable: PathBuf,
123        /// Environment variables affecting identity output.
124        environment: BTreeMap<String, Option<String>>,
125        /// Captured identity-command standard output.
126        stdout: Vec<u8>,
127    },
128    /// Surface a shim diagnostic through the session that owns the build.
129    ///
130    /// A shim must not print diagnostics itself: its stderr belongs to the
131    /// compiler it stands in for, and build scripts read that stream as part
132    /// of the compiler's answer -- cc-rs treats any stderr output from a flag
133    /// probe as "unsupported", which changes the flags of every compilation
134    /// that follows and, with them, every action key the build produces.
135    ///
136    /// Appended rather than grouped with the other `Record*` requests it
137    /// belongs beside: these variants carry no `repr`, so inserting one moves
138    /// the discriminant of every variant after it, and a break nobody asked
139    /// for is worth less than the grouping. New variants go here.
140    RecordWarning {
141        /// Human-readable single-line diagnostic.
142        message: String,
143    },
144    /// Find session-recorded digests for files with these identities.
145    FindFileDigests {
146        /// What the recorded digests may stand in for.
147        scope: FileDigestScope,
148        /// File identities to resolve, preserving request order in the
149        /// response.
150        files: Vec<FileIdentity>,
151    },
152    /// Record digests of files a shim hashed or wrote this session.
153    RecordFileDigests {
154        /// What the recorded digests may stand in for.
155        scope: FileDigestScope,
156        /// Hashed files and the identities their digests describe.
157        entries: Vec<RecordedFileDigest>,
158    },
159    /// Join or claim an invocation-wide promise through the cache server.
160    JoinActionPromise {
161        /// Adapter that owns the invocation and prediction payload.
162        adapter: String,
163        /// Digest of the compiler invocation before input discovery.
164        invocation: CacheDigest,
165    },
166    /// Fulfill a claimed promise after its action result is remotely durable.
167    CompleteActionPromise {
168        /// Opaque claim token returned by [`Self::JoinActionPromise`].
169        claim: String,
170        /// Prediction through which waiters reconstruct the final action key.
171        prediction: ActionPrediction,
172    },
173    /// Resolve file digests, coalescing concurrent reads through the agent.
174    ResolveFileDigests {
175        /// What the digest and any accompanying validation may stand in for.
176        scope: FileDigestScope,
177        /// File identities to resolve, preserving request order.
178        files: Vec<FileIdentity>,
179    },
180}
181
182/// Local output restoration work performed by one action-cache adapter hit.
183#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
184#[serde(deny_unknown_fields)]
185pub struct RestoreStats {
186    /// Cumulative time spent materializing and validating output files.
187    pub duration_ns: u64,
188    /// Compiler wall time recorded when this action was originally produced.
189    /// Zero means no timing hint was available.
190    pub avoided_compiler_duration_ns: u64,
191    /// Number of compiler output files restored.
192    pub output_files: u64,
193    /// Declared size of compiler output files restored.
194    pub output_bytes: u64,
195    /// Number of restored output files that share data blocks with the CAS.
196    pub reflinked_output_files: u64,
197    /// Declared size of restored outputs that share data blocks with the CAS.
198    pub reflinked_output_bytes: u64,
199    /// Number of restored output files that required a byte-for-byte copy.
200    pub copied_output_files: u64,
201    /// Declared size of restored outputs that required a byte-for-byte copy.
202    pub copied_output_bytes: u64,
203    /// Number of output files already in place with the cached contents, kept
204    /// rather than rewritten.
205    pub reused_output_files: u64,
206    /// Declared size of outputs kept in place rather than rewritten.
207    pub reused_output_bytes: u64,
208}
209
210/// One accounted cache decision, as it happens.
211///
212/// The agent already folds every one of these into [`AgentStats`]; an observer
213/// sees the same decisions individually, before that summing loses the crate
214/// they belong to. Delivered synchronously from the request handler, so an
215/// observer that blocks slows the build it is watching.
216#[derive(Debug, Clone)]
217#[non_exhaustive]
218pub enum AgentEvent {
219    /// An action's outputs were restored from cache.
220    ActionHit {
221        /// Compiler crate name, when the invocation supplied one.
222        crate_name: Option<String>,
223        /// Restoration work performed by the adapter.
224        restore: RestoreStats,
225    },
226    /// A compilation the adapter declined to cache.
227    Bypass {
228        /// Stable, low-cardinality bypass-reason name.
229        kind: String,
230    },
231    /// A compilation no lookup was possible for.
232    Unconsulted,
233    /// A real compiler invocation ran.
234    CompilerInvocation {
235        /// Stable outcome category such as `miss`, `unconsulted`, or `bypass`.
236        outcome: String,
237        /// Compiler crate name, when the invocation supplied one.
238        crate_name: Option<String>,
239        /// Wall time spent running the compiler.
240        duration_ns: u64,
241    },
242    /// A hit was rebuilt to verify it.
243    Verification {
244        /// Whether rebuilt and cached outputs matched.
245        matched: bool,
246        /// Restoration work performed before rebuilding.
247        restore: RestoreStats,
248    },
249    /// A shim reported a diagnostic for the session to surface.
250    Warning {
251        /// Human-readable single-line diagnostic.
252        message: String,
253    },
254    /// Cache-key material for the action event immediately following it.
255    ActionDiagnostic {
256        /// Outcome of the action this describes.
257        outcome: String,
258        /// Compiler crate name, when the invocation supplied one.
259        crate_name: Option<String>,
260        /// Privacy-preserving action-key decomposition.
261        diagnostic: ActionDiagnostic,
262    },
263}
264
265/// A privacy-preserving decomposition of an action key.
266///
267/// Values are content digests rather than source or environment contents. The
268/// names are enough to say what changed without copying secrets into session
269/// history.
270#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
271pub struct ActionDiagnostic {
272    /// Complete action-cache key.
273    pub action: CacheDigest,
274    /// Non-file key components, named for display.
275    pub components: BTreeMap<String, CacheDigest>,
276    /// Normalized input path to content digest.
277    pub inputs: BTreeMap<String, CacheDigest>,
278}
279
280/// A sink for [`AgentEvent`]s observed during one session.
281pub trait AgentEventObserver: Send + Sync {
282    /// Handle one event. Must not panic, and should not block.
283    fn event(&self, event: AgentEvent);
284}
285
286/// A response returned by the task-scoped cache agent.
287#[derive(Debug, Clone, Serialize, Deserialize)]
288#[serde(tag = "type", rename_all = "snake_case")]
289pub enum AgentResponse {
290    /// Successful protocol negotiation.
291    Hello {
292        /// Agent protocol version.
293        protocol: u8,
294        /// Human-readable agent version.
295        agent_version: String,
296    },
297    /// A task prediction run was begun.
298    TaskBegun {
299        /// Opaque run identifier to pass to compiler shims and commit later.
300        run: String,
301    },
302    /// A task prediction run was committed.
303    TaskCommitted,
304    /// A local CAS path already verified against the requested digest.
305    Blob {
306        /// Verified local path, or `None` on a cache miss.
307        path: Option<PathBuf>,
308    },
309    /// Local CAS paths already verified against the requested digests.
310    Blobs {
311        /// Verified local paths or misses, in request order.
312        paths: Vec<Option<PathBuf>>,
313    },
314    /// A blob was stored locally.
315    Stored {
316        /// Path of the stored object in the local CAS.
317        path: PathBuf,
318    },
319    /// Result of an action lookup.
320    ActionResult {
321        /// Validated action result, or `None` on a cache miss.
322        result: Option<RemoteActionResult>,
323    },
324    /// Hit statistics were updated.
325    ActionHitRecorded,
326    /// Verification statistics were updated.
327    ActionVerificationRecorded,
328    /// Bypass statistics were updated.
329    BypassRecorded,
330    /// Unconsulted-compilation statistics were updated.
331    UnconsultedRecorded,
332    /// Compiler invocation accounting was recorded.
333    CompilerInvocationRecorded,
334    /// An action result was stored.
335    ActionStored {
336        /// Path of the stored local action-result record.
337        path: PathBuf,
338    },
339    /// Result of an input-prediction lookup.
340    ActionPrediction {
341        /// Matching prediction, or `None` when none is known.
342        prediction: Option<ActionPrediction>,
343    },
344    /// An input prediction was recorded.
345    ActionPredictionRecorded,
346    /// Result of an executable-identity lookup.
347    ExecutableIdentity {
348        /// Captured output, or `None` when no identity is cached.
349        stdout: Option<Vec<u8>>,
350    },
351    /// The request failed without terminating the agent connection.
352    Error {
353        /// Human-readable failure description.
354        message: String,
355    },
356    /// A shim diagnostic was accepted for the session to surface.
357    ///
358    /// Sits past `Error` for the reason [`AgentRequest::RecordWarning`] sits
359    /// last: anywhere earlier renumbers the variants below it. New variants go
360    /// here.
361    WarningRecorded,
362    /// Digests recorded earlier for the requested file identities.
363    FileDigests {
364        /// Recorded digests or misses, in request order.
365        digests: Vec<Option<CacheDigest>>,
366    },
367    /// File digests were recorded.
368    FileDigestsRecorded,
369    /// State of an optional server-wide compilation promise.
370    ActionPromise {
371        /// Opaque lease owned by this client, when it should compile.
372        claim: Option<String>,
373        /// Completed prediction, when another client compiled first.
374        prediction: Option<ActionPrediction>,
375    },
376    /// A server-wide compilation promise was fulfilled or safely skipped.
377    ActionPromiseCompleted,
378    /// Digests and scope-specific validation outcomes for requested files.
379    FileDigestsResolved {
380        /// Resolution outcomes in request order.
381        resolutions: Vec<FileDigestResolution>,
382    },
383}