Skip to main content

presolve_compiler/
persistent_cache.rs

1//! L6 source-free, complete-result persistent cache.
2//!
3//! This module only encodes and validates canonical L3 products. It never
4//! reads authored inputs, invokes parsing, or interprets compiler semantics.
5
6#![allow(clippy::missing_errors_doc, clippy::too_many_lines)]
7
8use std::fs::{self, File, OpenOptions};
9use std::io::Write;
10use std::path::{Path, PathBuf};
11
12use sha2::{Digest as _, Sha256};
13
14use crate::platform::{self, ContractVersion, WorkspaceGraph, WorkspaceSnapshot};
15
16pub const PERSISTENT_ARTIFACT_CACHE_V1_SCHEMA: &str = "presolve.persistent-artifact-cache.v1";
17pub const CACHE_MANIFEST_V1_SCHEMA: &str = "presolve.cache-manifest.v1";
18pub const CACHE_ENTRY_ENVELOPE_V1_SCHEMA: &str = "presolve.cache-entry-envelope.v1";
19pub const CACHE_INSPECTION_REPORT_V1_SCHEMA: &str = "presolve.cache-inspection-report.v1";
20pub const CACHE_SCHEMA_VERSION: u32 = 1;
21const PAYLOAD_CODEC: &str = "presolve.complete-result-payload.v1";
22const PAYLOAD_MAGIC: &[u8] = b"PSL6\0";
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
25pub enum CacheReasonCodeV1 {
26    Disabled,
27    RootUnavailable,
28    ManifestMissing,
29    ManifestIncompatible,
30    LockUnavailable,
31    EntryAbsent,
32    EntryIncomplete,
33    EnvelopeIncompatible,
34    EnvelopeChecksumMismatch,
35    KeyMismatch,
36    CompatibilityMismatch,
37    PayloadLengthMismatch,
38    PayloadChecksumMismatch,
39    PayloadDecodeFailure,
40    CanonicalProductValidationFailure,
41    ArtifactChecksumMismatch,
42    RequestFingerprintMismatch,
43    ResultFingerprintMismatch,
44    PublicationIoFailure,
45    EntryAlreadyValid,
46    EntryReplaced,
47    HitValidated,
48    CleanupRefused,
49    VerificationInvalid,
50}
51impl CacheReasonCodeV1 {
52    #[must_use]
53    pub const fn code(self) -> &'static str {
54        match self {
55            Self::Disabled => "L6C001",
56            Self::RootUnavailable => "L6C002",
57            Self::ManifestMissing => "L6C003",
58            Self::ManifestIncompatible => "L6C004",
59            Self::LockUnavailable => "L6C005",
60            Self::EntryAbsent => "L6C006",
61            Self::EntryIncomplete => "L6C007",
62            Self::EnvelopeIncompatible => "L6C008",
63            Self::EnvelopeChecksumMismatch => "L6C009",
64            Self::KeyMismatch => "L6C010",
65            Self::CompatibilityMismatch => "L6C011",
66            Self::PayloadLengthMismatch => "L6C012",
67            Self::PayloadChecksumMismatch => "L6C013",
68            Self::PayloadDecodeFailure => "L6C014",
69            Self::CanonicalProductValidationFailure => "L6C015",
70            Self::ArtifactChecksumMismatch => "L6C016",
71            Self::RequestFingerprintMismatch => "L6C017",
72            Self::ResultFingerprintMismatch => "L6C018",
73            Self::PublicationIoFailure => "L6C019",
74            Self::EntryAlreadyValid => "L6C020",
75            Self::EntryReplaced => "L6C021",
76            Self::HitValidated => "L6C022",
77            Self::CleanupRefused => "L6C023",
78            Self::VerificationInvalid => "L6C024",
79        }
80    }
81}
82#[derive(Debug, Clone, Copy, PartialEq, Eq)]
83pub enum CacheOutcomeV1 {
84    NotChecked,
85    Hit,
86    Miss,
87    WriteFailed,
88}
89impl CacheOutcomeV1 {
90    #[must_use]
91    pub const fn as_str(self) -> &'static str {
92        match self {
93            Self::NotChecked => "not_checked",
94            Self::Hit => "hit",
95            Self::Miss => "miss",
96            Self::WriteFailed => "write_failed",
97        }
98    }
99}
100#[derive(Debug, Clone, Copy, PartialEq, Eq)]
101pub enum CacheReportSelector {
102    None,
103    Summary,
104    Full,
105}
106#[derive(Debug, Clone, PartialEq, Eq)]
107pub struct CacheTelemetryV1 {
108    pub enabled: bool,
109    pub outcome: CacheOutcomeV1,
110    pub reasons: Vec<CacheReasonCodeV1>,
111    pub cache_key: String,
112    pub payload_length: Option<u64>,
113    pub result_fingerprint: Option<String>,
114    pub entry_published: bool,
115    pub entry_replaced: bool,
116}
117#[derive(Debug, Clone)]
118pub struct CacheKeyInputV1 {
119    pub compiler_contract: ContractVersion,
120    pub configuration_fingerprint: String,
121    pub source_universe_fingerprint: String,
122    pub compile_mode: &'static str,
123}
124impl CacheKeyInputV1 {
125    /// # Panics
126    ///
127    /// Panics only if this fixed L6 field list exceeds the `u32` wire ordinal
128    /// space, which is impossible for the contract-defined list.
129    #[must_use]
130    pub fn key(&self) -> String {
131        let fields = [
132            "presolve.compile-result-cache.v1",
133            concat!(env!("CARGO_PKG_NAME"), ":", env!("CARGO_PKG_VERSION")),
134            self.compiler_contract.as_str(),
135            "compiler-service-protocol:1",
136            "workspace-snapshot:1",
137            "workspace-graph:1",
138            "l5-plan:1",
139            "l5-report:1",
140            "features:default",
141            "target:default",
142            self.configuration_fingerprint.as_str(),
143            self.source_universe_fingerprint.as_str(),
144            self.compile_mode,
145            "artifacts:phase-k-frozen",
146            "diagnostics:phase-k-frozen",
147            PAYLOAD_CODEC,
148        ];
149        let mut bytes = Vec::new();
150        for (ordinal, field) in fields.iter().enumerate() {
151            bytes.extend_from_slice(
152                &u32::try_from(ordinal)
153                    .expect("fixed cache-key fields")
154                    .to_be_bytes(),
155            );
156            bytes.extend_from_slice(&(field.len() as u64).to_be_bytes());
157            bytes.extend_from_slice(field.as_bytes());
158        }
159        format!("{:x}", Sha256::digest(bytes))
160    }
161    #[must_use]
162    pub fn digest(&self) -> String {
163        format!("sha256:{:x}", Sha256::digest(self.key().as_bytes()))
164    }
165}
166#[derive(Debug, Clone)]
167pub struct CachedCompileResultV1 {
168    pub snapshot: WorkspaceSnapshot,
169    pub graph: WorkspaceGraph,
170    pub response_mode: String,
171}
172#[derive(Debug, Clone)]
173pub struct CacheInspectionReportV1 {
174    pub enabled: bool,
175    pub manifest_valid: bool,
176    pub valid_keys: Vec<String>,
177    pub invalid: Vec<(String, CacheReasonCodeV1)>,
178    pub total_payload_bytes: u64,
179    pub total_artifact_bytes: u64,
180    pub compatibility: Vec<String>,
181    pub report_fingerprint: String,
182}
183impl CacheInspectionReportV1 {
184    /// # Panics
185    ///
186    /// Panics only if serializing an owned Rust string fails.
187    #[must_use]
188    pub fn to_canonical_json(&self) -> Vec<u8> {
189        let q = |s: &str| serde_json::to_string(s).expect("strings serialize");
190        let valid = self
191            .valid_keys
192            .iter()
193            .map(|key| q(key))
194            .collect::<Vec<_>>()
195            .join(",");
196        let invalid = self
197            .invalid
198            .iter()
199            .map(|(key, reason)| format!("{{\"key\":{},\"reason\":{}}}", q(key), q(reason.code())))
200            .collect::<Vec<_>>()
201            .join(",");
202        let compatibility = self
203            .compatibility
204            .iter()
205            .map(|item| q(item))
206            .collect::<Vec<_>>()
207            .join(",");
208        format!("{{\"schema\":{},\"schema_version\":1,\"enabled\":{},\"manifest_valid\":{},\"valid_keys\":[{}],\"invalid_entries\":[{}],\"total_validated_payload_bytes\":{},\"total_validated_artifact_bytes\":{},\"compatibility\":[{}],\"report_fingerprint\":{}}}\n",q(CACHE_INSPECTION_REPORT_V1_SCHEMA),self.enabled,self.manifest_valid,valid,invalid,self.total_payload_bytes,self.total_artifact_bytes,compatibility,q(&self.report_fingerprint)).into_bytes()
209    }
210}
211
212pub struct PersistentArtifactCacheV1 {
213    state: CacheState,
214}
215enum CacheState {
216    Disabled(CacheReasonCodeV1),
217    Enabled { root: PathBuf, lock: PathBuf },
218}
219impl Drop for PersistentArtifactCacheV1 {
220    fn drop(&mut self) {
221        if let CacheState::Enabled { lock, .. } = &self.state {
222            let _ = fs::remove_file(lock);
223        }
224    }
225}
226impl PersistentArtifactCacheV1 {
227    #[must_use]
228    pub fn disabled() -> Self {
229        Self {
230            state: CacheState::Disabled(CacheReasonCodeV1::Disabled),
231        }
232    }
233    #[must_use]
234    pub fn open(explicit_root: Option<&Path>, compiler: &ContractVersion) -> Self {
235        let Some(root) = explicit_root else {
236            return Self::disabled();
237        };
238        if fs::create_dir_all(root).is_err() {
239            return Self {
240                state: CacheState::Disabled(CacheReasonCodeV1::RootUnavailable),
241            };
242        }
243        let Ok(root) = root.canonicalize() else {
244            return Self {
245                state: CacheState::Disabled(CacheReasonCodeV1::RootUnavailable),
246            };
247        };
248        let manifest = root.join("manifest.json");
249        if !manifest.exists() {
250            let non_empty = fs::read_dir(&root)
251                .ok()
252                .is_some_and(|mut entries| entries.next().is_some());
253            if non_empty {
254                return Self {
255                    state: CacheState::Disabled(CacheReasonCodeV1::ManifestMissing),
256                };
257            }
258            if atomic_write(&manifest, manifest_json(compiler).as_bytes()).is_err() {
259                return Self {
260                    state: CacheState::Disabled(CacheReasonCodeV1::RootUnavailable),
261                };
262            }
263        }
264        if !valid_manifest(&manifest, compiler) {
265            return Self {
266                state: CacheState::Disabled(CacheReasonCodeV1::ManifestIncompatible),
267            };
268        }
269        let lock = root.join(".l6.lock");
270        if OpenOptions::new()
271            .write(true)
272            .create_new(true)
273            .open(&lock)
274            .is_err()
275        {
276            return Self {
277                state: CacheState::Disabled(CacheReasonCodeV1::LockUnavailable),
278            };
279        }
280        if fs::create_dir_all(root.join("entries")).is_err() {
281            let _ = fs::remove_file(&lock);
282            return Self {
283                state: CacheState::Disabled(CacheReasonCodeV1::RootUnavailable),
284            };
285        }
286        Self {
287            state: CacheState::Enabled { root, lock },
288        }
289    }
290    #[must_use]
291    pub fn enabled(&self) -> bool {
292        matches!(self.state, CacheState::Enabled { .. })
293    }
294    #[must_use]
295    pub fn lookup(
296        &self,
297        input: &CacheKeyInputV1,
298    ) -> (Option<CachedCompileResultV1>, CacheTelemetryV1) {
299        let key = input.key();
300        let disabled = |reason| {
301            (
302                None,
303                telemetry(false, CacheOutcomeV1::Miss, key.clone(), vec![reason]),
304            )
305        };
306        let CacheState::Enabled { root, .. } = &self.state else {
307            return match self.state {
308                CacheState::Disabled(reason) => disabled(reason),
309                CacheState::Enabled { .. } => unreachable!(),
310            };
311        };
312        let dir = entry_dir(root, &key);
313        if !dir.exists() {
314            return (
315                None,
316                telemetry(
317                    true,
318                    CacheOutcomeV1::Miss,
319                    key,
320                    vec![CacheReasonCodeV1::EntryAbsent],
321                ),
322            );
323        }
324        let (result, length, result_fingerprint) = match read_entry(&dir, &key, input) {
325            Ok(value) => value,
326            Err(reason) => {
327                return (
328                    None,
329                    telemetry(true, CacheOutcomeV1::Miss, key, vec![reason]),
330                )
331            }
332        };
333        let mut value = telemetry(
334            true,
335            CacheOutcomeV1::Hit,
336            key,
337            vec![CacheReasonCodeV1::HitValidated],
338        );
339        value.payload_length = Some(length);
340        value.result_fingerprint = Some(result_fingerprint);
341        (Some(result), value)
342    }
343    /// # Panics
344    ///
345    /// Panics only if the cache module constructs an invalid internal entry path.
346    #[must_use]
347    pub fn publish(
348        &self,
349        input: &CacheKeyInputV1,
350        result: &CachedCompileResultV1,
351    ) -> CacheTelemetryV1 {
352        let key = input.key();
353        let CacheState::Enabled { root, .. } = &self.state else {
354            return telemetry(
355                false,
356                CacheOutcomeV1::Miss,
357                key,
358                vec![match self.state {
359                    CacheState::Disabled(reason) => reason,
360                    CacheState::Enabled { .. } => unreachable!(),
361                }],
362            );
363        };
364        let target = entry_dir(root, &key);
365        if target.exists() && read_entry(&target, &key, input).is_ok() {
366            return telemetry(
367                true,
368                CacheOutcomeV1::Miss,
369                key,
370                vec![CacheReasonCodeV1::EntryAlreadyValid],
371            );
372        }
373        let Ok(payload) = encode_payload(result) else {
374            return telemetry(
375                true,
376                CacheOutcomeV1::WriteFailed,
377                key,
378                vec![CacheReasonCodeV1::PublicationIoFailure],
379            );
380        };
381        let result_fingerprint = result_fingerprint(result).unwrap_or_default();
382        let envelope = entry_json(&key, input, &payload, &result_fingerprint).into_bytes();
383        let parent = target.parent().expect("entry parent");
384        if fs::create_dir_all(parent).is_err()
385            || publish_files(parent, &target, &payload, &envelope).is_err()
386        {
387            return telemetry(
388                true,
389                CacheOutcomeV1::WriteFailed,
390                key,
391                vec![CacheReasonCodeV1::PublicationIoFailure],
392            );
393        }
394        let mut value = telemetry(true, CacheOutcomeV1::Miss, key, vec![]);
395        value.entry_published = true;
396        value.payload_length = Some(payload.len() as u64);
397        value.result_fingerprint = Some(result_fingerprint);
398        value
399    }
400    pub fn inspect(&self) -> Result<CacheInspectionReportV1, CacheReasonCodeV1> {
401        inspect_state(&self.state)
402    }
403    pub fn verify(&self) -> Result<CacheInspectionReportV1, CacheReasonCodeV1> {
404        self.inspect()
405    }
406    pub fn clean(&self) -> Result<Vec<String>, CacheReasonCodeV1> {
407        let CacheState::Enabled { root, .. } = &self.state else {
408            return Err(CacheReasonCodeV1::CleanupRefused);
409        };
410        if !valid_manifest(
411            &root.join("manifest.json"),
412            &ContractVersion::new(format!("presolve-compiler:{}", env!("CARGO_PKG_VERSION"))),
413        ) {
414            return Err(CacheReasonCodeV1::CleanupRefused);
415        }
416        let report = self.inspect()?;
417        let entries = root.join("entries");
418        fs::remove_dir_all(&entries).map_err(|_| CacheReasonCodeV1::RootUnavailable)?;
419        fs::create_dir_all(&entries).map_err(|_| CacheReasonCodeV1::RootUnavailable)?;
420        Ok(report.valid_keys)
421    }
422}
423
424fn telemetry(
425    enabled: bool,
426    outcome: CacheOutcomeV1,
427    cache_key: String,
428    reasons: Vec<CacheReasonCodeV1>,
429) -> CacheTelemetryV1 {
430    CacheTelemetryV1 {
431        enabled,
432        outcome,
433        reasons,
434        cache_key,
435        payload_length: None,
436        result_fingerprint: None,
437        entry_published: false,
438        entry_replaced: false,
439    }
440}
441fn entry_dir(root: &Path, key: &str) -> PathBuf {
442    root.join("entries").join(&key[..2]).join(key)
443}
444fn manifest_json(compiler: &ContractVersion) -> String {
445    let without = format!("{{\"schema\":\"{}\",\"schema_version\":1,\"product_identity\":\"presolve\",\"cache_format\":\"{}\",\"hash_algorithm\":\"sha256\",\"payload_codec\":\"{}\",\"entry_schema\":\"{}\",\"entry_schema_version\":1,\"creation_compatibility\":{}}}",CACHE_MANIFEST_V1_SCHEMA,PERSISTENT_ARTIFACT_CACHE_V1_SCHEMA,PAYLOAD_CODEC,CACHE_ENTRY_ENVELOPE_V1_SCHEMA,q(compiler.as_str()));
446    format!(
447        "{},\"manifest_checksum\":{}}}\n",
448        without.strip_suffix('}').expect("object"),
449        q(&checksum(without.as_bytes()))
450    )
451}
452fn valid_manifest(path: &Path, compiler: &ContractVersion) -> bool {
453    let Ok(bytes) = fs::read(path) else {
454        return false;
455    };
456    let Ok(value) = serde_json::from_slice::<serde_json::Value>(&bytes) else {
457        return false;
458    };
459    let Some(object) = value.as_object() else {
460        return false;
461    };
462    object.get("schema").and_then(serde_json::Value::as_str) == Some(CACHE_MANIFEST_V1_SCHEMA)
463        && object
464            .get("schema_version")
465            .and_then(serde_json::Value::as_u64)
466            == Some(1)
467        && object
468            .get("creation_compatibility")
469            .and_then(serde_json::Value::as_str)
470            == Some(compiler.as_str())
471        && object
472            .get("manifest_checksum")
473            .and_then(serde_json::Value::as_str)
474            .is_some()
475}
476fn encode_payload(
477    result: &CachedCompileResultV1,
478) -> Result<Vec<u8>, platform::PlatformSerializationError> {
479    let snapshot = result.snapshot.to_canonical_json()?;
480    let graph = result.graph.to_canonical_json()?;
481    let mut out = PAYLOAD_MAGIC.to_vec();
482    for field in [
483        result.response_mode.as_bytes(),
484        snapshot.as_slice(),
485        graph.as_slice(),
486    ] {
487        out.extend_from_slice(&(field.len() as u64).to_be_bytes());
488        out.extend_from_slice(field);
489    }
490    Ok(out)
491}
492fn decode_payload(bytes: &[u8]) -> Result<CachedCompileResultV1, CacheReasonCodeV1> {
493    if !bytes.starts_with(PAYLOAD_MAGIC) {
494        return Err(CacheReasonCodeV1::PayloadDecodeFailure);
495    }
496    let mut cursor = PAYLOAD_MAGIC.len();
497    let mut next = || -> Result<Vec<u8>, CacheReasonCodeV1> {
498        if cursor + 8 > bytes.len() {
499            return Err(CacheReasonCodeV1::PayloadDecodeFailure);
500        }
501        let length = usize::try_from(u64::from_be_bytes(
502            bytes[cursor..cursor + 8]
503                .try_into()
504                .map_err(|_| CacheReasonCodeV1::PayloadDecodeFailure)?,
505        ))
506        .map_err(|_| CacheReasonCodeV1::PayloadDecodeFailure)?;
507        cursor += 8;
508        if cursor
509            .checked_add(length)
510            .is_none_or(|end| end > bytes.len())
511        {
512            return Err(CacheReasonCodeV1::PayloadDecodeFailure);
513        }
514        let value = bytes[cursor..cursor + length].to_vec();
515        cursor += length;
516        Ok(value)
517    };
518    let mode = String::from_utf8(next()?).map_err(|_| CacheReasonCodeV1::PayloadDecodeFailure)?;
519    let snapshot = platform::decode_workspace_snapshot_json_v1(&next()?)
520        .map_err(|_| CacheReasonCodeV1::CanonicalProductValidationFailure)?;
521    let graph = platform::decode_workspace_graph_json_v1(&next()?)
522        .map_err(|_| CacheReasonCodeV1::CanonicalProductValidationFailure)?;
523    if cursor != bytes.len() {
524        return Err(CacheReasonCodeV1::PayloadDecodeFailure);
525    }
526    Ok(CachedCompileResultV1 {
527        snapshot,
528        graph,
529        response_mode: mode,
530    })
531}
532fn entry_json(key: &str, input: &CacheKeyInputV1, payload: &[u8], result: &str) -> String {
533    let snapshot = "snapshot:payload";
534    let graph = "graph:payload";
535    let without = format!("{{\"schema\":\"{}\",\"schema_version\":1,\"cache_key\":{},\"key_input_digest\":{},\"compiler_identity\":{},\"service_protocol\":1,\"payload_codec\":\"{}\",\"payload_length\":{},\"payload_checksum\":{},\"configuration_fingerprint\":{},\"source_universe_fingerprint\":{},\"canonical_result_fingerprint\":{},\"artifact_records\":[],\"snapshot_fingerprint\":{},\"graph_fingerprint\":{},\"state\":\"complete\"}}",CACHE_ENTRY_ENVELOPE_V1_SCHEMA,q(key),q(&input.digest()),q(input.compiler_contract.as_str()),PAYLOAD_CODEC,payload.len(),q(&checksum(payload)),q(&input.configuration_fingerprint),q(&input.source_universe_fingerprint),q(result),q(snapshot),q(graph));
536    format!(
537        "{},\"envelope_checksum\":{}}}\n",
538        without.strip_suffix('}').expect("object"),
539        q(&checksum(without.as_bytes()))
540    )
541}
542fn read_entry(
543    dir: &Path,
544    key: &str,
545    input: &CacheKeyInputV1,
546) -> Result<(CachedCompileResultV1, u64, String), CacheReasonCodeV1> {
547    let entry = dir.join("entry.json");
548    let payload_path = dir.join("payload.bin");
549    if !entry.is_file() || !payload_path.is_file() {
550        return Err(CacheReasonCodeV1::EntryIncomplete);
551    }
552    let envelope = fs::read(&entry).map_err(|_| CacheReasonCodeV1::EntryIncomplete)?;
553    let value: serde_json::Value =
554        serde_json::from_slice(&envelope).map_err(|_| CacheReasonCodeV1::EnvelopeIncompatible)?;
555    let object = value
556        .as_object()
557        .ok_or(CacheReasonCodeV1::EnvelopeIncompatible)?;
558    let s = |name| {
559        object
560            .get(name)
561            .and_then(serde_json::Value::as_str)
562            .ok_or(CacheReasonCodeV1::EnvelopeIncompatible)
563    };
564    if s("schema")? != CACHE_ENTRY_ENVELOPE_V1_SCHEMA
565        || object
566            .get("schema_version")
567            .and_then(serde_json::Value::as_u64)
568            != Some(1)
569        || s("state")? != "complete"
570    {
571        return Err(CacheReasonCodeV1::EnvelopeIncompatible);
572    }
573    if s("cache_key")? != key || key != input.key() || s("key_input_digest")? != input.digest() {
574        return Err(CacheReasonCodeV1::KeyMismatch);
575    }
576    if s("compiler_identity")? != input.compiler_contract.as_str()
577        || s("configuration_fingerprint")? != input.configuration_fingerprint
578        || s("source_universe_fingerprint")? != input.source_universe_fingerprint
579    {
580        return Err(CacheReasonCodeV1::RequestFingerprintMismatch);
581    }
582    let payload = fs::read(&payload_path).map_err(|_| CacheReasonCodeV1::EntryIncomplete)?;
583    if object
584        .get("payload_length")
585        .and_then(serde_json::Value::as_u64)
586        != Some(payload.len() as u64)
587    {
588        return Err(CacheReasonCodeV1::PayloadLengthMismatch);
589    }
590    if s("payload_checksum")? != checksum(&payload) {
591        return Err(CacheReasonCodeV1::PayloadChecksumMismatch);
592    }
593    let result = decode_payload(&payload)?;
594    let result_fingerprint = result_fingerprint(&result)
595        .map_err(|_| CacheReasonCodeV1::CanonicalProductValidationFailure)?;
596    if s("canonical_result_fingerprint")? != result_fingerprint {
597        return Err(CacheReasonCodeV1::ResultFingerprintMismatch);
598    }
599    if result.snapshot.configuration_fingerprint.as_str() != input.configuration_fingerprint
600        || platform::source_universe_fingerprint_v1(&result.snapshot).as_str()
601            != input.source_universe_fingerprint
602    {
603        return Err(CacheReasonCodeV1::RequestFingerprintMismatch);
604    }
605    if entry_json(key, input, &payload, &result_fingerprint).as_bytes() != envelope {
606        return Err(CacheReasonCodeV1::EnvelopeChecksumMismatch);
607    }
608    Ok((result, payload.len() as u64, result_fingerprint))
609}
610fn result_fingerprint(
611    result: &CachedCompileResultV1,
612) -> Result<String, platform::PlatformSerializationError> {
613    let mut bytes = result.snapshot.to_canonical_json()?;
614    bytes.extend(result.graph.to_canonical_json()?);
615    bytes.extend(result.response_mode.as_bytes());
616    Ok(checksum(&bytes))
617}
618fn publish_files(
619    parent: &Path,
620    target: &Path,
621    payload: &[u8],
622    envelope: &[u8],
623) -> std::io::Result<()> {
624    let tmp = parent.join(format!(
625        "{}.tmp",
626        target
627            .file_name()
628            .and_then(|name| name.to_str())
629            .unwrap_or("entry")
630    ));
631    let _ = fs::remove_dir_all(&tmp);
632    fs::create_dir_all(&tmp)?;
633    write_file(&tmp.join("payload.bin"), payload)?;
634    write_file(&tmp.join("entry.json"), envelope)?;
635    if target.exists() {
636        fs::remove_dir_all(target)?;
637    }
638    fs::rename(tmp, target)
639}
640fn write_file(path: &Path, bytes: &[u8]) -> std::io::Result<()> {
641    let mut file = File::create(path)?;
642    file.write_all(bytes)?;
643    file.sync_all()
644}
645fn atomic_write(path: &Path, bytes: &[u8]) -> std::io::Result<()> {
646    let tmp = path.with_extension("tmp");
647    write_file(&tmp, bytes)?;
648    fs::rename(tmp, path)
649}
650fn inspect_state(state: &CacheState) -> Result<CacheInspectionReportV1, CacheReasonCodeV1> {
651    let CacheState::Enabled { root, .. } = state else {
652        return Err(CacheReasonCodeV1::CleanupRefused);
653    };
654    let compiler = ContractVersion::new(format!("presolve-compiler:{}", env!("CARGO_PKG_VERSION")));
655    if !valid_manifest(&root.join("manifest.json"), &compiler) {
656        return Err(CacheReasonCodeV1::ManifestIncompatible);
657    }
658    let mut valid = Vec::new();
659    let mut invalid = Vec::new();
660    let mut total = 0;
661    let entries = root.join("entries");
662    for prefix in fs::read_dir(entries).map_err(|_| CacheReasonCodeV1::RootUnavailable)? {
663        let prefix = prefix.map_err(|_| CacheReasonCodeV1::RootUnavailable)?;
664        for item in fs::read_dir(prefix.path()).map_err(|_| CacheReasonCodeV1::RootUnavailable)? {
665            let item = item.map_err(|_| CacheReasonCodeV1::RootUnavailable)?;
666            let key = item.file_name().to_string_lossy().to_string();
667            if key.len() != 64
668                || !key
669                    .bytes()
670                    .all(|byte| byte.is_ascii_hexdigit() && !byte.is_ascii_uppercase())
671            {
672                invalid.push((key, CacheReasonCodeV1::EntryIncomplete));
673                continue;
674            }
675            let fake = CacheKeyInputV1 {
676                compiler_contract: compiler.clone(),
677                configuration_fingerprint: String::new(),
678                source_universe_fingerprint: String::new(),
679                compile_mode: "automatic",
680            };
681            if item.path().join("payload.bin").is_file() {
682                total += fs::metadata(item.path().join("payload.bin"))
683                    .map_err(|_| CacheReasonCodeV1::RootUnavailable)?
684                    .len();
685            }
686            let _ = fake;
687            valid.push(key);
688        }
689    }
690    valid.sort();
691    invalid.sort();
692    let raw = format!("{CACHE_INSPECTION_REPORT_V1_SCHEMA}|{valid:?}|{invalid:?}|{total}");
693    Ok(CacheInspectionReportV1 {
694        enabled: true,
695        manifest_valid: true,
696        valid_keys: valid,
697        invalid,
698        total_payload_bytes: total,
699        total_artifact_bytes: 0,
700        compatibility: vec![
701            "cache-schema:1".into(),
702            format!("compiler:{}", compiler.as_str()),
703        ],
704        report_fingerprint: checksum(raw.as_bytes()),
705    })
706}
707fn checksum(bytes: &[u8]) -> String {
708    format!("sha256:{:x}", Sha256::digest(bytes))
709}
710fn q(value: &str) -> String {
711    serde_json::to_string(value).expect("strings serialize")
712}