mbx_cache_core/agent/wire.rs
1use super::{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 = 6;
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}
174
175/// Local output restoration work performed by one action-cache adapter hit.
176#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
177#[serde(deny_unknown_fields)]
178pub struct RestoreStats {
179 /// Cumulative time spent materializing and validating output files.
180 pub duration_ns: u64,
181 /// Compiler wall time recorded when this action was originally produced.
182 /// Zero means no timing hint was available.
183 pub avoided_compiler_duration_ns: u64,
184 /// Number of compiler output files restored.
185 pub output_files: u64,
186 /// Declared size of compiler output files restored.
187 pub output_bytes: u64,
188 /// Number of restored output files that share data blocks with the CAS.
189 pub reflinked_output_files: u64,
190 /// Declared size of restored outputs that share data blocks with the CAS.
191 pub reflinked_output_bytes: u64,
192 /// Number of restored output files that required a byte-for-byte copy.
193 pub copied_output_files: u64,
194 /// Declared size of restored outputs that required a byte-for-byte copy.
195 pub copied_output_bytes: u64,
196 /// Number of output files already in place with the cached contents, kept
197 /// rather than rewritten.
198 pub reused_output_files: u64,
199 /// Declared size of outputs kept in place rather than rewritten.
200 pub reused_output_bytes: u64,
201}
202
203/// One accounted cache decision, as it happens.
204///
205/// The agent already folds every one of these into [`AgentStats`]; an observer
206/// sees the same decisions individually, before that summing loses the crate
207/// they belong to. Delivered synchronously from the request handler, so an
208/// observer that blocks slows the build it is watching.
209#[derive(Debug, Clone)]
210#[non_exhaustive]
211pub enum AgentEvent {
212 /// An action's outputs were restored from cache.
213 ActionHit {
214 /// Compiler crate name, when the invocation supplied one.
215 crate_name: Option<String>,
216 /// Restoration work performed by the adapter.
217 restore: RestoreStats,
218 },
219 /// A compilation the adapter declined to cache.
220 Bypass {
221 /// Stable, low-cardinality bypass-reason name.
222 kind: String,
223 },
224 /// A compilation no lookup was possible for.
225 Unconsulted,
226 /// A real compiler invocation ran.
227 CompilerInvocation {
228 /// Stable outcome category such as `miss`, `unconsulted`, or `bypass`.
229 outcome: String,
230 /// Compiler crate name, when the invocation supplied one.
231 crate_name: Option<String>,
232 /// Wall time spent running the compiler.
233 duration_ns: u64,
234 },
235 /// A hit was rebuilt to verify it.
236 Verification {
237 /// Whether rebuilt and cached outputs matched.
238 matched: bool,
239 /// Restoration work performed before rebuilding.
240 restore: RestoreStats,
241 },
242 /// A shim reported a diagnostic for the session to surface.
243 Warning {
244 /// Human-readable single-line diagnostic.
245 message: String,
246 },
247 /// Cache-key material for the action event immediately following it.
248 ActionDiagnostic {
249 /// Outcome of the action this describes.
250 outcome: String,
251 /// Compiler crate name, when the invocation supplied one.
252 crate_name: Option<String>,
253 /// Privacy-preserving action-key decomposition.
254 diagnostic: ActionDiagnostic,
255 },
256}
257
258/// A privacy-preserving decomposition of an action key.
259///
260/// Values are content digests rather than source or environment contents. The
261/// names are enough to say what changed without copying secrets into session
262/// history.
263#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
264pub struct ActionDiagnostic {
265 /// Complete action-cache key.
266 pub action: CacheDigest,
267 /// Non-file key components, named for display.
268 pub components: BTreeMap<String, CacheDigest>,
269 /// Normalized input path to content digest.
270 pub inputs: BTreeMap<String, CacheDigest>,
271}
272
273/// A sink for [`AgentEvent`]s observed during one session.
274pub trait AgentEventObserver: Send + Sync {
275 /// Handle one event. Must not panic, and should not block.
276 fn event(&self, event: AgentEvent);
277}
278
279/// A response returned by the task-scoped cache agent.
280#[derive(Debug, Clone, Serialize, Deserialize)]
281#[serde(tag = "type", rename_all = "snake_case")]
282pub enum AgentResponse {
283 /// Successful protocol negotiation.
284 Hello {
285 /// Agent protocol version.
286 protocol: u8,
287 /// Human-readable agent version.
288 agent_version: String,
289 },
290 /// A task prediction run was begun.
291 TaskBegun {
292 /// Opaque run identifier to pass to compiler shims and commit later.
293 run: String,
294 },
295 /// A task prediction run was committed.
296 TaskCommitted,
297 /// A local CAS path already verified against the requested digest.
298 Blob {
299 /// Verified local path, or `None` on a cache miss.
300 path: Option<PathBuf>,
301 },
302 /// Local CAS paths already verified against the requested digests.
303 Blobs {
304 /// Verified local paths or misses, in request order.
305 paths: Vec<Option<PathBuf>>,
306 },
307 /// A blob was stored locally.
308 Stored {
309 /// Path of the stored object in the local CAS.
310 path: PathBuf,
311 },
312 /// Result of an action lookup.
313 ActionResult {
314 /// Validated action result, or `None` on a cache miss.
315 result: Option<RemoteActionResult>,
316 },
317 /// Hit statistics were updated.
318 ActionHitRecorded,
319 /// Verification statistics were updated.
320 ActionVerificationRecorded,
321 /// Bypass statistics were updated.
322 BypassRecorded,
323 /// Unconsulted-compilation statistics were updated.
324 UnconsultedRecorded,
325 /// Compiler invocation accounting was recorded.
326 CompilerInvocationRecorded,
327 /// An action result was stored.
328 ActionStored {
329 /// Path of the stored local action-result record.
330 path: PathBuf,
331 },
332 /// Result of an input-prediction lookup.
333 ActionPrediction {
334 /// Matching prediction, or `None` when none is known.
335 prediction: Option<ActionPrediction>,
336 },
337 /// An input prediction was recorded.
338 ActionPredictionRecorded,
339 /// Result of an executable-identity lookup.
340 ExecutableIdentity {
341 /// Captured output, or `None` when no identity is cached.
342 stdout: Option<Vec<u8>>,
343 },
344 /// The request failed without terminating the agent connection.
345 Error {
346 /// Human-readable failure description.
347 message: String,
348 },
349 /// A shim diagnostic was accepted for the session to surface.
350 ///
351 /// Sits past `Error` for the reason [`AgentRequest::RecordWarning`] sits
352 /// last: anywhere earlier renumbers the variants below it. New variants go
353 /// here.
354 WarningRecorded,
355 /// Digests recorded earlier for the requested file identities.
356 FileDigests {
357 /// Recorded digests or misses, in request order.
358 digests: Vec<Option<CacheDigest>>,
359 },
360 /// File digests were recorded.
361 FileDigestsRecorded,
362 /// State of an optional server-wide compilation promise.
363 ActionPromise {
364 /// Opaque lease owned by this client, when it should compile.
365 claim: Option<String>,
366 /// Completed prediction, when another client compiled first.
367 prediction: Option<ActionPrediction>,
368 },
369 /// A server-wide compilation promise was fulfilled or safely skipped.
370 ActionPromiseCompleted,
371}