Skip to main content

presolve_compiler/
production_validation.rs

1//! K14 ordered V0-V10 production validation and compact failure records.
2
3use serde::{Deserialize, Serialize};
4
5use crate::{
6    parse_production_runtime_artifact_v1, ProductionArtifactIntegrityViolation,
7    ProductionRuntimeArtifactV1, ResumeBuildId,
8};
9
10#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, Serialize, Deserialize)]
11pub enum ProductionValidationPhase {
12    V0,
13    V1,
14    V2,
15    V3,
16    V4,
17    V5,
18    V6,
19    V7,
20    V8,
21    V9,
22    V10,
23}
24
25#[derive(Clone, Debug, Eq, PartialEq)]
26#[allow(clippy::struct_excessive_bools)]
27pub struct ProductionValidationEvidence {
28    pub endpoints_valid: bool,
29    pub fingerprints_and_aliases_valid: bool,
30    pub chunk_exports_valid: bool,
31    pub boot_entry_closed: bool,
32    pub resume_products_agree: bool,
33    pub anchor_event_tables_agree: bool,
34    pub cleanup_closure_valid: bool,
35}
36
37impl ProductionValidationEvidence {
38    #[must_use]
39    pub const fn all_valid() -> Self {
40        Self {
41            endpoints_valid: true,
42            fingerprints_and_aliases_valid: true,
43            chunk_exports_valid: true,
44            boot_entry_closed: true,
45            resume_products_agree: true,
46            anchor_event_tables_agree: true,
47            cleanup_closure_valid: true,
48        }
49    }
50}
51
52#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
53#[serde(deny_unknown_fields)]
54pub struct ProductionRuntimeFailure {
55    pub class: String,
56    pub code: String,
57    pub build_id: ResumeBuildId,
58    pub subject_kind: String,
59    pub subject_id_or_ordinal: String,
60    pub subject_trusted: bool,
61    pub phase: ProductionValidationPhase,
62}
63
64#[derive(Clone, Debug, Eq, PartialEq)]
65pub struct ProductionValidationResult {
66    pub artifact: ProductionRuntimeArtifactV1,
67    pub completed_phases: Vec<ProductionValidationPhase>,
68}
69
70/// Runs V0-V10 in exact order before any authored execution is permitted.
71///
72/// # Errors
73///
74/// Returns one stable compact failure at the first invalid phase.
75pub fn validate_production_runtime_pipeline(
76    json: &str,
77    expected_build_id: &ResumeBuildId,
78    evidence: &ProductionValidationEvidence,
79) -> Result<ProductionValidationResult, Box<ProductionRuntimeFailure>> {
80    if contains_forbidden_generic_key(json) {
81        return Err(Box::new(failure(
82            expected_build_id,
83            ProductionValidationPhase::V0,
84            "PSPRD1400",
85            "schema",
86            "untrusted-field",
87            false,
88        )));
89    }
90    let artifact =
91        parse_production_runtime_artifact_v1(json, expected_build_id).map_err(|violations| {
92            let phase = integrity_phase(&violations);
93            Box::new(failure(
94                expected_build_id,
95                phase,
96                "PSPRD1401",
97                "artifact",
98                "untrusted",
99                false,
100            ))
101        })?;
102    let checks = [
103        (ProductionValidationPhase::V4, evidence.endpoints_valid),
104        (
105            ProductionValidationPhase::V5,
106            evidence.fingerprints_and_aliases_valid,
107        ),
108        (ProductionValidationPhase::V6, evidence.chunk_exports_valid),
109        (ProductionValidationPhase::V7, evidence.boot_entry_closed),
110        (
111            ProductionValidationPhase::V8,
112            evidence.resume_products_agree,
113        ),
114        (
115            ProductionValidationPhase::V9,
116            evidence.anchor_event_tables_agree,
117        ),
118        (
119            ProductionValidationPhase::V10,
120            evidence.cleanup_closure_valid,
121        ),
122    ];
123    for (phase, valid) in checks {
124        if !valid {
125            return Err(Box::new(failure(
126                &artifact.build_id,
127                phase,
128                "PSPRD1402",
129                "validation",
130                "closed-product",
131                true,
132            )));
133        }
134    }
135    Ok(ProductionValidationResult {
136        artifact,
137        completed_phases: vec![
138            ProductionValidationPhase::V0,
139            ProductionValidationPhase::V1,
140            ProductionValidationPhase::V2,
141            ProductionValidationPhase::V3,
142            ProductionValidationPhase::V4,
143            ProductionValidationPhase::V5,
144            ProductionValidationPhase::V6,
145            ProductionValidationPhase::V7,
146            ProductionValidationPhase::V8,
147            ProductionValidationPhase::V9,
148            ProductionValidationPhase::V10,
149        ],
150    })
151}
152
153fn integrity_phase(
154    violations: &[ProductionArtifactIntegrityViolation],
155) -> ProductionValidationPhase {
156    if violations.iter().any(|violation| {
157        matches!(
158            violation,
159            ProductionArtifactIntegrityViolation::SchemaVersionMismatch
160        )
161    }) {
162        ProductionValidationPhase::V0
163    } else if violations.iter().any(|violation| {
164        matches!(
165            violation,
166            ProductionArtifactIntegrityViolation::BuildIdMismatch
167                | ProductionArtifactIntegrityViolation::RuntimeProtocolMismatch
168        )
169    }) {
170        ProductionValidationPhase::V1
171    } else if violations.iter().any(|violation| {
172        matches!(
173            violation,
174            ProductionArtifactIntegrityViolation::TableChecksumMismatch(_)
175                | ProductionArtifactIntegrityViolation::ArtifactChecksumMismatch
176        )
177    }) {
178        ProductionValidationPhase::V2
179    } else {
180        ProductionValidationPhase::V3
181    }
182}
183
184fn contains_forbidden_generic_key(json: &str) -> bool {
185    ["\"__proto__\"", "\"constructor\"", "\"prototype\""]
186        .iter()
187        .any(|key| json.contains(key))
188}
189
190fn failure(
191    build_id: &ResumeBuildId,
192    phase: ProductionValidationPhase,
193    code: &str,
194    subject_kind: &str,
195    subject: &str,
196    trusted: bool,
197) -> ProductionRuntimeFailure {
198    ProductionRuntimeFailure {
199        class: "ProductionArtifactMismatch".to_string(),
200        code: code.to_string(),
201        build_id: build_id.clone(),
202        subject_kind: subject_kind.to_string(),
203        subject_id_or_ordinal: subject.to_string(),
204        subject_trusted: trusted,
205        phase,
206    }
207}
208
209#[cfg(test)]
210mod tests {
211    use super::*;
212    use crate::{
213        build_production_runtime_artifact, extract_production_chunk_graph,
214        production_runtime_artifact_json, ExecutableProgramFingerprint, ProductionRootChunkInput,
215        ResumeBoundaryId, ResumeManifest, SharedChunkCandidatePlan,
216    };
217    use std::str::FromStr;
218
219    fn valid_json() -> (String, ResumeBuildId) {
220        let manifest = ResumeManifest {
221            schema_version: 6,
222            build_id: ResumeBuildId::zero_sentinel(),
223            snapshot_schema_version: 1,
224            runtime_protocol_version: 1,
225            application_root_boundary_id: ResumeBoundaryId::from_str("resume-boundary:root")
226                .expect("boundary"),
227            boundaries: Vec::new(),
228            slot_schemas: Vec::new(),
229            capture_programs: Vec::new(),
230            restore_programs: Vec::new(),
231            chunks: Vec::new(),
232            activations: Vec::new(),
233            anchors: Vec::new(),
234            events: Vec::new(),
235            phase_i_component_resume_records: Vec::new(),
236            phase_i_form_resume_records: Vec::new(),
237        };
238        let graph = extract_production_chunk_graph(
239            &SharedChunkCandidatePlan {
240                candidates: Vec::new(),
241                rejections: Vec::new(),
242            },
243            &[ProductionRootChunkInput {
244                activation_root_id: "root".to_string(),
245                root_kind: "interaction".to_string(),
246                programs: vec![ExecutableProgramFingerprint::for_canonical_opcode_stream(
247                    b"a",
248                )],
249            }],
250        )
251        .expect("graph")
252        .0;
253        let artifact = build_production_runtime_artifact(&manifest, &graph).expect("artifact");
254        (
255            production_runtime_artifact_json(&artifact),
256            manifest.build_id,
257        )
258    }
259
260    #[test]
261    fn k14_runs_all_phases_and_stops_before_authored_execution() {
262        let (json, build_id) = valid_json();
263        let result = validate_production_runtime_pipeline(
264            &json,
265            &build_id,
266            &ProductionValidationEvidence::all_valid(),
267        )
268        .expect("validated pipeline");
269        assert_eq!(result.completed_phases.len(), 11);
270        let mut invalid = ProductionValidationEvidence::all_valid();
271        invalid.cleanup_closure_valid = false;
272        let failure = validate_production_runtime_pipeline(&json, &build_id, &invalid)
273            .expect_err("V10 failure");
274        assert_eq!(failure.phase, ProductionValidationPhase::V10);
275        assert!(!serde_json::to_string(&failure)
276            .expect("failure JSON")
277            .contains("src/"));
278    }
279
280    #[test]
281    fn k14_rejects_prototype_pollution_as_untrusted_v0_input() {
282        let build_id = ResumeBuildId::zero_sentinel();
283        let failure = validate_production_runtime_pipeline(
284            r#"{"__proto__":{}}"#,
285            &build_id,
286            &ProductionValidationEvidence::all_valid(),
287        )
288        .expect_err("prototype key");
289        assert_eq!(failure.phase, ProductionValidationPhase::V0);
290        assert!(!failure.subject_trusted);
291    }
292}