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}