1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
//! Pooled target-dir cache key (bd-session-history-remediation-ocv9i.11.2).
//!
//! Pooled Rust target dirs let independent builds share a warm cache instead of
//! each carving a fresh `target/` — but only when sharing is *safe*. Two builds
//! may reuse the same pooled dir iff every dimension that can change the
//! compiled artifacts matches: the originating repo, the toolchain, the target
//! triple, the cargo profile, and the enabled feature/runtime set. Mixing any
//! of these in one directory corrupts the cache (stale rebuilds, wrong-feature
//! artifacts, cross-project contamination) — the exact failure session history
//! attributed to ad-hoc shared target dirs.
//!
//! [`PooledTargetDimensions`] captures those dimensions and
//! [`PooledTargetKey`] derives a stable, filesystem-safe key from them. The
//! derivation is **pure** (no clock, no env) so it is unit-testable and
//! reproducible: identical dimensions always yield the same key, and any
//! difference yields a different one. The hash is domain-separated per field so
//! adjacent fields can never run together into a colliding key (e.g. repo `ab`
//! + toolchain `c` must not equal repo `a` + toolchain `bc`).
//!
//! The reaper that *evicts* stale pooled dirs, the active-build protection that
//! pins in-use keys, and the opt-out config knob are the daemon-side half of
//! this bead; this module is the shared key contract both the pooled-dir layout
//! and the reaper key off.
use blake3::Hasher;
/// Root directory name under which pooled target dirs live, mirroring the
/// `.rch-target` convention RCH already rewrites `CARGO_TARGET_DIR` to.
pub const POOLED_TARGET_ROOT: &str = ".rch-pool";
/// The dimensions that must match for two builds to safely share a pooled
/// target dir. Any field differing means a different (non-shared) pooled dir.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PooledTargetDimensions {
/// Stable repo identity (e.g. the canonical project root or its hash).
/// Prevents cross-project contamination of a shared pool.
pub repo_identity: String,
/// Toolchain channel or pinned version (e.g. `nightly-2025-11-01`).
pub toolchain: String,
/// Target triple (e.g. `x86_64-unknown-linux-gnu`, `wasm32-unknown-unknown`).
pub target_triple: String,
/// Cargo profile (e.g. `dev`, `release`).
pub profile: String,
/// Enabled cargo features. Order- and duplicate-insensitive (canonicalized
/// before hashing), so `["a","b"]` and `["b","a","a"]` share a pool.
pub features: Vec<String>,
/// Extra runtime dimension that changes artifacts (e.g. `bun`, `node`,
/// `wasm`), when relevant. `None` for a plain Rust build.
pub runtime: Option<String>,
}
impl PooledTargetDimensions {
/// Construct the minimal Rust build dimensions (no features/runtime).
#[must_use]
pub fn new(
repo_identity: impl Into<String>,
toolchain: impl Into<String>,
target_triple: impl Into<String>,
profile: impl Into<String>,
) -> Self {
Self {
repo_identity: repo_identity.into(),
toolchain: toolchain.into(),
target_triple: target_triple.into(),
profile: profile.into(),
features: Vec::new(),
runtime: None,
}
}
/// Set the feature set (builder style); canonicalized at hash time.
#[must_use]
pub fn with_features<I, S>(mut self, features: I) -> Self
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
self.features = features.into_iter().map(Into::into).collect();
self
}
/// Set the runtime dimension (builder style).
#[must_use]
pub fn with_runtime(mut self, runtime: impl Into<String>) -> Self {
self.runtime = Some(runtime.into());
self
}
/// Features sorted and de-duplicated — the canonical form used for hashing
/// and for an auditable view of what a pool key covers.
#[must_use]
pub fn canonical_features(&self) -> Vec<String> {
let mut f = self.features.clone();
f.sort();
f.dedup();
f
}
}
/// A derived pooled target-dir key. Stable across processes and hosts for the
/// same dimensions.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PooledTargetKey {
hex: String,
}
/// Feed one domain-separated, length-prefixed field into the hasher so adjacent
/// fields can never concatenate into a colliding pre-image.
fn absorb(hasher: &mut Hasher, tag: &str, value: &str) {
// tag\0 <len-as-decimal>\0 value\0 — the length prefix plus the NUL
// terminators make the framing unambiguous regardless of field contents.
hasher.update(tag.as_bytes());
hasher.update(b"\0");
hasher.update(value.len().to_string().as_bytes());
hasher.update(b"\0");
hasher.update(value.as_bytes());
hasher.update(b"\0");
}
impl PooledTargetKey {
/// Derive the key from dimensions. Pure and total.
#[must_use]
pub fn derive(dims: &PooledTargetDimensions) -> Self {
let mut hasher = Hasher::new();
absorb(&mut hasher, "v", "1"); // key-schema version, so we can rotate.
absorb(&mut hasher, "repo", &dims.repo_identity);
absorb(&mut hasher, "toolchain", &dims.toolchain);
absorb(&mut hasher, "triple", &dims.target_triple);
absorb(&mut hasher, "profile", &dims.profile);
absorb(
&mut hasher,
"runtime",
dims.runtime.as_deref().unwrap_or(""),
);
// Canonicalized features, count-prefixed then each length-framed, so the
// feature *set* (not its order or multiplicity) determines the key.
let features = dims.canonical_features();
absorb(&mut hasher, "features.n", &features.len().to_string());
for feature in &features {
absorb(&mut hasher, "feature", feature);
}
// 128-bit truncation: ample collision resistance for a cache key while
// keeping the directory name short.
let hex = hasher.finalize().to_hex();
Self {
hex: hex[..32].to_string(),
}
}
/// The stable hex key (filesystem-safe: lowercase hex only).
#[must_use]
pub fn as_str(&self) -> &str {
&self.hex
}
/// The pooled target directory name for this key, e.g.
/// `.rch-pool/<key>`. Always filesystem-safe.
#[must_use]
pub fn pooled_dir_name(&self) -> String {
format!("{POOLED_TARGET_ROOT}/{}", self.hex)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn base() -> PooledTargetDimensions {
PooledTargetDimensions::new(
"/data/projects/acme",
"nightly-2025-11-01",
"x86_64-unknown-linux-gnu",
"dev",
)
}
fn key_of(dims: &PooledTargetDimensions) -> String {
PooledTargetKey::derive(dims).as_str().to_string()
}
#[test]
fn cache_reuse_identical_dimensions_share_a_key() {
// The whole point of pooling: same dimensions => same warm cache.
assert_eq!(key_of(&base()), key_of(&base()));
}
#[test]
fn feature_order_and_duplicates_do_not_change_the_key() {
let a = base().with_features(["serde", "tokio"]);
let b = base().with_features(["tokio", "serde", "serde"]);
assert_eq!(key_of(&a), key_of(&b), "feature SET must determine the key");
}
#[test]
fn incompatible_features_get_separate_pools() {
// Different feature sets must NOT share a pool (wrong-feature artifacts).
let none = base();
let with = base().with_features(["tokio"]);
let other = base().with_features(["async-std"]);
assert_ne!(key_of(&none), key_of(&with));
assert_ne!(key_of(&with), key_of(&other));
}
#[test]
fn distinct_repos_never_share_a_pool() {
let acme = base();
let other = PooledTargetDimensions::new(
"/data/projects/other",
"nightly-2025-11-01",
"x86_64-unknown-linux-gnu",
"dev",
);
assert_ne!(
key_of(&acme),
key_of(&other),
"cross-project contamination guard"
);
}
#[test]
fn field_boundaries_are_unambiguous_no_contamination() {
// repo `ab` + toolchain `c` must not collide with repo `a` + toolchain `bc`.
let left = PooledTargetDimensions::new("ab", "c", "t", "dev");
let right = PooledTargetDimensions::new("a", "bc", "t", "dev");
assert_ne!(key_of(&left), key_of(&right));
// Likewise across the triple/profile boundary.
let p1 = PooledTargetDimensions::new("r", "t", "abc", "");
let p2 = PooledTargetDimensions::new("r", "t", "ab", "c");
assert_ne!(key_of(&p1), key_of(&p2));
}
#[test]
fn each_dimension_change_changes_the_key() {
let b = base();
let bk = key_of(&b);
assert_ne!(
bk,
key_of(&PooledTargetDimensions::new(
b.repo_identity.clone(),
"nightly-2026-01-01",
b.target_triple.clone(),
b.profile.clone(),
)),
"toolchain"
);
assert_ne!(
bk,
key_of(&PooledTargetDimensions::new(
b.repo_identity.clone(),
b.toolchain.clone(),
"wasm32-unknown-unknown",
b.profile.clone(),
)),
"triple"
);
assert_ne!(
bk,
key_of(&PooledTargetDimensions::new(
b.repo_identity.clone(),
b.toolchain.clone(),
b.target_triple.clone(),
"release",
)),
"profile"
);
assert_ne!(bk, key_of(&base().with_runtime("bun")), "runtime");
}
#[test]
fn key_is_filesystem_safe_and_stable_length() {
let key = PooledTargetKey::derive(&base().with_features(["a", "b"]));
let s = key.as_str();
assert_eq!(s.len(), 32, "128-bit truncated hex");
assert!(
s.chars()
.all(|c| c.is_ascii_hexdigit() && !c.is_ascii_uppercase()),
"lowercase hex only: {s}"
);
assert_eq!(key.pooled_dir_name(), format!(".rch-pool/{s}"));
}
#[test]
fn runtime_none_and_empty_string_are_equivalent() {
// A plain Rust build (runtime None) keys the same as runtime="" — the
// absence of a runtime dimension, not a distinct value.
let none = base();
let mut empty = base();
empty.runtime = Some(String::new());
assert_eq!(key_of(&none), key_of(&empty));
}
}