Skip to main content

gam_runtime/warm_start/
store.rs

1//! Filesystem store for warm-start entries.
2//!
3//! Each entry is a `(<runid>.json, <runid>.bin)` pair inside a per-key
4//! directory. Writes go through a temp-file → fsync → rename sequence so a
5//! crash mid-write never leaves a half-written entry visible to readers.
6//! Per-entry SHA-256 checksums catch any residual corruption.
7//!
8//! See [`crate::warm_start`] for the public API summary.
9
10use crate::warm_start::key::{Fingerprint, Fingerprinter};
11use serde::{Deserialize, Serialize};
12use sha2::{Digest, Sha256};
13use std::collections::HashMap;
14use std::fs;
15use std::io::{self, Write as _};
16use std::path::{Path, PathBuf};
17use std::sync::atomic::{AtomicU64, Ordering};
18use std::sync::{Arc, Mutex, OnceLock};
19use std::time::{Duration, SystemTime, UNIX_EPOCH};
20
21/// Record a best-effort maintenance failure instead of discarding it.
22///
23/// The warm-start store is a CACHE: a failed unlink, directory fsync, or
24/// eviction pass must never fail the fit that triggered it. Discarding the
25/// error outright — the previous `.ok()` — also discarded the only evidence
26/// that the cache is degrading, and a full disk, a read-only mount, and a
27/// permission fault all then present identically, as unexplained unbounded
28/// growth. `debug` keeps that observable without adding noise to a healthy run.
29fn log_best_effort<E: std::fmt::Display>(operation: &str, result: Result<(), E>) {
30    if let Err(error) = result {
31        log::debug!("warm-start store: {operation} failed: {error}");
32    }
33}
34
35/// On-disk schema version. Bump on incompatible format changes; old entries
36/// are then ignored at read time and evicted on the next save.
37pub(crate) const SCHEMA_VERSION: u32 = 1;
38
39/// How many times a save may lose the key-directory race before giving up.
40///
41/// A save that finds its key directory removed mid-sequence fails `NotFound`
42/// and must recreate the directory and retry. **This store no longer causes
43/// that**: `evict_overflow` used to `remove_dir` emptied key directories, which
44/// was the whole source of the gam#868 race, and that sweep is gone (see the
45/// comment at its former site) because it reclaimed no budgeted bytes. The
46/// retry is therefore defence-in-depth against a remover this process does not
47/// control — a sibling running an older build that still sweeps, or an operator
48/// cleaning the store root — rather than the mechanism correctness rests on.
49///
50/// The bound cannot be derived from the adversary, which is free to remove the
51/// directory at any rate, so no finite number of retries is provably
52/// sufficient; it exists to guarantee termination instead of describing the
53/// race. It is deliberately larger than the ONE retry this used to allow, which
54/// was justified by the claim that "the eviction window is one `remove_dir`
55/// syscall wide" — an assumption about relative timing rather than an
56/// invariant. Adding a single field to the metadata record was enough to defeat
57/// it (gam#2625), turning a deterministic test into a 2-in-5 flake. A save
58/// whose correctness depends on how many bytes the metadata occupies is not
59/// correct.
60const SAVE_KEY_DIR_RACE_RETRIES: u8 = 8;
61
62/// Default disk-budget for the whole warm-start store root (~1 GiB).
63pub(crate) const DEFAULT_SIZE_BUDGET_BYTES: u64 = 1024 * 1024 * 1024;
64
65/// Default TTL — entries untouched for this long are dropped.
66pub(crate) const DEFAULT_TTL_SECS: u64 = 60 * 60 * 24 * 30;
67
68#[derive(Debug, thiserror::Error)]
69pub enum StoreError {
70    #[error("io: {0}")]
71    Io(#[from] io::Error),
72    #[error("json: {0}")]
73    Json(#[from] serde_json::Error),
74}
75
76/// Entry returned from [`WarmStartStore::lookup`].
77#[derive(Debug, Clone)]
78pub struct WarmStartEntry {
79    pub payload: Vec<u8>,
80    pub objective: Option<f64>,
81    pub iteration: Option<u64>,
82    pub written_unix_secs: u64,
83    pub kind: EntryKind,
84}
85
86#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
87pub enum EntryKind {
88    /// Mid-fit checkpoint — fit was alive when written.
89    Checkpoint,
90    /// End-of-fit — fit terminated successfully.
91    Final,
92}
93
94#[derive(Debug, Clone, Serialize, Deserialize)]
95struct OnDiskMeta {
96    schema_version: u32,
97    /// Identity of the binary that wrote this entry — see
98    /// [`producer_identity`]. Compared on every read; a mismatch downgrades
99    /// [`EntryKind::Final`] to [`EntryKind::Checkpoint`], so one build can
100    /// never ship another build's terminal certificate.
101    ///
102    /// `#[serde(default)]` yields the empty string for entries written before
103    /// this field existed. That never matches a real identity, so legacy
104    /// entries are usable as seeds and never as certificates — which is the
105    /// correct reading of an entry whose producer is unknown.
106    #[serde(default)]
107    producer: String,
108    written_unix_secs: u64,
109    /// Nanosecond component of the write timestamp. Used to break ties in
110    /// LRU eviction so entries written within the same second don't sort
111    /// arbitrarily.
112    #[serde(default)]
113    written_nanos: u32,
114    objective: Option<f64>,
115    iteration: Option<u64>,
116    kind: EntryKind,
117    checksum_hex: String,
118    payload_bytes: u64,
119    /// Set when a lookup has reused this entry. Eviction keeps recently reused
120    /// entries behind never-hit writes when a tight budget forces a choice.
121    #[serde(default)]
122    accessed: bool,
123    /// Last-access timestamp (unix seconds + nanos). Distinct from the
124    /// immutable `written_*` creation stamp: a lookup that reuses this entry
125    /// bumps the access stamp (refreshing its TTL so hot entries survive)
126    /// WITHOUT touching `written_*`. Keeping the two separate is required for
127    /// correctness — `lookup_latest`/`entry_newer` order by the immutable
128    /// creation stamp, so if a read moved `written_*` forward the merely
129    /// *read* entry would masquerade as the most-recently-*written* one. Zero
130    /// (the serde default for entries written before this field existed, and
131    /// for never-reused entries) means "no access newer than creation": TTL
132    /// then falls back to `written_*`.
133    #[serde(default)]
134    accessed_unix_secs: u64,
135    #[serde(default)]
136    accessed_nanos: u32,
137}
138
139/// Effective activity timestamp (nanoseconds since the unix epoch): the more
140/// recent of the immutable creation stamp and the last-access stamp. TTL
141/// expiry is measured from this so a reused entry stays alive, while ordering
142/// (`entry_newer`) keys on `written_*` alone.
143fn meta_activity_nanos(meta: &OnDiskMeta) -> u128 {
144    let written = (meta.written_unix_secs as u128) * 1_000_000_000u128 + meta.written_nanos as u128;
145    let accessed =
146        (meta.accessed_unix_secs as u128) * 1_000_000_000u128 + meta.accessed_nanos as u128;
147    written.max(accessed)
148}
149
150#[derive(Debug, Clone)]
151pub struct StoreOptions {
152    pub size_budget_bytes: u64,
153    pub ttl: Duration,
154}
155
156impl Default for StoreOptions {
157    fn default() -> Self {
158        Self {
159            size_budget_bytes: DEFAULT_SIZE_BUDGET_BYTES,
160            ttl: Duration::from_secs(DEFAULT_TTL_SECS),
161        }
162    }
163}
164
165#[derive(Debug)]
166pub struct WarmStartStore {
167    root: PathBuf,
168    opts: StoreOptions,
169    /// Per-store metadata index. It is populated lazily and shared by clones so
170    /// checkpoint-heavy sessions do not repeatedly open every metadata JSON.
171    index: Arc<Mutex<MetadataIndex>>,
172    /// Approximate sum of bytes written under `root`. Used to throttle the
173    /// full directory-scanning eviction in [`Self::save_overwrite`] — see
174    /// `EVICT_EVERY_N_SAVES`. The counter resyncs to ground truth after every
175    /// triggered sweep. Shared across clones (`Arc`) so the eviction throttle
176    /// survives use through clone-shared configured store capabilities —
177    /// otherwise every fit reset the counter and ran a full eviction walk on
178    /// its first save (gam#1114).
179    byte_total: Arc<AtomicU64>,
180    /// Monotonically increasing save counter, shared across clones. Used
181    /// together with `byte_total` to throttle the eviction directory walk.
182    save_counter: Arc<AtomicU64>,
183    /// Root-directory mtime observed at the last completed eviction sweep,
184    /// shared across clones. When a throttled sweep fires while the store is
185    /// comfortably *under* the size budget, the only work left for it is a
186    /// TTL/byte resync over every key dir — an N-dir `read_dir` + `stat` walk
187    /// that, with thousands of fingerprint dirs in a long CI run, dominates
188    /// the per-32-save sweep even after the per-dir listing cache lands (the
189    /// residual #1114 walk). The root dir's mtime is bumped by the OS whenever
190    /// a key dir is created or removed under it, so an unchanged root mtime
191    /// means no key dir was added/dropped since our last sweep; combined with
192    /// a comfortably-under-budget byte total, the size-eviction walk is then a
193    /// guaranteed no-op and is skipped. TTL expiry of *existing* entries does
194    /// not change the root mtime, but it is already performed lazily on every
195    /// `lookup_with` and on the next root-changing save, so skipping it here is
196    /// behaviour-neutral (no entry the gate skips could be returned stale).
197    last_evict_root_mtime: Arc<Mutex<Option<SystemTime>>>,
198    /// Per-store test-only monotonic time offset (nanoseconds) added to every
199    /// `*_now` reading. Always zero in production. Tests mutate it through
200    /// [`Self::test_advance_time`] to simulate elapsed time without
201    /// `thread::sleep`. Lives on the store rather than as a process-wide
202    /// static so parallel tests with their own stores cannot pollute each
203    /// other's clocks — a global clock made `cargo test` non-deterministic
204    /// (gam test infra: one test's +1.5s TTL advance was bumping another
205    /// test's just-saved entry past its 1s TTL on immediate lookup).
206    test_time_offset_ns: AtomicU64,
207}
208
209impl Clone for WarmStartStore {
210    fn clone(&self) -> Self {
211        Self {
212            root: self.root.clone(),
213            opts: self.opts.clone(),
214            index: Arc::clone(&self.index),
215            // Throttle counters are shared across clones so the eviction
216            // directory walk stays throttled to every Nth save across every
217            // clone of one explicitly configured store capability.
218            byte_total: Arc::clone(&self.byte_total),
219            save_counter: Arc::clone(&self.save_counter),
220            last_evict_root_mtime: Arc::clone(&self.last_evict_root_mtime),
221            test_time_offset_ns: AtomicU64::new(self.test_time_offset_ns.load(Ordering::Relaxed)),
222        }
223    }
224}
225
226impl WarmStartStore {
227    /// Open (or create) a store rooted at `root`.
228    pub fn open(root: PathBuf, opts: StoreOptions) -> Result<Self, StoreError> {
229        fs::create_dir_all(&root)?;
230        Ok(Self {
231            root,
232            opts,
233            index: Arc::new(Mutex::new(MetadataIndex::default())),
234            byte_total: Arc::new(AtomicU64::new(0)),
235            save_counter: Arc::new(AtomicU64::new(0)),
236            last_evict_root_mtime: Arc::new(Mutex::new(None)),
237            test_time_offset_ns: AtomicU64::new(0),
238        })
239    }
240
241    pub fn root(&self) -> &Path {
242        &self.root
243    }
244
245    pub fn options(&self) -> &StoreOptions {
246        &self.opts
247    }
248
249    fn key_dir(&self, key: &Fingerprint) -> PathBuf {
250        self.root.join(key.to_hex())
251    }
252
253    /// Look up the best entry for `key`, or `None` if no valid entry exists.
254    ///
255    /// Selection: a [`EntryKind::Final`] entry outranks every
256    /// [`EntryKind::Checkpoint`]; among terminal writes the latest one wins
257    /// (which completed fit to resume is a provenance question — see
258    /// `entry_better`); among checkpoints the lowest `objective` wins, ties and
259    /// absent objectives falling back to the latest write.
260    /// Corrupt or schema-mismatched candidates are silently cleaned up and
261    /// skipped.
262    pub fn lookup(&self, key: &Fingerprint) -> Result<Option<WarmStartEntry>, StoreError> {
263        self.lookup_with(key, LookupMode::Best)
264    }
265
266    /// Look up the newest valid entry for `key`, or `None` if no valid entry
267    /// exists.
268    ///
269    /// Unlike [`Self::lookup`], this deliberately ignores objective values.
270    /// Use this for near-match seed namespaces where entries may come from
271    /// different folds, diseases, or row sets, and objective magnitudes are
272    /// not comparable. Exact-key resume should keep using [`Self::lookup`].
273    pub fn lookup_latest(&self, key: &Fingerprint) -> Result<Option<WarmStartEntry>, StoreError> {
274        self.lookup_with(key, LookupMode::Latest)
275    }
276
277    fn lookup_with(
278        &self,
279        key: &Fingerprint,
280        mode: LookupMode,
281    ) -> Result<Option<WarmStartEntry>, StoreError> {
282        let dir = self.key_dir(key);
283        if !dir.exists() {
284            // A stale in-memory cache entry could outlive its directory if
285            // another process evicted us. Drop it so we don't return data
286            // for a key whose backing files are gone.
287            lookup_cache_invalidate(&LookupCacheKey { fp: *key, mode });
288            self.metadata_index_remove_key(key);
289            return Ok(None);
290        }
291        // Fast path: if the same (key, mode) was looked up before and the
292        // chosen meta file's mtime is unchanged, return the cached entry
293        // without re-reading any JSON or re-checksumming the .bin payload.
294        // A separate writer (this process or another) bumps mtime on
295        // rename → mismatch → we fall through to the slow path. The TTL
296        // cutoff is also re-checked here against `nanos_now()` so a hot
297        // poll loop cannot keep returning an expired entry between eviction
298        // sweeps (eviction is throttled via `EVICT_EVERY_N_SAVES`).
299        let cache_key = LookupCacheKey { fp: *key, mode };
300        let now_nanos = self.nanos_now();
301        if let Some(hit) = lookup_cache_get(&cache_key) {
302            if let Ok(md) = fs::metadata(&hit.meta_path)
303                && md.modified().ok() == Some(hit.meta_mtime)
304            {
305                let expired = self.opts.ttl.as_nanos() > 0
306                    && now_nanos.saturating_sub(hit.write_nanos) >= self.opts.ttl.as_nanos();
307                if !expired {
308                    let entry = self.touch_lookup_hit(&hit.meta_path, hit.entry)?;
309                    return Ok(Some(entry));
310                }
311                lookup_cache_invalidate(&cache_key);
312                let bin = hit.meta_path.with_extension("bin");
313                log_best_effort(
314                    "removing the expired entry's metadata",
315                    fs::remove_file(&hit.meta_path),
316                );
317                log_best_effort(
318                    "removing the expired entry's payload",
319                    fs::remove_file(&bin),
320                );
321                // Removing the entry stales any cached directory listing.
322                self.metadata_index_remove(&hit.meta_path);
323                return Ok(None);
324            }
325            lookup_cache_invalidate(&cache_key);
326        }
327        // Resolve all valid entries for this key directory. `scan_key_dir`
328        // serves the listing from the per-store directory cache when the dir's
329        // mtime is unchanged since the last scan (no re-`read_dir`, no per-file
330        // `stat`, no JSON re-parse), and drops TTL-expired / corrupt entries in
331        // passing — exactly the syscall storm #1114 traced.
332        let mut best: Option<(OnDiskMeta, PathBuf)> = None;
333        for scanned in self.scan_key_dir(&dir, now_nanos) {
334            let take = match best {
335                None => true,
336                Some((ref cur, _)) => mode.better(&scanned.meta, cur),
337            };
338            if take {
339                best = Some((scanned.meta, scanned.meta_path));
340            }
341        }
342        let (meta, meta_path) = match best {
343            Some(b) => b,
344            None => {
345                lookup_cache_invalidate(&cache_key);
346                return Ok(None);
347            }
348        };
349        let bin_path = meta_path.with_extension("bin");
350        let payload = match fs::read(&bin_path) {
351            Ok(v) => v,
352            Err(_) => return Ok(None),
353        };
354        // Validate checksum
355        if checksum_hex(&payload) != meta.checksum_hex {
356            log_best_effort(
357                "removing the checksum-mismatched metadata",
358                fs::remove_file(&meta_path),
359            );
360            log_best_effort(
361                "removing the checksum-mismatched payload",
362                fs::remove_file(&bin_path),
363            );
364            lookup_cache_invalidate(&cache_key);
365            self.metadata_index_remove(&meta_path);
366            return Ok(None);
367        }
368        let entry = WarmStartEntry {
369            payload,
370            objective: meta.objective,
371            iteration: meta.iteration,
372            written_unix_secs: meta.written_unix_secs,
373            kind: meta.kind,
374        };
375        let (meta, entry) = self.touch_lookup_meta(&meta_path, meta, entry)?;
376        // Record (meta_path, mtime) → entry so subsequent identical lookups
377        // short-circuit until the meta file's mtime changes. The effective
378        // activity stamp (post-touch, so it reflects this very access) is
379        // cached alongside so the fast path can re-apply the TTL cutoff without
380        // re-reading the JSON.
381        if let Ok(md) = fs::metadata(&meta_path)
382            && let Ok(mtime) = md.modified()
383        {
384            let write_nanos = meta_activity_nanos(&meta);
385            lookup_cache_insert(
386                cache_key,
387                CachedLookup {
388                    meta_path: meta_path.clone(),
389                    meta_mtime: mtime,
390                    write_nanos,
391                    entry: entry.clone(),
392                },
393            );
394        }
395        Ok(Some(entry))
396    }
397
398    /// Save a new entry with a fresh run-id. Returns the run-id (caller may
399    /// hand it to [`Self::save_overwrite`] for periodic in-place updates).
400    pub fn save(
401        &self,
402        key: &Fingerprint,
403        payload: &[u8],
404        objective: Option<f64>,
405        iteration: Option<u64>,
406        kind: EntryKind,
407    ) -> Result<String, StoreError> {
408        let run_id = self.fresh_run_id();
409        self.save_overwrite(key, &run_id, payload, objective, iteration, kind)?;
410        Ok(run_id)
411    }
412
413    /// Save under a specific run-id (overwrites an existing entry with the
414    /// same id atomically).
415    pub fn save_overwrite(
416        &self,
417        key: &Fingerprint,
418        run_id: &str,
419        payload: &[u8],
420        objective: Option<f64>,
421        iteration: Option<u64>,
422        kind: EntryKind,
423    ) -> Result<(), StoreError> {
424        // Any new write under this key may change which entry wins both
425        // `LookupMode::Best` and `LookupMode::Latest`, so drop both cached
426        // rows before touching disk. A pure save_overwrite of the same
427        // run_id would also bump mtime and self-invalidate, but a save()
428        // with a fresh run_id leaves the old meta file unchanged — only
429        // explicit invalidation catches that.
430        lookup_cache_invalidate(&LookupCacheKey {
431            fp: *key,
432            mode: LookupMode::Best,
433        });
434        lookup_cache_invalidate(&LookupCacheKey {
435            fp: *key,
436            mode: LookupMode::Latest,
437        });
438        let dir = self.key_dir(key);
439        let pid = std::process::id();
440        // 1. Compute checksum from payload.
441        let checksum = checksum_hex(payload);
442        let objective_finite = objective.filter(|o| o.is_finite());
443        // The meta's `written_unix_secs`/`written_nanos` are captured INSIDE the
444        // write loop — just before the meta_tmp is written, AFTER the bin write
445        // has completed. The stored timestamp drives the TTL contract: an
446        // entry's clock should start ticking from when the entry becomes
447        // (nearly) visible to lookups, not from `save_overwrite`'s entry. On
448        // slow disks the bin write + fsync + rename can take longer than the
449        // entire TTL window itself (the warm-start test fixture pins TTL=1s
450        // while the ext4-backed CI image takes >1s on small writes), so an
451        // up-front stamp causes the entry to be classified as expired the
452        // moment `save_overwrite` returns. Pushing the stamp past the bin
453        // fsync removes that systemic drift from the cost of writing the
454        // entry — only the meta fsync + final rename + dir fsync still
455        // elapse between the stamp and the entry becoming visible.
456
457        // 3. Write both temp files and atomically rename them into place. The
458        //    whole "ensure dir → write temps → rename" sequence is retried once
459        //    as a unit on `ErrorKind::NotFound`, because a concurrent process'
460        //    `evict_overflow` can `remove_dir` this key dir the instant it
461        //    observes it empty (store.rs `evict_overflow`, "Sweep now-empty key
462        //    dirs"). That removal races every write step here: it can vanish the
463        //    dir after `create_dir_all` but before a temp `File::create`, or
464        //    take the dir *and our just-written temps with it* before the
465        //    rename, surfacing as `io: No such file or directory (os error 2)`
466        //    under parallel CV / bootstrap fitting (gam#868). Retrying the
467        //    sequence (not an individual step) is the only correct response: a
468        //    bare rename retry can't recover once the source temp was swept with
469        //    the dir, so we recreate the dir and rewrite the temps from the
470        //    in-memory `payload` / `meta_json` we still hold. A single retry is
471        //    sufficient — the eviction window is one `remove_dir` syscall wide —
472        //    and a second genuine `NotFound` is propagated as before.
473        let nonce = self.nanos_now();
474        let bin_final = dir.join(format!("{run_id}.bin"));
475        let meta_final = dir.join(format!("{run_id}.json"));
476        let mut attempt = 0u8;
477        // Resolve the producing build's identity ONCE, before the write/retry
478        // sequence: it is constant for the process, and computing it per attempt
479        // would put an allocation (and, on the first call in the process, two
480        // filesystem syscalls) inside the window a concurrent eviction races.
481        let producer = producer_identity();
482        let build_meta_json = |secs: u64, subsec_nanos: u32| -> Result<Vec<u8>, StoreError> {
483            let meta = OnDiskMeta {
484                schema_version: SCHEMA_VERSION,
485                producer: producer.to_string(),
486                written_unix_secs: secs,
487                written_nanos: subsec_nanos,
488                objective: objective_finite,
489                iteration,
490                kind,
491                checksum_hex: checksum.clone(),
492                payload_bytes: payload.len() as u64,
493                accessed: false,
494                accessed_unix_secs: 0,
495                accessed_nanos: 0,
496            };
497            Ok(serde_json::to_vec_pretty(&meta)?)
498        };
499        loop {
500            let bin_tmp = dir.join(format!("{run_id}.bin.tmp.{pid}.{nonce}.{attempt}"));
501            let meta_tmp = dir.join(format!("{run_id}.json.tmp.{pid}.{nonce}.{attempt}"));
502            let stamp_fn = || self.unix_now_parts();
503            let build_meta_for_io = |secs: u64, subsec_nanos: u32| -> io::Result<Vec<u8>> {
504                build_meta_json(secs, subsec_nanos)
505                    .map_err(|e| io::Error::other(format!("meta build: {e:?}")))
506            };
507            match write_and_promote_entry(&EntryWrite {
508                dir: &dir,
509                bin_tmp: &bin_tmp,
510                meta_tmp: &meta_tmp,
511                payload,
512                bin_final: &bin_final,
513                meta_final: &meta_final,
514                stamp_fn: &stamp_fn,
515                build_meta_json: &build_meta_for_io,
516            }) {
517                Ok(()) => break,
518                Err(e)
519                    if e.kind() == io::ErrorKind::NotFound
520                        && attempt < SAVE_KEY_DIR_RACE_RETRIES =>
521                {
522                    // A sibling process' eviction removed the key dir mid-write.
523                    // Clean up any partial temps, then retry the whole sequence
524                    // after recreating the dir inside `write_and_promote_entry`.
525                    log_best_effort(
526                        "removing a partial payload temp after a racing eviction",
527                        fs::remove_file(&bin_tmp),
528                    );
529                    log_best_effort(
530                        "removing a partial metadata temp after a racing eviction",
531                        fs::remove_file(&meta_tmp),
532                    );
533                    attempt += 1;
534                    continue;
535                }
536                Err(e) => {
537                    log_best_effort(
538                        "removing the payload temp after a failed write",
539                        fs::remove_file(&bin_tmp),
540                    );
541                    log_best_effort(
542                        "removing the metadata temp after a failed write",
543                        fs::remove_file(&meta_tmp),
544                    );
545                    log_best_effort(
546                        "removing the promoted payload after a failed write",
547                        fs::remove_file(&bin_final),
548                    );
549                    return Err(StoreError::Io(e));
550                }
551            }
552        }
553        // Fsync the containing directory so the rename itself is durable
554        // across a power loss / hard crash. fs::File::sync_all on the
555        // payload only guarantees the file content reaches disk; without
556        // also fsyncing the directory inode, the *rename* (which is what
557        // makes the entry visible to lookups) can be lost. Best-effort on
558        // platforms where opening a directory for fsync is not supported.
559        if let Ok(d) = fs::File::open(&dir) {
560            log_best_effort("fsyncing the key directory after promote", d.sync_all());
561        }
562        log_best_effort(
563            "refreshing the metadata index after promote",
564            self.metadata_index_upsert(&meta_final, &bin_final),
565        );
566        // 5. Best-effort eviction; failure here is non-fatal. Throttle the
567        // full directory scan: maintain a process-wide approximate byte
568        // total and only run eviction when the per-save counter wraps
569        // `EVICT_EVERY_N_SAVES` as a drift-resync trigger, or on the very
570        // first save (so a fresh process inheriting a populated store root
571        // sweeps once). The
572        // counter is best-effort: it can drift relative to disk truth
573        // because other processes may write/evict, but every triggered
574        // sweep resyncs it to ground truth.
575        //
576        // The counter throttle alone does NOT bound the store: a burst of up
577        // to `EVICT_EVERY_N_SAVES - 1` saves between two counter-triggered
578        // sweeps can push the footprint arbitrarily far past the budget (e.g.
579        // 31 payloads under a budget that fits a handful). Bound it by also
580        // sweeping whenever the approximate byte total already exceeds the
581        // budget — a single cheap atomic load, so the common under-budget path
582        // still walks the directory only every Nth save, while an over-budget
583        // total forces the very next save to reclaim it. The eviction resyncs
584        // `byte_total` to ground truth, so this fires once per crossing rather
585        // than on every subsequent save.
586        let approx_added = payload.len() as u64 + APPROX_META_BYTES;
587        let new_total = self.byte_total.fetch_add(approx_added, Ordering::Relaxed) + approx_added;
588        let n = self.save_counter.fetch_add(1, Ordering::Relaxed);
589        if n == 0
590            || n.is_multiple_of(EVICT_EVERY_N_SAVES)
591            || new_total > self.opts.size_budget_bytes
592        {
593            log_best_effort("the post-save eviction pass", self.evict_overflow());
594        }
595        Ok(())
596    }
597
598    /// Drop entries older than TTL, then evict by recorded write-time
599    /// ascending until total bytes ≤ `opts.size_budget_bytes`. Idempotent;
600    /// safe under concurrent processes (worst case some entries are
601    /// double-removed, which is a no-op).
602    ///
603    /// Sort key is the `(written_unix_secs, written_nanos)` recorded in
604    /// each entry's meta, not the filesystem mtime — at second-resolution
605    /// mtime, batches of writes within the same second would sort
606    /// arbitrarily and could evict the most recent entry.
607    pub fn evict_overflow(&self) -> Result<(), StoreError> {
608        // Root-mtime short-circuit. A throttled sweep that fires while the
609        // approximate byte total is comfortably under budget has no size
610        // eviction to do; its only residual work is the TTL/byte resync walk
611        // over every key dir. The root mtime is bumped whenever a key dir is
612        // created/removed beneath it, so if it is unchanged since our last
613        // completed sweep AND we are under budget, no key dir was added or
614        // dropped and the size-eviction walk is provably a no-op — skip the
615        // N-dir `read_dir`+`stat` storm. (TTL expiry of existing entries does
616        // not move the root mtime, but it is already enforced lazily on every
617        // `lookup_with` and on the next root-changing save, so the gate cannot
618        // surface a stale entry.) This trims the residual #1114 walk in long
619        // refit-heavy CI runs where thousands of fingerprint dirs accumulate.
620        let current_root_mtime = fs::metadata(&self.root)
621            .ok()
622            .and_then(|m| m.modified().ok());
623        if self.byte_total.load(Ordering::Relaxed) <= self.opts.size_budget_bytes
624            && let Some(now_mtime) = current_root_mtime
625            && let Ok(last) = self.last_evict_root_mtime.lock()
626            && *last == Some(now_mtime)
627        {
628            return Ok(());
629        }
630        let read_dir = match fs::read_dir(&self.root) {
631            Ok(rd) => rd,
632            Err(_) => return Ok(()),
633        };
634        // Collect (meta_path, bin_path, total_bytes, write_nanos_since_epoch, accessed).
635        let mut all: Vec<(PathBuf, PathBuf, u64, u128, bool)> = Vec::new();
636        let now_nanos = self.nanos_now();
637        for key_dir_entry in read_dir {
638            let key_dir = match key_dir_entry {
639                Ok(e) => e.path(),
640                Err(_) => continue,
641            };
642            if !key_dir.is_dir() {
643                continue;
644            }
645            // `scan_key_dir` reuses the per-store directory-listing cache when
646            // the key dir's mtime is unchanged, so an unchanged dir costs a
647            // single `stat` rather than a `read_dir` + per-file `stat` + JSON
648            // read of every entry. It also sweeps foreign tmp files and drops
649            // corrupt / TTL-expired entries, mirroring the old inline pass.
650            let scanned = self.scan_key_dir(&key_dir, now_nanos);
651            for entry in &scanned {
652                let write_nanos = (entry.meta.written_unix_secs as u128) * 1_000_000_000u128
653                    + entry.meta.written_nanos as u128;
654                let total_bytes = entry.meta_len + entry.bin_len;
655                all.push((
656                    entry.meta_path.clone(),
657                    entry.bin_path.clone(),
658                    total_bytes,
659                    write_nanos,
660                    entry.meta.accessed,
661                ));
662            }
663            // An emptied key dir is deliberately LEFT IN PLACE.
664            //
665            // Removing it here was the sole cause of the gam#868 write race: the
666            // `remove_dir` could land anywhere inside a concurrent save's
667            // `create_dir_all` → write → rename sequence, so that save failed
668            // `NotFound` and needed the recreate-and-retry path below to survive.
669            //
670            // It bought nothing. This branch only ran once the directory was
671            // observed EMPTY, so it reclaimed no entry bytes — and entry bytes are
672            // the only thing the budget governs (`total` sums `meta_len + bin_len`
673            // over scanned entries, and an empty dir contributes zero). The cost of
674            // keeping it is one empty directory per key ever fitted, under
675            // `temp_dir()`; the cost of removing it was a correctness race against
676            // every sibling writer, tolerated by a retry whose sufficiency depended
677            // on the write sequence staying short enough (gam#2625 made it one field
678            // longer and turned a deterministic test into a 2-in-5 flake).
679            //
680            // Trading a race for some inodes is the right direction, so the race is
681            // not created in the first place. The retry in `save` is kept as
682            // defence-in-depth — a sibling process running an OLDER build still
683            // sweeps, and nothing stops an operator from removing a directory — but
684            // it is no longer the mechanism this store relies on.
685            if scanned.is_empty()
686                && let Ok(mut index) = self.index.lock()
687            {
688                index.by_key_dir.remove(&key_dir);
689            }
690        }
691        let total: u64 = all.iter().map(|e| e.2).sum();
692        if total <= self.opts.size_budget_bytes {
693            // Resync the approximate byte counter even when no eviction was
694            // needed. Otherwise the in-memory `byte_total` only grows (it
695            // never observes deletions made by sibling processes or
696            // expiration sweeps), so after enough saves `new_total` exceeds
697            // the budget on every call and triggers a full directory walk
698            // on every save instead of every Nth save.
699            self.byte_total.store(total, Ordering::Relaxed);
700            // Record the root mtime observed by this completed under-budget
701            // sweep so a subsequent throttled sweep can short-circuit while
702            // the root is unchanged. Re-read after the walk: any key dir the
703            // walk removed (empty-dir sweep above) bumps the root mtime, and
704            // capturing the post-walk value keeps the gate from skipping a
705            // genuinely-changed root on the next call.
706            if let (Ok(mut last), Some(m)) = (
707                self.last_evict_root_mtime.lock(),
708                fs::metadata(&self.root)
709                    .ok()
710                    .and_then(|m| m.modified().ok()),
711            ) {
712                *last = Some(m);
713            }
714            return Ok(());
715        }
716        all.sort_by(|a, b| {
717            a.4.cmp(&b.4)
718                .then_with(|| a.3.cmp(&b.3))
719                .then_with(|| a.0.cmp(&b.0))
720        });
721        let mut remaining = total;
722        for (meta, bin, bytes, _, _) in all.into_iter() {
723            if remaining <= self.opts.size_budget_bytes {
724                break;
725            }
726            log_best_effort(
727                "removing the evicted entry's metadata",
728                fs::remove_file(&meta),
729            );
730            log_best_effort(
731                "removing the evicted entry's payload",
732                fs::remove_file(&bin),
733            );
734            self.metadata_index_remove(&meta);
735            remaining = remaining.saturating_sub(bytes);
736        }
737        // Resync the approximate byte counter to ground truth. Subsequent
738        // saves increment from here until the next sweep.
739        self.byte_total.store(remaining, Ordering::Relaxed);
740        Ok(())
741    }
742}
743
744/// Ensure the key dir exists, write the `.bin` and `.json` temp files, and
745/// atomically rename both into place. Returns the raw `io::Error` (not a
746/// `StoreError`) so the caller can branch on `ErrorKind::NotFound` to retry the
747/// whole sequence after a concurrent eviction removed the dir mid-write
748/// (gam#868). Idempotent across a retry: every path is derived from the caller's
749/// stable args and the temps are rewritten from the in-memory payload, so a
750/// second pass into a freshly recreated dir produces the same final entry.
751///
752/// `.bin` is renamed before `.json` so a meta-pointing-to-missing-bin window is
753/// impossible on the happy path; a reader that catches `.bin`-missing treats the
754/// entry as corrupt and cleans it up.
755struct EntryWrite<'a> {
756    dir: &'a Path,
757    bin_tmp: &'a Path,
758    meta_tmp: &'a Path,
759    payload: &'a [u8],
760    bin_final: &'a Path,
761    meta_final: &'a Path,
762    /// Read the current wall clock as `(unix_secs, subsec_nanos)`. Called
763    /// AFTER the bin write and bin fsync complete (and after the bin rename)
764    /// so the recorded write time tracks when the entry actually becomes
765    /// (nearly) visible, not when `save_overwrite` was first invoked. On
766    /// slow disks the bin fsync can dominate save latency and a pre-write
767    /// stamp would burn TTL the caller never sees.
768    stamp_fn: &'a dyn Fn() -> (u64, u32),
769    /// Build the meta JSON given the captured `(secs, subsec_nanos)`. The
770    /// closure folds those values into `OnDiskMeta` and serializes it.
771    build_meta_json: &'a dyn Fn(u64, u32) -> io::Result<Vec<u8>>,
772}
773
774fn write_and_promote_entry(w: &EntryWrite<'_>) -> io::Result<()> {
775    // Recreate the dir up front: on the first attempt this is the original
776    // `create_dir_all`; on a retry it re-establishes the dir a sibling
777    // process' eviction removed.
778    fs::create_dir_all(w.dir)?;
779    {
780        let mut f = fs::File::create(w.bin_tmp)?;
781        f.write_all(w.payload)?;
782        log_best_effort("fsyncing the payload temp", f.sync_all());
783    }
784    // Promote the bin first so a crash between the two renames leaves an
785    // orphan .bin (cleaned up by `evict_overflow`) rather than a meta
786    // pointing at a missing .bin (which the reader would mark corrupt).
787    fs::rename(w.bin_tmp, w.bin_final)?;
788    // Stamp the meta AFTER the bin promotion. This is the latest moment the
789    // timestamp can still be inlined into the meta JSON. The remaining gap
790    // before the entry is visible to lookups is one meta write+fsync + the
791    // meta rename + the caller's directory fsync — all bounded, so TTL is
792    // measured from a near-visible moment instead of from the entry to
793    // `save_overwrite`.
794    let (secs, subsec_nanos) = (w.stamp_fn)();
795    let meta_json = (w.build_meta_json)(secs, subsec_nanos)?;
796    {
797        let mut f = fs::File::create(w.meta_tmp)?;
798        f.write_all(&meta_json)?;
799        log_best_effort("fsyncing the metadata temp", f.sync_all());
800    }
801    if let Err(e) = fs::rename(w.meta_tmp, w.meta_final) {
802        // Roll back the bin we just promoted to avoid orphaning it, then
803        // surface the error so the caller can retry or fail.
804        log_best_effort(
805            "rolling back the promoted payload after a failed metadata rename",
806            fs::remove_file(w.bin_final),
807        );
808        return Err(e);
809    }
810    Ok(())
811}
812
813/// Conservative meta-JSON size used by the throttled save counter. Real
814/// meta files run ~250-400 bytes after pretty-printing; overestimating
815/// just means the throttle fires slightly earlier, never later.
816const APPROX_META_BYTES: u64 = 512;
817
818/// How [`WarmStartStore::lookup_with`] ranks candidate entries.
819#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
820enum LookupMode {
821    /// Ranked by [`entry_better`]: terminal writes first (latest one), then
822    /// checkpoints by lowest objective.
823    Best,
824    /// Newest write wins; objectives ignored.
825    Latest,
826}
827
828impl LookupMode {
829    fn better(&self, candidate: &OnDiskMeta, current: &OnDiskMeta) -> bool {
830        match self {
831            LookupMode::Best => entry_better(candidate, current),
832            LookupMode::Latest => entry_newer(candidate, current),
833        }
834    }
835}
836
837#[derive(Clone, Copy, PartialEq, Eq, Hash)]
838struct LookupCacheKey {
839    fp: Fingerprint,
840    mode: LookupMode,
841}
842
843#[derive(Clone)]
844struct CachedLookup {
845    meta_path: PathBuf,
846    meta_mtime: SystemTime,
847    /// Full-precision nanosecond write timestamp from the on-disk meta,
848    /// kept alongside `entry.written_unix_secs` so the fast path can apply
849    /// the same TTL cutoff as `evict_overflow` without re-reading the JSON.
850    write_nanos: u128,
851    entry: WarmStartEntry,
852}
853
854#[derive(Debug, Default)]
855struct MetadataIndex {
856    by_meta_path: HashMap<PathBuf, IndexedMeta>,
857    /// Per-key-directory cached listing, keyed by the directory's mtime.
858    ///
859    /// A key dir's mtime is bumped by the OS whenever an entry is created,
860    /// renamed, or removed inside it (which is exactly when our entries
861    /// change). So a matching `dir_mtime` means the set of `<runid>.{json,bin}`
862    /// pairs is byte-for-byte what we scanned last time, letting
863    /// [`WarmStartStore::scan_key_dir`] return the cached `Vec<ScannedEntry>`
864    /// without a fresh `read_dir` or any per-file `stat`/JSON read. This is
865    /// what kills the metadata-syscall storm in repeated `lookup_with` /
866    /// `evict_overflow` calls within one fit (gam#1114).
867    by_key_dir: HashMap<PathBuf, ScannedDir>,
868}
869
870#[derive(Debug, Clone)]
871struct IndexedMeta {
872    meta_mtime: SystemTime,
873    meta_len: u64,
874    bin_len: u64,
875    meta: OnDiskMeta,
876}
877
878impl IndexedMeta {
879    fn matches(&self, meta_md: &fs::Metadata, bin_md: &fs::Metadata) -> bool {
880        meta_md.modified().ok() == Some(self.meta_mtime)
881            && meta_md.len() == self.meta_len
882            && bin_md.len() == self.bin_len
883    }
884}
885
886/// Cached result of scanning one key directory: its mtime at scan time plus
887/// the resolved entries. Reused verbatim while the dir's mtime is unchanged.
888#[derive(Debug, Clone)]
889struct ScannedDir {
890    dir_mtime: SystemTime,
891    entries: Vec<ScannedEntry>,
892}
893
894/// One resolved `(meta, bin)` pair discovered during a key-dir scan. Carries
895/// everything both the lookup ranker and the eviction sweep need so neither
896/// has to re-`stat` or re-read the files when the dir is unchanged.
897#[derive(Debug, Clone)]
898struct ScannedEntry {
899    meta_path: PathBuf,
900    bin_path: PathBuf,
901    meta_len: u64,
902    bin_len: u64,
903    meta_mtime: Option<SystemTime>,
904    bin_mtime: Option<SystemTime>,
905    meta: OnDiskMeta,
906}
907
908impl ScannedEntry {
909    fn matches_files(&self, meta_md: &fs::Metadata, bin_md: &fs::Metadata) -> bool {
910        meta_md.len() == self.meta_len
911            && bin_md.len() == self.bin_len
912            && meta_md.modified().ok() == self.meta_mtime
913            && bin_md.modified().ok() == self.bin_mtime
914    }
915}
916
917/// True iff a meta with the given (`secs`, `nanos`) write timestamp is older
918/// than `ttl` relative to `now_nanos`. Mirrors the cutoff in
919/// [`WarmStartStore::evict_overflow`] so `lookup_with` cannot return an entry
920/// that the eviction sweep would have dropped.
921/// TTL expiry test. `activity_nanos` is the entry's effective activity stamp
922/// (`meta_activity_nanos`: the more recent of creation and last access), so a
923/// reused entry's TTL restarts from its last lookup rather than its creation.
924const fn meta_expired(activity_nanos: u128, ttl: Duration, now_nanos: u128) -> bool {
925    let ttl_nanos = ttl.as_nanos();
926    if ttl_nanos == 0 {
927        return false;
928    }
929    now_nanos.saturating_sub(activity_nanos) >= ttl_nanos
930}
931
932/// Process-wide in-memory cache for [`WarmStartStore::lookup_with`]. Hot poll
933/// loops hit the same (key, mode) repeatedly between writes, so caching the
934/// resolved entry behind an mtime check eliminates the per-call directory
935/// walk, JSON parse, and SHA-256 recomputation. Mtime mismatch — including
936/// writes from a sibling process — invalidates the row and falls back to
937/// the full slow path.
938fn lookup_cache() -> &'static Mutex<HashMap<LookupCacheKey, CachedLookup>> {
939    static CACHE: OnceLock<Mutex<HashMap<LookupCacheKey, CachedLookup>>> = OnceLock::new();
940    CACHE.get_or_init(|| Mutex::new(HashMap::new()))
941}
942
943const LOOKUP_CACHE_MAX_ENTRIES: usize = 128;
944const LOOKUP_CACHE_MAX_BYTES: usize = 256 * 1024 * 1024;
945
946const fn cached_lookup_resident_bytes(value: &CachedLookup) -> usize {
947    std::mem::size_of::<CachedLookup>().saturating_add(value.entry.payload.capacity())
948}
949
950fn lookup_cache_get(key: &LookupCacheKey) -> Option<CachedLookup> {
951    let guard = lookup_cache().lock().ok()?;
952    guard.get(key).cloned()
953}
954
955fn lookup_cache_insert(key: LookupCacheKey, val: CachedLookup) {
956    if let Ok(mut guard) = lookup_cache().lock() {
957        let new_bytes = cached_lookup_resident_bytes(&val);
958        if new_bytes > LOOKUP_CACHE_MAX_BYTES {
959            return;
960        }
961        let mut resident_bytes: usize = guard.values().map(cached_lookup_resident_bytes).sum();
962        if let Some(old) = guard.remove(&key) {
963            resident_bytes = resident_bytes.saturating_sub(cached_lookup_resident_bytes(&old));
964        }
965        while guard.len() >= LOOKUP_CACHE_MAX_ENTRIES
966            || resident_bytes.saturating_add(new_bytes) > LOOKUP_CACHE_MAX_BYTES
967        {
968            let oldest = guard
969                .iter()
970                .min_by_key(|(_, cached)| cached.write_nanos)
971                .map(|(old_key, _)| *old_key);
972            let Some(oldest) = oldest else {
973                break;
974            };
975            if let Some(old) = guard.remove(&oldest) {
976                resident_bytes = resident_bytes.saturating_sub(cached_lookup_resident_bytes(&old));
977            }
978        }
979        guard.insert(key, val);
980    }
981}
982
983fn lookup_cache_invalidate(key: &LookupCacheKey) {
984    if let Ok(mut guard) = lookup_cache().lock() {
985        guard.remove(key);
986    }
987}
988
989/// Run a full [`WarmStartStore::evict_overflow`] sweep every Nth save. The
990/// budget can briefly overshoot by K-1 payloads, which the next sweep
991/// reclaims. K=32 keeps the amortized cost negligible on hot checkpoint
992/// paths while still bounding worst-case disk drift.
993const EVICT_EVERY_N_SAVES: u64 = 32;
994
995fn parse_tmp_pid(name: &str) -> Option<u32> {
996    // Names look like "<runid>.bin.tmp.<pid>.<nonce>.<attempt>" or
997    // "<runid>.json.tmp.<pid>.<nonce>.<attempt>" (the trailing retry-attempt
998    // suffix is irrelevant here — only the first token after ".tmp." is the pid).
999    let tail = name.split(".tmp.").nth(1)?;
1000    let pid_str = tail.split('.').next()?;
1001    pid_str.parse::<u32>().ok()
1002}
1003
1004/// Identity of the binary running right now, as an opaque hex digest.
1005///
1006/// An [`EntryKind::Final`] entry is a claim that a converged optimization
1007/// ended at this payload, *judged against the criterion the writing code
1008/// implements*. Nothing in the key identifies that code — the fingerprint is
1009/// over `(data, spec)` only — so two different builds fitting the same model
1010/// share entries, and the first run of build B could resume build A's terminus
1011/// and ship it at zero outer iterations. The fit then depends on machine
1012/// history rather than on this build's own criterion (gam#2625).
1013///
1014/// The token deliberately does NOT come from a build script. Cargo records
1015/// every `cargo:rustc-env` in the crate fingerprint, so a code-identity value
1016/// emitted that way dirties the lib fingerprint on every build and forces a
1017/// full recompile of the workspace; the root `build.rs` measures that cost and
1018/// forbids it. The running executable's own path, length and mtime need no
1019/// build-time support at all, and `current_exe` reaches them through an OS
1020/// primitive rather than the banned `env::var` family — the same reasoning
1021/// that already routes the persistent store root through `env::temp_dir`.
1022///
1023/// This is a PROXY for "the same code", and its error direction is chosen: a
1024/// rebuild that changes nothing semantically still moves the mtime and costs a
1025/// refit, while a rebuild that *does* change the criterion can never go
1026/// unnoticed. A cache miss costs time; a wrongly-certified fit costs
1027/// correctness. The token is therefore sufficient for the property claimed —
1028/// no entry is certified across builds — and is not claimed to be a minimal
1029/// one.
1030///
1031/// If the executable cannot be identified the token falls back to a
1032/// per-process nonce. That is the conservative degradation rather than a
1033/// weakening: a process can still resume the checkpoints *it* wrote, and no
1034/// entry is ever certified across processes on a platform where the producing
1035/// build cannot be established.
1036fn producer_identity() -> &'static str {
1037    static ID: OnceLock<String> = OnceLock::new();
1038    ID.get_or_init(|| {
1039        let mut fp = Fingerprinter::new();
1040        fp.absorb_str(
1041            b"warm-start-producer-runtime-version",
1042            env!("CARGO_PKG_VERSION"),
1043        );
1044        match std::env::current_exe() {
1045            Ok(path) => {
1046                fp.absorb_str(b"warm-start-producer-exe-path", &path.to_string_lossy());
1047                match fs::metadata(&path) {
1048                    Ok(md) => {
1049                        fp.absorb_u64(b"warm-start-producer-exe-len", md.len());
1050                        let mtime_nanos = md
1051                            .modified()
1052                            .ok()
1053                            .and_then(|t| t.duration_since(UNIX_EPOCH).ok())
1054                            .map(|d| d.as_nanos())
1055                            .unwrap_or_default();
1056                        fp.absorb_str(
1057                            b"warm-start-producer-exe-mtime-nanos",
1058                            &mtime_nanos.to_string(),
1059                        );
1060                    }
1061                    Err(error) => {
1062                        log::debug!(
1063                            "warm-start store: cannot stat the running executable ({error}); \
1064                             falling back to a per-process producer identity, so no entry will \
1065                             be certified across processes"
1066                        );
1067                        fp.absorb_u64(b"warm-start-producer-pid", u64::from(std::process::id()));
1068                        fp.absorb_str(
1069                            b"warm-start-producer-process-nonce",
1070                            &nanos_since_epoch().to_string(),
1071                        );
1072                    }
1073                }
1074            }
1075            Err(error) => {
1076                log::debug!(
1077                    "warm-start store: cannot locate the running executable ({error}); falling \
1078                     back to a per-process producer identity, so no entry will be certified \
1079                     across processes"
1080                );
1081                fp.absorb_u64(b"warm-start-producer-pid", u64::from(std::process::id()));
1082                fp.absorb_str(
1083                    b"warm-start-producer-process-nonce",
1084                    &nanos_since_epoch().to_string(),
1085                );
1086            }
1087        }
1088        fp.finalize().to_hex()
1089    })
1090    .as_str()
1091}
1092
1093/// Wall-clock nanoseconds since the epoch, or `0` if the clock is before it.
1094///
1095/// Only ever used to salt a fallback producer identity, never to make a
1096/// decision about a fit, so a clock that cannot be read degrades to a constant
1097/// rather than failing.
1098fn nanos_since_epoch() -> u128 {
1099    SystemTime::now()
1100        .duration_since(UNIX_EPOCH)
1101        .map(|d| d.as_nanos())
1102        .unwrap_or_default()
1103}
1104
1105/// Read one entry's metadata, downgrading a terminal certificate that this
1106/// build did not produce.
1107///
1108/// This is the ONLY place metadata is deserialized, which is why the downgrade
1109/// lives here: no consumer — lookup, ranking, or the eviction sweep — can
1110/// observe a foreign [`EntryKind::Final`], so none of them has to remember to
1111/// ask. A foreign entry keeps its payload, its objective and its timestamps
1112/// and remains a perfectly good *seed*: the ρ it carries is a real optimum of
1113/// a nearby criterion. What it loses is the right to be shipped as a fit that
1114/// this build's outer search never ran, which is the whole defect and not the
1115/// mere fact of reuse.
1116fn read_meta(path: &Path) -> Result<OnDiskMeta, StoreError> {
1117    let bytes = fs::read(path)?;
1118    let mut parsed: OnDiskMeta = serde_json::from_slice(&bytes)?;
1119    if parsed.kind == EntryKind::Final && parsed.producer != producer_identity() {
1120        log::debug!(
1121            "warm-start store: {} was finalized by a different build; resuming it as a seed \
1122             rather than as a terminal certificate",
1123            path.display()
1124        );
1125        parsed.kind = EntryKind::Checkpoint;
1126    }
1127    Ok(parsed)
1128}
1129
1130fn entry_better(candidate: &OnDiskMeta, current: &OnDiskMeta) -> bool {
1131    match (candidate.kind, current.kind) {
1132        // Two terminal writes under one key are two COMPLETED fits of the same
1133        // problem, and choosing between them is a provenance question, not a
1134        // quality one. Ranking them by recorded objective let any historical
1135        // write whose criterion value happened to be lower outrank the fit that
1136        // just finished — and keep outranking it on every future run, since the
1137        // fresh terminus never displaces it. A resume then advertised itself as
1138        // "a prior fit's terminal certificate" while carrying a point the
1139        // previous fit never shipped, and (being inside this criterion's
1140        // stationarity band) got accepted at zero outer iterations and shipped
1141        // verbatim: the fit became a function of machine history (#2622).
1142        //
1143        // The recorded objectives are not on a common scale across fits anyway.
1144        // Anything that moves the criterion's VALUE without moving this key —
1145        // a different build, a differently anchored frozen nuisance — makes the
1146        // comparison meaningless while leaving it numerically decisive.
1147        // Recency is the ordering the claim needs: the newest terminal write IS
1148        // the previous fit's terminus.
1149        (EntryKind::Final, EntryKind::Final) => entry_newer(candidate, current),
1150        // A completed fit's terminus outranks mid-flight state unconditionally.
1151        // A checkpoint's objective is measured at a sub-converged iterate, so a
1152        // lower number there does not make it the better resume; crash recovery
1153        // is unaffected because checkpoints still rank among themselves whenever
1154        // no terminal write exists for the key.
1155        (EntryKind::Final, EntryKind::Checkpoint) => true,
1156        (EntryKind::Checkpoint, EntryKind::Final) => false,
1157        (EntryKind::Checkpoint, EntryKind::Checkpoint) => {
1158            match (candidate.objective, current.objective) {
1159                (Some(c), Some(d)) => {
1160                    if (c - d).abs() < 1e-12 {
1161                        entry_newer(candidate, current)
1162                    } else {
1163                        c < d
1164                    }
1165                }
1166                (Some(_), None) => true,
1167                (None, Some(_)) => false,
1168                (None, None) => entry_newer(candidate, current),
1169            }
1170        }
1171    }
1172}
1173
1174fn entry_newer(candidate: &OnDiskMeta, current: &OnDiskMeta) -> bool {
1175    let candidate_stamp = (
1176        candidate.written_unix_secs,
1177        candidate.written_nanos,
1178        candidate_kind_rank(candidate.kind),
1179    );
1180    let current_stamp = (
1181        current.written_unix_secs,
1182        current.written_nanos,
1183        candidate_kind_rank(current.kind),
1184    );
1185    candidate_stamp > current_stamp
1186}
1187
1188const fn candidate_kind_rank(kind: EntryKind) -> u8 {
1189    match kind {
1190        EntryKind::Checkpoint => 0,
1191        EntryKind::Final => 1,
1192    }
1193}
1194
1195fn checksum_hex(payload: &[u8]) -> String {
1196    let mut h = Sha256::new();
1197    h.update(payload);
1198    let out = h.finalize();
1199    let mut s = String::with_capacity(out.len() * 2);
1200    for b in out.iter() {
1201        use std::fmt::Write;
1202        write!(&mut s, "{:02x}", b).expect("writing to String is infallible");
1203    }
1204    s
1205}
1206
1207impl WarmStartStore {
1208    fn touch_lookup_hit(
1209        &self,
1210        meta_path: &Path,
1211        entry: WarmStartEntry,
1212    ) -> Result<WarmStartEntry, StoreError> {
1213        let meta = read_meta(meta_path)?;
1214        let (_meta, entry) = self.touch_lookup_meta(meta_path, meta, entry)?;
1215        Ok(entry)
1216    }
1217
1218    fn touch_lookup_meta(
1219        &self,
1220        meta_path: &Path,
1221        mut meta: OnDiskMeta,
1222        entry: WarmStartEntry,
1223    ) -> Result<(OnDiskMeta, WarmStartEntry), StoreError> {
1224        let now = self.nanos_now();
1225        // Refresh the ACCESS stamp (TTL clock), never the creation stamp: the
1226        // creation stamp is the ordering key for `lookup_latest`, so bumping it
1227        // on a read would make a merely-read entry win "latest" over a strictly
1228        // newer write. Advance strictly past the previous access stamp so a
1229        // second touch inside the same nanosecond still moves forward.
1230        let old_access =
1231            (meta.accessed_unix_secs as u128) * 1_000_000_000u128 + meta.accessed_nanos as u128;
1232        let touched = now.max(old_access.saturating_add(1));
1233        meta.accessed_unix_secs = u64::try_from(touched / 1_000_000_000u128)
1234            .expect("warm-start access timestamp seconds must fit in u64");
1235        meta.accessed_nanos = u32::try_from(touched % 1_000_000_000u128)
1236            .expect("subsecond nanoseconds are less than one billion");
1237        meta.accessed = true;
1238        let json = serde_json::to_vec_pretty(&meta)?;
1239        let tmp = meta_path.with_extension(format!(
1240            "json.touch.tmp.{}.{}",
1241            std::process::id(),
1242            self.nanos_now()
1243        ));
1244        {
1245            let mut f = fs::File::create(&tmp)?;
1246            f.write_all(&json)?;
1247            f.sync_all()?;
1248        }
1249        fs::rename(&tmp, meta_path)?;
1250        if let Some(dir) = meta_path.parent()
1251            && let Ok(d) = fs::File::open(dir)
1252        {
1253            log_best_effort(
1254                "fsyncing the metadata directory after rewrite",
1255                d.sync_all(),
1256            );
1257        }
1258        self.metadata_index_remove(meta_path);
1259        // `entry.written_unix_secs` intentionally keeps the immutable creation
1260        // stamp — the touch above only advanced the access clock.
1261        Ok((meta, entry))
1262    }
1263
1264    fn read_meta_indexed(
1265        &self,
1266        path: &Path,
1267        meta_md: &fs::Metadata,
1268        bin_md: &fs::Metadata,
1269    ) -> Result<OnDiskMeta, StoreError> {
1270        if let Ok(index) = self.index.lock()
1271            && let Some(cached) = index.by_meta_path.get(path)
1272            && cached.matches(meta_md, bin_md)
1273        {
1274            return Ok(cached.meta.clone());
1275        }
1276
1277        let meta = read_meta(path)?;
1278        let Some(meta_mtime) = meta_md.modified().ok() else {
1279            return Ok(meta);
1280        };
1281        if let Ok(mut index) = self.index.lock() {
1282            index.by_meta_path.insert(
1283                path.to_path_buf(),
1284                IndexedMeta {
1285                    meta_mtime,
1286                    meta_len: meta_md.len(),
1287                    bin_len: bin_md.len(),
1288                    meta: meta.clone(),
1289                },
1290            );
1291        }
1292        Ok(meta)
1293    }
1294
1295    fn metadata_index_upsert(&self, meta_path: &Path, bin_path: &Path) -> Result<(), StoreError> {
1296        // An overwrite may preserve both file lengths and the filesystem's
1297        // observable mtime (coarse timestamps or two replacements in one
1298        // clock tick). Invalidate the path before reading it: otherwise
1299        // `read_meta_indexed` can pair the previous checksum/objective with the
1300        // newly promoted payload and the next lookup will delete the valid pair
1301        // as corrupt. The writer is the authoritative mutation signal; no
1302        // filesystem heuristic is needed here.
1303        if let Ok(mut index) = self.index.lock() {
1304            index.by_meta_path.remove(meta_path);
1305            if let Some(parent) = meta_path.parent() {
1306                index.by_key_dir.remove(parent);
1307            }
1308        }
1309        let meta_md = fs::metadata(meta_path)?;
1310        let bin_md = fs::metadata(bin_path)?;
1311        self.read_meta_indexed(meta_path, &meta_md, &bin_md)?;
1312        Ok(())
1313    }
1314
1315    fn metadata_index_remove(&self, meta_path: &Path) {
1316        if let Ok(mut index) = self.index.lock() {
1317            index.by_meta_path.remove(meta_path);
1318            if let Some(parent) = meta_path.parent() {
1319                index.by_key_dir.remove(parent);
1320            }
1321        }
1322    }
1323
1324    fn metadata_index_remove_key(&self, key: &Fingerprint) {
1325        let dir = self.key_dir(key);
1326        if let Ok(mut index) = self.index.lock() {
1327            index.by_meta_path.retain(|path, _| !path.starts_with(&dir));
1328            index.by_key_dir.remove(&dir);
1329        }
1330    }
1331
1332    /// Cached listing lookup for one key directory.
1333    ///
1334    /// Returns the cached `Vec<ScannedEntry>` if the directory's current mtime
1335    /// matches the cached scan (no entry added/removed since), otherwise
1336    /// `None` so the caller performs a fresh scan via [`Self::scan_key_dir`].
1337    ///
1338    /// A matching dir mtime guarantees the *set* of files is unchanged, but TTL
1339    /// is wall-clock relative, so an entry valid at scan time can expire while
1340    /// the listing is still cached. The caller re-applies the TTL cutoff to the
1341    /// returned entries; this only proves the file set is stable.
1342    fn cached_dir_scan(&self, dir: &Path, dir_md: &fs::Metadata) -> Option<Vec<ScannedEntry>> {
1343        let dir_mtime = dir_md.modified().ok()?;
1344        let index = self.index.lock().ok()?;
1345        let cached = index.by_key_dir.get(dir)?;
1346        if cached.dir_mtime != dir_mtime {
1347            return None;
1348        }
1349        for entry in &cached.entries {
1350            let meta_md = fs::metadata(&entry.meta_path).ok()?;
1351            let bin_md = fs::metadata(&entry.bin_path).ok()?;
1352            if !entry.matches_files(&meta_md, &bin_md) {
1353                return None;
1354            }
1355        }
1356        Some(cached.entries.clone())
1357    }
1358
1359    fn store_dir_scan(&self, dir: &Path, dir_mtime: SystemTime, entries: &[ScannedEntry]) {
1360        if let Ok(mut index) = self.index.lock() {
1361            index.by_key_dir.insert(
1362                dir.to_path_buf(),
1363                ScannedDir {
1364                    dir_mtime,
1365                    entries: entries.to_vec(),
1366                },
1367            );
1368        }
1369    }
1370
1371    /// Scan one key directory, resolving every valid `(meta, bin)` pair and
1372    /// cleaning up corrupt / orphaned / schema-mismatched files in passing.
1373    ///
1374    /// Serves both [`Self::lookup_with`] and [`Self::evict_overflow`]: when the
1375    /// directory's mtime is unchanged since the previous scan it returns the
1376    /// cached listing without a single `read_dir`, `metadata`, or JSON read —
1377    /// the metadata-syscall storm that #1114 traced. A fresh scan re-caches the
1378    /// listing keyed by the dir mtime observed *after* any cleanup, so a later
1379    /// unchanged call hits the cache. (`now_nanos` drives the TTL drop; expired
1380    /// entries are removed and excluded from the result.)
1381    ///
1382    /// `.tmp.*` files belonging to other processes are swept; same-PID temps
1383    /// (in-flight writes from us) are left alone.
1384    fn scan_key_dir(&self, dir: &Path, now_nanos: u128) -> Vec<ScannedEntry> {
1385        let dir_md = match fs::metadata(dir) {
1386            Ok(m) => m,
1387            Err(_) => return Vec::new(),
1388        };
1389        if let Some(cached) = self.cached_dir_scan(dir, &dir_md) {
1390            // The file set is unchanged, but TTL is wall-clock relative: an
1391            // entry valid when scanned may have expired since. Re-apply the
1392            // cutoff against `now_nanos`, removing any that crossed it. If none
1393            // expired we return the cached listing untouched (the fast path);
1394            // otherwise the removals bump the dir mtime, so we drop the stale
1395            // cache and re-cache the survivors keyed by the post-removal mtime.
1396            let any_expired = cached
1397                .iter()
1398                .any(|e| meta_expired(meta_activity_nanos(&e.meta), self.opts.ttl, now_nanos));
1399            if !any_expired {
1400                return cached;
1401            }
1402            let mut survivors = Vec::with_capacity(cached.len());
1403            for entry in cached {
1404                if meta_expired(meta_activity_nanos(&entry.meta), self.opts.ttl, now_nanos) {
1405                    log_best_effort(
1406                        "removing the TTL-expired entry's metadata",
1407                        fs::remove_file(&entry.meta_path),
1408                    );
1409                    log_best_effort(
1410                        "removing the TTL-expired entry's payload",
1411                        fs::remove_file(&entry.bin_path),
1412                    );
1413                    self.metadata_index_remove(&entry.meta_path);
1414                } else {
1415                    survivors.push(entry);
1416                }
1417            }
1418            if let Some(mtime) = fs::metadata(dir).ok().and_then(|m| m.modified().ok()) {
1419                self.store_dir_scan(dir, mtime, &survivors);
1420            }
1421            return survivors;
1422        }
1423        let read_dir = match fs::read_dir(dir) {
1424            Ok(rd) => rd,
1425            Err(_) => return Vec::new(),
1426        };
1427        let mut entries = Vec::new();
1428        let mut mutated = false;
1429        for f in read_dir {
1430            let path = match f {
1431                Ok(e) => e.path(),
1432                Err(_) => continue,
1433            };
1434            let name = match path.file_name().and_then(|s| s.to_str()) {
1435                Some(s) => s,
1436                None => continue,
1437            };
1438            if name.contains(".tmp.") {
1439                if let Some(pid) = parse_tmp_pid(name)
1440                    && pid != std::process::id()
1441                {
1442                    log_best_effort(
1443                        "removing another process' abandoned temp",
1444                        fs::remove_file(&path),
1445                    );
1446                    mutated = true;
1447                }
1448                continue;
1449            }
1450            if path.extension().and_then(|s| s.to_str()) != Some("json") {
1451                continue;
1452            }
1453            let meta_md = match fs::metadata(&path) {
1454                Ok(m) => m,
1455                Err(_) => continue,
1456            };
1457            let bin = path.with_extension("bin");
1458            let bin_md = match fs::metadata(&bin) {
1459                Ok(m) => m,
1460                Err(_) => {
1461                    log_best_effort(
1462                        "removing metadata whose payload is missing",
1463                        fs::remove_file(&path),
1464                    );
1465                    self.metadata_index_remove(&path);
1466                    mutated = true;
1467                    continue;
1468                }
1469            };
1470            let meta = match self.read_meta_indexed(&path, &meta_md, &bin_md) {
1471                Ok(m) => m,
1472                Err(_) => {
1473                    log_best_effort("removing unreadable metadata", fs::remove_file(&path));
1474                    log_best_effort(
1475                        "removing the payload of unreadable metadata",
1476                        fs::remove_file(&bin),
1477                    );
1478                    self.metadata_index_remove(&path);
1479                    mutated = true;
1480                    continue;
1481                }
1482            };
1483            if meta.schema_version != SCHEMA_VERSION {
1484                log_best_effort(
1485                    "removing metadata from an older schema version",
1486                    fs::remove_file(&path),
1487                );
1488                log_best_effort(
1489                    "removing the payload of older-schema metadata",
1490                    fs::remove_file(&bin),
1491                );
1492                self.metadata_index_remove(&path);
1493                mutated = true;
1494                continue;
1495            }
1496            if meta_expired(meta_activity_nanos(&meta), self.opts.ttl, now_nanos) {
1497                log_best_effort(
1498                    "removing TTL-expired metadata during the sweep",
1499                    fs::remove_file(&path),
1500                );
1501                log_best_effort(
1502                    "removing the TTL-expired payload during the sweep",
1503                    fs::remove_file(&bin),
1504                );
1505                self.metadata_index_remove(&path);
1506                mutated = true;
1507                continue;
1508            }
1509            entries.push(ScannedEntry {
1510                meta_path: path,
1511                bin_path: bin,
1512                meta_len: meta_md.len(),
1513                bin_len: bin_md.len(),
1514                meta_mtime: meta_md.modified().ok(),
1515                bin_mtime: bin_md.modified().ok(),
1516                meta,
1517            });
1518        }
1519        // Cache keyed by the mtime *after* any cleanup so the next unchanged
1520        // call is a cache hit. If cleanup mutated the dir, re-stat to capture
1521        // the post-mutation mtime; otherwise reuse the mtime we already read.
1522        let final_mtime = if mutated {
1523            fs::metadata(dir).ok().and_then(|m| m.modified().ok())
1524        } else {
1525            dir_md.modified().ok()
1526        };
1527        if let Some(mtime) = final_mtime {
1528            self.store_dir_scan(dir, mtime, &entries);
1529        }
1530        entries
1531    }
1532
1533    fn test_time_offset_ns(&self) -> u64 {
1534        self.test_time_offset_ns.load(Ordering::Relaxed)
1535    }
1536
1537    fn unix_now_parts(&self) -> (u64, u32) {
1538        let total = nanos_since_epoch().saturating_add(u128::from(self.test_time_offset_ns()));
1539        let secs = u64::try_from(total / 1_000_000_000u128)
1540            .expect("warm-start timestamp seconds must fit in u64");
1541        let nanos = u32::try_from(total % 1_000_000_000u128)
1542            .expect("subsecond nanoseconds are less than one billion");
1543        (secs, nanos)
1544    }
1545
1546    fn nanos_now(&self) -> u128 {
1547        nanos_since_epoch().saturating_add(u128::from(self.test_time_offset_ns()))
1548    }
1549
1550    fn fresh_run_id(&self) -> String {
1551        let pid = std::process::id();
1552        let nanos = self.nanos_now();
1553        format!("r{pid:x}-{nanos:x}")
1554    }
1555}
1556
1557#[cfg(test)]
1558mod tests {
1559    use super::*;
1560    impl WarmStartStore {
1561        /// Advance this store's simulated monotonic clock by `dur`. Only
1562        /// available in tests — production code reads the real wall clock and
1563        /// never mutates the per-store offset.
1564        fn test_advance_time(&self, dur: Duration) {
1565            self.test_time_offset_ns
1566                .fetch_add(dur.as_nanos() as u64, Ordering::Relaxed);
1567        }
1568    }
1569
1570    fn temp_store() -> (tempfile::TempDir, WarmStartStore) {
1571        let dir = tempfile::tempdir().unwrap();
1572        let store = WarmStartStore::open(
1573            dir.path().to_path_buf(),
1574            StoreOptions {
1575                size_budget_bytes: 1024 * 1024,
1576                ttl: Duration::from_secs(60),
1577            },
1578        )
1579        .unwrap();
1580        (dir, store)
1581    }
1582
1583    fn key_for(s: &str) -> Fingerprint {
1584        let mut fp = Fingerprinter::new();
1585        fp.absorb_str(b"test", s);
1586        fp.finalize()
1587    }
1588
1589    #[test]
1590    fn roundtrip_save_then_lookup() {
1591        let (_d, store) = temp_store();
1592        let key = key_for("roundtrip");
1593        store
1594            .save(
1595                &key,
1596                b"hello-warm",
1597                Some(1.5),
1598                Some(7),
1599                EntryKind::Checkpoint,
1600            )
1601            .unwrap();
1602        let got = store.lookup(&key).unwrap().unwrap();
1603        assert_eq!(got.payload, b"hello-warm");
1604        assert_eq!(got.objective, Some(1.5));
1605        assert_eq!(got.iteration, Some(7));
1606        assert_eq!(got.kind, EntryKind::Checkpoint);
1607    }
1608
1609    #[test]
1610    fn lookup_picks_lowest_objective() {
1611        let (_d, store) = temp_store();
1612        let key = key_for("multi");
1613        store
1614            .save(&key, b"worse", Some(3.0), Some(1), EntryKind::Checkpoint)
1615            .unwrap();
1616        store
1617            .save(&key, b"better", Some(1.0), Some(2), EntryKind::Checkpoint)
1618            .unwrap();
1619        store
1620            .save(&key, b"mid", Some(2.0), Some(3), EntryKind::Checkpoint)
1621            .unwrap();
1622        let got = store.lookup(&key).unwrap().unwrap();
1623        assert_eq!(got.payload, b"better");
1624        assert_eq!(got.objective, Some(1.0));
1625    }
1626
1627    #[test]
1628    fn lookup_latest_ignores_objective_ordering() {
1629        let (_d, store) = temp_store();
1630        let key = key_for("latest-vs-best");
1631        store
1632            .save(&key, b"low-objective", Some(1.0), Some(1), EntryKind::Final)
1633            .unwrap();
1634        store.test_advance_time(Duration::from_millis(2));
1635        store
1636            .save(
1637                &key,
1638                b"newer-higher-objective",
1639                Some(10.0),
1640                Some(2),
1641                EntryKind::Checkpoint,
1642            )
1643            .unwrap();
1644
1645        let best = store.lookup(&key).unwrap().unwrap();
1646        assert_eq!(best.payload, b"low-objective");
1647
1648        let latest = store.lookup_latest(&key).unwrap().unwrap();
1649        assert_eq!(latest.payload, b"newer-higher-objective");
1650        assert_eq!(latest.iteration, Some(2));
1651    }
1652
1653    #[test]
1654    fn lookup_prefers_the_latest_terminal_write_over_a_lower_objective_one_2622() {
1655        let (_d, store) = temp_store();
1656        let key = key_for("terminal-provenance");
1657        // A completed fit from earlier: its recorded criterion value is lower,
1658        // but it is not this key's most recent terminus. Nothing about a lower
1659        // number makes it the fit whose result a resume may claim to carry.
1660        store
1661            .save(
1662                &key,
1663                b"older-lower-objective",
1664                Some(1.0),
1665                Some(9),
1666                EntryKind::Final,
1667            )
1668            .unwrap();
1669        store.test_advance_time(Duration::from_millis(2));
1670        store
1671            .save(
1672                &key,
1673                b"newest-terminus",
1674                Some(10.0),
1675                Some(4),
1676                EntryKind::Final,
1677            )
1678            .unwrap();
1679
1680        let got = store.lookup(&key).unwrap().unwrap();
1681        assert_eq!(
1682            got.payload, b"newest-terminus",
1683            "a terminal-certificate resume must carry the LAST completed fit's terminus; ranking \
1684             two Final writes by recorded objective let a historical entry outrank the fit that \
1685             just finished, permanently, and the resume then shipped a point no recent fit \
1686             produced (#2622)"
1687        );
1688        assert_eq!(got.objective, Some(10.0));
1689    }
1690
1691    #[test]
1692    fn lookup_prefers_a_terminal_write_over_a_lower_objective_checkpoint_2622() {
1693        let (_d, store) = temp_store();
1694        let key = key_for("terminal-vs-checkpoint");
1695        store
1696            .save(&key, b"final", Some(5.0), Some(3), EntryKind::Final)
1697            .unwrap();
1698        store.test_advance_time(Duration::from_millis(2));
1699        // A mid-flight iterate measured at a sub-converged state. Its objective
1700        // is not on the terminus' scale, so a lower number here is not evidence
1701        // that it is the better resume.
1702        store
1703            .save(
1704                &key,
1705                b"lower-objective-checkpoint",
1706                Some(0.5),
1707                Some(70),
1708                EntryKind::Checkpoint,
1709            )
1710            .unwrap();
1711
1712        let got = store.lookup(&key).unwrap().unwrap();
1713        assert_eq!(got.payload, b"final");
1714        assert_eq!(got.kind, EntryKind::Final);
1715    }
1716
1717    #[test]
1718    fn checkpoints_still_rank_by_objective_when_no_terminal_write_exists_2622() {
1719        let (_d, store) = temp_store();
1720        let key = key_for("checkpoint-only");
1721        store
1722            .save(&key, b"worse", Some(3.0), Some(1), EntryKind::Checkpoint)
1723            .unwrap();
1724        store.test_advance_time(Duration::from_millis(2));
1725        store
1726            .save(&key, b"best", Some(1.0), Some(2), EntryKind::Checkpoint)
1727            .unwrap();
1728        store.test_advance_time(Duration::from_millis(2));
1729        store
1730            .save(
1731                &key,
1732                b"newest-but-worse",
1733                Some(2.0),
1734                Some(3),
1735                EntryKind::Checkpoint,
1736            )
1737            .unwrap();
1738
1739        let got = store.lookup(&key).unwrap().unwrap();
1740        assert_eq!(
1741            got.payload, b"best",
1742            "crash recovery keeps the best iterate seen: with no terminal write for the key, \
1743             checkpoints are still ordered by objective"
1744        );
1745    }
1746
1747    #[test]
1748    fn tiebreak_final_beats_checkpoint() {
1749        let (_d, store) = temp_store();
1750        let key = key_for("tie");
1751        store
1752            .save(&key, b"ckpt", Some(1.0), None, EntryKind::Checkpoint)
1753            .unwrap();
1754        // Same objective, different kind.
1755        store
1756            .save(&key, b"final", Some(1.0), None, EntryKind::Final)
1757            .unwrap();
1758        let got = store.lookup(&key).unwrap().unwrap();
1759        assert_eq!(got.payload, b"final");
1760        assert_eq!(got.kind, EntryKind::Final);
1761    }
1762
1763    #[test]
1764    fn tiebreak_latest_mtime_when_no_objective() {
1765        let (_d, store) = temp_store();
1766        let key = key_for("latest");
1767        store
1768            .save(&key, b"first", None, None, EntryKind::Checkpoint)
1769            .unwrap();
1770        store.test_advance_time(Duration::from_millis(1_100));
1771        store
1772            .save(&key, b"second", None, None, EntryKind::Checkpoint)
1773            .unwrap();
1774        let got = store.lookup(&key).unwrap().unwrap();
1775        assert_eq!(got.payload, b"second");
1776    }
1777
1778    #[test]
1779    fn corrupt_payload_is_cleaned_up() {
1780        let (_d, store) = temp_store();
1781        let key = key_for("corrupt");
1782        store
1783            .save(&key, b"original", Some(0.0), None, EntryKind::Checkpoint)
1784            .unwrap();
1785        // Tamper with the .bin file.
1786        let dir = store.key_dir(&key);
1787        for entry in fs::read_dir(&dir).unwrap() {
1788            let p = entry.unwrap().path();
1789            if p.extension().and_then(|s| s.to_str()) == Some("bin") {
1790                fs::write(&p, b"tampered!").unwrap();
1791            }
1792        }
1793        let got = store.lookup(&key).unwrap();
1794        assert!(got.is_none(), "tampered entry must be rejected");
1795        // The corrupt files should be cleaned up so they don't accumulate.
1796        let remaining: Vec<_> = fs::read_dir(&dir).unwrap().collect();
1797        assert!(remaining.is_empty(), "corrupt entry should be removed");
1798    }
1799
1800    #[test]
1801    fn corrupt_meta_json_is_cleaned_up() {
1802        let (_d, store) = temp_store();
1803        let key = key_for("badjson");
1804        store
1805            .save(&key, b"x", None, None, EntryKind::Checkpoint)
1806            .unwrap();
1807        let dir = store.key_dir(&key);
1808        for entry in fs::read_dir(&dir).unwrap() {
1809            let p = entry.unwrap().path();
1810            if p.extension().and_then(|s| s.to_str()) == Some("json") {
1811                fs::write(&p, b"{not valid json").unwrap();
1812            }
1813        }
1814        let got = store.lookup(&key).unwrap();
1815        assert!(got.is_none());
1816    }
1817
1818    #[test]
1819    fn schema_mismatched_entry_is_cleaned_up() {
1820        let (_d, store) = temp_store();
1821        let key = key_for("schema");
1822        store
1823            .save(&key, b"x", None, None, EntryKind::Checkpoint)
1824            .unwrap();
1825        let dir = store.key_dir(&key);
1826        for entry in fs::read_dir(&dir).unwrap() {
1827            let p = entry.unwrap().path();
1828            if p.extension().and_then(|s| s.to_str()) == Some("json") {
1829                let raw = fs::read(&p).unwrap();
1830                let mut parsed: serde_json::Value = serde_json::from_slice(&raw).unwrap();
1831                parsed["schema_version"] = serde_json::json!(SCHEMA_VERSION + 99);
1832                fs::write(&p, serde_json::to_vec_pretty(&parsed).unwrap()).unwrap();
1833            }
1834        }
1835        assert!(store.lookup(&key).unwrap().is_none());
1836        let remaining: Vec<_> = fs::read_dir(&dir).unwrap().collect();
1837        assert!(
1838            remaining.is_empty(),
1839            "schema-mismatched entry should be removed"
1840        );
1841    }
1842
1843    #[test]
1844    fn schema_mismatched_entry_is_removed_during_save_eviction_path() {
1845        let dir = tempfile::tempdir().unwrap();
1846        let store = WarmStartStore::open(
1847            dir.path().to_path_buf(),
1848            StoreOptions {
1849                size_budget_bytes: 6 * 1024,
1850                ttl: Duration::from_secs(3600),
1851            },
1852        )
1853        .unwrap();
1854        let stale_key = key_for("schema-size-stale");
1855        store
1856            .save(
1857                &stale_key,
1858                &vec![0u8; 4 * 1024],
1859                None,
1860                None,
1861                EntryKind::Checkpoint,
1862            )
1863            .unwrap();
1864
1865        let stale_dir = store.key_dir(&stale_key);
1866        let mut stale_meta = None;
1867        let mut stale_bin = None;
1868        for entry in fs::read_dir(&stale_dir).unwrap() {
1869            let p = entry.unwrap().path();
1870            let extension = p.extension().and_then(|s| s.to_str()).map(str::to_owned);
1871            if extension.as_deref() == Some("json") {
1872                let raw = fs::read(&p).unwrap();
1873                let mut parsed: serde_json::Value = serde_json::from_slice(&raw).unwrap();
1874                parsed["schema_version"] = serde_json::json!(SCHEMA_VERSION + 99);
1875                fs::write(&p, serde_json::to_vec_pretty(&parsed).unwrap()).unwrap();
1876                stale_meta = Some(p);
1877            } else if extension.as_deref() == Some("bin") {
1878                stale_bin = Some(p);
1879            }
1880        }
1881        let stale_meta = stale_meta.expect("saved entry should have metadata");
1882        let stale_bin = stale_bin.expect("saved entry should have payload");
1883
1884        let fresh_key = key_for("schema-size-fresh");
1885        store
1886            .save(
1887                &fresh_key,
1888                &vec![1u8; 2 * 1024],
1889                None,
1890                None,
1891                EntryKind::Checkpoint,
1892            )
1893            .unwrap();
1894
1895        assert!(
1896            !stale_meta.exists(),
1897            "schema-mismatched metadata should be removed during eviction scan"
1898        );
1899        assert!(
1900            !stale_bin.exists(),
1901            "schema-mismatched payload should be removed during eviction scan"
1902        );
1903
1904        let mut total = 0u64;
1905        for key_dir in fs::read_dir(store.root()).unwrap() {
1906            let key_dir = key_dir.unwrap().path();
1907            if key_dir.is_dir() {
1908                for entry in fs::read_dir(key_dir).unwrap() {
1909                    total += fs::metadata(entry.unwrap().path()).unwrap().len();
1910                }
1911            }
1912        }
1913        assert!(
1914            total <= store.options().size_budget_bytes,
1915            "schema-mismatched bytes must not leak past size accounting (got {total})"
1916        );
1917        assert!(store.lookup(&stale_key).unwrap().is_none());
1918        assert!(store.lookup(&fresh_key).unwrap().is_some());
1919    }
1920
1921    #[test]
1922    fn missing_bin_treated_as_missing() {
1923        let (_d, store) = temp_store();
1924        let key = key_for("nobin");
1925        store
1926            .save(&key, b"x", None, None, EntryKind::Checkpoint)
1927            .unwrap();
1928        let dir = store.key_dir(&key);
1929        for entry in fs::read_dir(&dir).unwrap() {
1930            let p = entry.unwrap().path();
1931            if p.extension().and_then(|s| s.to_str()) == Some("bin") {
1932                fs::remove_file(&p).unwrap();
1933            }
1934        }
1935        assert!(store.lookup(&key).unwrap().is_none());
1936    }
1937
1938    #[test]
1939    fn missing_key_returns_none() {
1940        let (_d, store) = temp_store();
1941        let key = key_for("absent");
1942        assert!(store.lookup(&key).unwrap().is_none());
1943    }
1944
1945    #[test]
1946    fn lru_eviction_under_size_budget() {
1947        let dir = tempfile::tempdir().unwrap();
1948        // Tiny budget: 4 KiB. Each entry payload + meta JSON is ~600 B.
1949        let store = WarmStartStore::open(
1950            dir.path().to_path_buf(),
1951            StoreOptions {
1952                size_budget_bytes: 4 * 1024,
1953                ttl: Duration::from_secs(3600),
1954            },
1955        )
1956        .unwrap();
1957        let mut keys = Vec::new();
1958        for i in 0..20 {
1959            let mut fp = Fingerprinter::new();
1960            fp.absorb_u64(b"i", i);
1961            let key = fp.finalize();
1962            keys.push(key);
1963            let payload = vec![0u8; 256];
1964            store
1965                .save(&key, &payload, Some(i as f64), None, EntryKind::Checkpoint)
1966                .unwrap();
1967        }
1968        // Walk the store root and confirm total bytes is bounded.
1969        let mut total = 0u64;
1970        for kd in fs::read_dir(store.root()).unwrap() {
1971            let kd = kd.unwrap().path();
1972            if kd.is_dir() {
1973                for f in fs::read_dir(&kd).unwrap() {
1974                    total += fs::metadata(f.unwrap().path()).unwrap().len();
1975                }
1976            }
1977        }
1978        assert!(
1979            total <= 8 * 1024,
1980            "eviction failed to bound size (got {total})"
1981        );
1982        // Earliest keys must have been evicted; latest survive.
1983        assert!(store.lookup(&keys[0]).unwrap().is_none());
1984        assert!(store.lookup(keys.last().unwrap()).unwrap().is_some());
1985    }
1986
1987    #[test]
1988    fn ttl_drops_old_entries() {
1989        // Expiration is driven by `test_advance_time` (additive simulated time
1990        // on top of the wall clock), so the TTL itself only needs to be larger
1991        // than any plausible save→lookup wall-time on the CI runner. The
1992        // 1-second TTL the original fixture used was tighter than the worst
1993        // ext4 fsync this image sees (see `save_overwrite`'s late-stamp
1994        // comment), so the first `is_some()` check would flake to "expired"
1995        // before any time advance ever ran. 60 s clears that race with margin.
1996        let dir = tempfile::tempdir().unwrap();
1997        let ttl = Duration::from_secs(60);
1998        let store = WarmStartStore::open(
1999            dir.path().to_path_buf(),
2000            StoreOptions {
2001                size_budget_bytes: 1024 * 1024,
2002                ttl,
2003            },
2004        )
2005        .unwrap();
2006        let key = key_for("ttl");
2007        store
2008            .save(&key, b"x", None, None, EntryKind::Checkpoint)
2009            .unwrap();
2010        assert!(store.lookup(&key).unwrap().is_some());
2011        store.test_advance_time(ttl + Duration::from_secs(5));
2012        // Trigger eviction via a save under an unrelated key.
2013        let other = key_for("ttl-other");
2014        store
2015            .save(&other, b"y", None, None, EntryKind::Checkpoint)
2016            .unwrap();
2017        // Original now expired.
2018        assert!(store.lookup(&key).unwrap().is_none());
2019        assert!(store.lookup(&other).unwrap().is_some());
2020    }
2021
2022    #[test]
2023    fn orphan_temp_files_from_dead_processes_are_swept() {
2024        let (_d, store) = temp_store();
2025        let key = key_for("tmp");
2026        let dir = store.key_dir(&key);
2027        fs::create_dir_all(&dir).unwrap();
2028        // Use PID 1 — never the current process, so it counts as "other".
2029        let orphan_other = dir.join("r0-0.json.tmp.1.0");
2030        let mine = dir.join(format!("r0-0.bin.tmp.{}.0", std::process::id()));
2031        fs::write(&orphan_other, b"orphan").unwrap();
2032        fs::write(&mine, b"mine").unwrap();
2033        store.evict_overflow().unwrap();
2034        assert!(!orphan_other.exists(), "other-PID tmp file should be swept");
2035        assert!(mine.exists(), "same-PID tmp file must be left alone");
2036    }
2037
2038    #[test]
2039    fn tmp_filenames_without_pid_are_skipped() {
2040        // Malformed tmp names (no parseable pid) must not crash the sweep.
2041        let (_d, store) = temp_store();
2042        let key = key_for("malformed");
2043        let dir = store.key_dir(&key);
2044        fs::create_dir_all(&dir).unwrap();
2045        let weird = dir.join("garbage.tmp.notapid.suffix");
2046        fs::write(&weird, b"x").unwrap();
2047        // Must not panic.
2048        store.evict_overflow().unwrap();
2049        assert!(weird.exists());
2050    }
2051
2052    #[test]
2053    fn save_overwrite_keeps_single_entry() {
2054        let (_d, store) = temp_store();
2055        let key = key_for("overwrite");
2056        let id = store
2057            .save(&key, b"v1", Some(2.0), Some(1), EntryKind::Checkpoint)
2058            .unwrap();
2059        store
2060            .save_overwrite(&key, &id, b"v2", Some(1.0), Some(2), EntryKind::Checkpoint)
2061            .unwrap();
2062        // Only one (meta, bin) pair on disk.
2063        let dir = store.key_dir(&key);
2064        let files: Vec<_> = fs::read_dir(&dir).unwrap().collect();
2065        assert_eq!(files.len(), 2, "overwrite should not create a new run-id");
2066        let got = store.lookup(&key).unwrap().unwrap();
2067        assert_eq!(got.payload, b"v2");
2068        assert_eq!(got.objective, Some(1.0));
2069    }
2070
2071    #[test]
2072    fn write_and_promote_recreates_dir_removed_before_write() {
2073        // gam#868: a sibling process' eviction can `remove_dir` the key dir the
2074        // instant it observes it empty, racing every write step in `save`. The
2075        // promote helper must recreate the dir rather than failing with ENOENT.
2076        let (_d, store) = temp_store();
2077        let key = key_for("race-recreate");
2078        let dir = store.key_dir(&key);
2079        // Dir does NOT exist yet (simulates eviction having removed it after a
2080        // prior `create_dir_all`). The helper must create it and succeed.
2081        assert!(!dir.exists());
2082        let bin_tmp = dir.join("r0.bin.tmp.1.0.0");
2083        let meta_tmp = dir.join("r0.json.tmp.1.0.0");
2084        let bin_final = dir.join("r0.bin");
2085        let meta_final = dir.join("r0.json");
2086        let stamp_fn = || (0u64, 0u32);
2087        let build_meta_json = |_: u64, _: u32| -> io::Result<Vec<u8>> { Ok(b"{}".to_vec()) };
2088        write_and_promote_entry(&EntryWrite {
2089            dir: &dir,
2090            bin_tmp: &bin_tmp,
2091            meta_tmp: &meta_tmp,
2092            payload: b"payload",
2093            bin_final: &bin_final,
2094            meta_final: &meta_final,
2095            stamp_fn: &stamp_fn,
2096            build_meta_json: &build_meta_json,
2097        })
2098        .expect("promote into a missing dir must recreate it and succeed");
2099        assert!(bin_final.exists() && meta_final.exists());
2100        assert_eq!(fs::read(&bin_final).unwrap(), b"payload");
2101    }
2102
2103    #[test]
2104    fn save_survives_concurrent_eviction_removing_key_dir() {
2105        // gam#868 end-to-end: hammer the same key with four concurrent writers
2106        // while a sibling thread runs `evict_overflow` continuously at a zero byte
2107        // budget, so eviction is deleting entries underneath every save. Every
2108        // save must still succeed; we assert none returns an error.
2109        //
2110        // What this no longer covers, deliberately: `evict_overflow` used to also
2111        // `remove_dir` emptied key directories, and a save whose
2112        // `create_dir_all`→write/rename window straddled that removal failed with
2113        // ENOENT. That sweep is GONE (gam#2625 — it reclaimed no budgeted bytes and
2114        // was the sole source of the race), so the hazard is designed out rather
2115        // than merely survived, and this test can no longer reach it through
2116        // eviction.
2117        //
2118        // The recreate-and-retry path that used to be the only defence still exists
2119        // for a remover this process does not control, and it is covered
2120        // DETERMINISTICALLY by `write_and_promote_recreates_dir_removed_before_write`
2121        // — which is the right shape for it. Driving a removal from this test
2122        // instead would mean inventing an adversary that deletes the directory in a
2123        // loop; no finite retry bound can survive that, so the assertion would be
2124        // impossible rather than demanding, and it also raises EEXIST rather than
2125        // the ENOENT the retry is about.
2126        use std::sync::Arc;
2127        use std::sync::atomic::AtomicBool;
2128
2129        let dir = tempfile::tempdir().unwrap();
2130        // Zero size budget forces `evict_overflow` to delete entries (and then
2131        // sweep the emptied key dir) on essentially every sweep, maximizing the
2132        // race window.
2133        let store = Arc::new(
2134            WarmStartStore::open(
2135                dir.path().to_path_buf(),
2136                StoreOptions {
2137                    size_budget_bytes: 0,
2138                    ttl: Duration::from_secs(60),
2139                },
2140            )
2141            .unwrap(),
2142        );
2143        let key = key_for("concurrent-evict");
2144        let stop = Arc::new(AtomicBool::new(false));
2145
2146        let evictor = {
2147            let store = Arc::clone(&store);
2148            let stop = Arc::clone(&stop);
2149            std::thread::spawn(move || {
2150                while !stop.load(Ordering::Relaxed) {
2151                    log_best_effort("the concurrent eviction pass", store.evict_overflow());
2152                }
2153            })
2154        };
2155
2156        let writers: Vec<_> = (0..4)
2157            .map(|w| {
2158                let store = Arc::clone(&store);
2159                std::thread::spawn(move || {
2160                    for i in 0..200u32 {
2161                        let payload = format!("w{w}-i{i}");
2162                        store
2163                            .save(
2164                                &key,
2165                                payload.as_bytes(),
2166                                Some(i as f64),
2167                                Some(i as u64),
2168                                EntryKind::Checkpoint,
2169                            )
2170                            .expect("save must not fail with ENOENT under concurrent eviction");
2171                    }
2172                })
2173            })
2174            .collect();
2175
2176        for h in writers {
2177            h.join().unwrap();
2178        }
2179        stop.store(true, Ordering::Relaxed);
2180        evictor.join().unwrap();
2181    }
2182
2183    #[test]
2184    fn keys_are_isolated() {
2185        let (_d, store) = temp_store();
2186        let a = key_for("a");
2187        let b = key_for("b");
2188        store
2189            .save(&a, b"AAA", Some(1.0), None, EntryKind::Final)
2190            .unwrap();
2191        store
2192            .save(&b, b"BBB", Some(1.0), None, EntryKind::Final)
2193            .unwrap();
2194        assert_eq!(store.lookup(&a).unwrap().unwrap().payload, b"AAA");
2195        assert_eq!(store.lookup(&b).unwrap().unwrap().payload, b"BBB");
2196    }
2197
2198    /// Overwrite the `producer` field of every metadata file under `key`.
2199    ///
2200    /// `None` deletes the field, reproducing an entry written before the field
2201    /// existed. Returns how many metadata files were rewritten so a caller can
2202    /// assert it actually reached one — a helper that silently matched nothing
2203    /// would make the tests below pass by doing nothing.
2204    fn rewrite_producer(store: &WarmStartStore, key: &Fingerprint, producer: Option<&str>) -> usize {
2205        let dir = store.key_dir(key);
2206        let mut rewritten = 0usize;
2207        for entry in fs::read_dir(&dir).unwrap() {
2208            let p = entry.unwrap().path();
2209            if p.extension().and_then(|s| s.to_str()) != Some("json") {
2210                continue;
2211            }
2212            let raw = fs::read(&p).unwrap();
2213            let mut parsed: serde_json::Value = serde_json::from_slice(&raw).unwrap();
2214            match producer {
2215                Some(value) => parsed["producer"] = serde_json::json!(value),
2216                None => {
2217                    parsed
2218                        .as_object_mut()
2219                        .expect("entry metadata is a JSON object")
2220                        .remove("producer");
2221                }
2222            }
2223            fs::write(&p, serde_json::to_vec_pretty(&parsed).unwrap()).unwrap();
2224            rewritten += 1;
2225        }
2226        rewritten
2227    }
2228
2229    #[test]
2230    fn a_final_entry_this_build_wrote_is_still_a_terminal_certificate_2625() {
2231        // The control for the two downgrade tests below. Without it, a bug that
2232        // downgraded EVERY entry would satisfy them both.
2233        let (_d, store) = temp_store();
2234        let key = key_for("producer-own");
2235        store
2236            .save(&key, b"mine", Some(1.0), Some(7), EntryKind::Final)
2237            .unwrap();
2238        let got = store.lookup(&key).unwrap().unwrap();
2239        assert_eq!(got.kind, EntryKind::Final);
2240        assert_eq!(got.payload, b"mine");
2241    }
2242
2243    #[test]
2244    fn a_final_entry_from_another_build_is_a_seed_not_a_certificate_2625() {
2245        // gam#2625: the key is over (data, spec) only, so a different build of
2246        // gam shares entries. Resuming another build's terminus certified a fit
2247        // this build's outer search never ran. The entry stays usable — the rho
2248        // it carries is a real optimum of a nearby criterion — but it must come
2249        // back as a checkpoint, which no consumer treats as terminal.
2250        let (_d, store) = temp_store();
2251        let key = key_for("producer-foreign");
2252        store
2253            .save(&key, b"theirs", Some(1.0), Some(7), EntryKind::Final)
2254            .unwrap();
2255        assert_eq!(
2256            rewrite_producer(&store, &key, Some("a-different-build")),
2257            1,
2258            "the helper must have rewritten exactly the one entry just saved"
2259        );
2260        let got = store.lookup(&key).unwrap().unwrap();
2261        assert_eq!(
2262            got.kind,
2263            EntryKind::Checkpoint,
2264            "a foreign terminus must be downgraded to a seed"
2265        );
2266        assert_eq!(
2267            got.payload, b"theirs",
2268            "the payload is still the best available seed and must survive"
2269        );
2270        assert_eq!(
2271            got.objective,
2272            Some(1.0),
2273            "the objective travels with the seed; only the certification is withdrawn"
2274        );
2275    }
2276
2277    #[test]
2278    fn a_final_entry_with_no_recorded_producer_is_a_seed_2625() {
2279        // Entries written before the field existed deserialize to the empty
2280        // string. An unknown producer cannot be shown to be this build, so the
2281        // conservative reading is the correct one.
2282        let (_d, store) = temp_store();
2283        let key = key_for("producer-legacy");
2284        store
2285            .save(&key, b"legacy", Some(2.0), None, EntryKind::Final)
2286            .unwrap();
2287        assert_eq!(rewrite_producer(&store, &key, None), 1);
2288        let got = store.lookup(&key).unwrap().unwrap();
2289        assert_eq!(got.kind, EntryKind::Checkpoint);
2290        assert_eq!(got.payload, b"legacy");
2291    }
2292
2293    #[test]
2294    fn a_checkpoint_from_another_build_is_unaffected_2625() {
2295        // The downgrade is about certification, so it has nothing to say about
2296        // an entry that never claimed to be terminal.
2297        let (_d, store) = temp_store();
2298        let key = key_for("producer-foreign-checkpoint");
2299        store
2300            .save(&key, b"ckpt", Some(3.0), Some(2), EntryKind::Checkpoint)
2301            .unwrap();
2302        assert_eq!(rewrite_producer(&store, &key, Some("a-different-build")), 1);
2303        let got = store.lookup(&key).unwrap().unwrap();
2304        assert_eq!(got.kind, EntryKind::Checkpoint);
2305        assert_eq!(got.payload, b"ckpt");
2306    }
2307
2308    #[test]
2309    fn the_producer_identity_is_stable_within_a_process_2625() {
2310        // The token is memoized, and the fix depends on it: an identity that
2311        // moved between two reads in one process would downgrade the process's
2312        // own terminus and silently disable resume everywhere.
2313        let first = producer_identity();
2314        let second = producer_identity();
2315        assert!(
2316            std::ptr::eq(first, second),
2317            "the OnceLock must return the same allocation, not merely equal text"
2318        );
2319        assert!(
2320            !first.is_empty(),
2321            "an empty token would collide with the legacy serde default"
2322        );
2323    }
2324}