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}
248
249/// A sink for [`AgentEvent`]s observed during one session.
250pub trait AgentEventObserver: Send + Sync {
251 /// Handle one event. Must not panic, and should not block.
252 fn event(&self, event: AgentEvent);
253}
254
255/// A response returned by the task-scoped cache agent.
256#[derive(Debug, Clone, Serialize, Deserialize)]
257#[serde(tag = "type", rename_all = "snake_case")]
258pub enum AgentResponse {
259 /// Successful protocol negotiation.
260 Hello {
261 /// Agent protocol version.
262 protocol: u8,
263 /// Human-readable agent version.
264 agent_version: String,
265 },
266 /// A task prediction run was begun.
267 TaskBegun {
268 /// Opaque run identifier to pass to compiler shims and commit later.
269 run: String,
270 },
271 /// A task prediction run was committed.
272 TaskCommitted,
273 /// A local CAS path already verified against the requested digest.
274 Blob {
275 /// Verified local path, or `None` on a cache miss.
276 path: Option<PathBuf>,
277 },
278 /// Local CAS paths already verified against the requested digests.
279 Blobs {
280 /// Verified local paths or misses, in request order.
281 paths: Vec<Option<PathBuf>>,
282 },
283 /// A blob was stored locally.
284 Stored {
285 /// Path of the stored object in the local CAS.
286 path: PathBuf,
287 },
288 /// Result of an action lookup.
289 ActionResult {
290 /// Validated action result, or `None` on a cache miss.
291 result: Option<RemoteActionResult>,
292 },
293 /// Hit statistics were updated.
294 ActionHitRecorded,
295 /// Verification statistics were updated.
296 ActionVerificationRecorded,
297 /// Bypass statistics were updated.
298 BypassRecorded,
299 /// Unconsulted-compilation statistics were updated.
300 UnconsultedRecorded,
301 /// Compiler invocation accounting was recorded.
302 CompilerInvocationRecorded,
303 /// An action result was stored.
304 ActionStored {
305 /// Path of the stored local action-result record.
306 path: PathBuf,
307 },
308 /// Result of an input-prediction lookup.
309 ActionPrediction {
310 /// Matching prediction, or `None` when none is known.
311 prediction: Option<ActionPrediction>,
312 },
313 /// An input prediction was recorded.
314 ActionPredictionRecorded,
315 /// Result of an executable-identity lookup.
316 ExecutableIdentity {
317 /// Captured output, or `None` when no identity is cached.
318 stdout: Option<Vec<u8>>,
319 },
320 /// The request failed without terminating the agent connection.
321 Error {
322 /// Human-readable failure description.
323 message: String,
324 },
325 /// A shim diagnostic was accepted for the session to surface.
326 ///
327 /// Sits past `Error` for the reason [`AgentRequest::RecordWarning`] sits
328 /// last: anywhere earlier renumbers the variants below it. New variants go
329 /// here.
330 WarningRecorded,
331 /// Digests recorded earlier for the requested file identities.
332 FileDigests {
333 /// Recorded digests or misses, in request order.
334 digests: Vec<Option<CacheDigest>>,
335 },
336 /// File digests were recorded.
337 FileDigestsRecorded,
338 /// State of an optional server-wide compilation promise.
339 ActionPromise {
340 /// Opaque lease owned by this client, when it should compile.
341 claim: Option<String>,
342 /// Completed prediction, when another client compiled first.
343 prediction: Option<ActionPrediction>,
344 },
345 /// A server-wide compilation promise was fulfilled or safely skipped.
346 ActionPromiseCompleted,
347}