Skip to main content

presolve_compiler/
production_bootstrap.rs

1//! K10 validated canonical-ID to ordinal production bootstrap indexes.
2
3use std::collections::{BTreeMap, BTreeSet};
4
5use crate::{ProductionRuntimeArtifactV1, ProductionRuntimeTableRegistry};
6
7#[derive(Clone, Debug, Eq, PartialEq)]
8pub struct ProductionRuntimeIndexes {
9    pub anchors: BTreeMap<String, u32>,
10    pub events: BTreeMap<String, u32>,
11    pub activations: BTreeMap<String, u32>,
12    pub activation_roots: BTreeMap<String, u32>,
13}
14
15#[derive(Clone, Debug, Eq, PartialEq)]
16pub struct ProductionBootstrapPlan {
17    pub indexes: ProductionRuntimeIndexes,
18    pub listener_event_types: Vec<String>,
19    pub ready: bool,
20}
21
22#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
23pub enum ProductionBootstrapBlock {
24    MissingTable(String),
25    UnknownEventAnchor(String),
26    UnknownEventActivation(String),
27}
28
29/// Builds closed ordinal indexes only after the K8 artifact has been validated.
30///
31/// # Errors
32///
33/// Returns deterministic blocks when frozen manifest event references do not
34/// resolve through the packed artifact tables.
35pub fn build_production_bootstrap_plan(
36    artifact: &ProductionRuntimeArtifactV1,
37    manifest: &crate::ResumeManifest,
38) -> Result<ProductionBootstrapPlan, Vec<ProductionBootstrapBlock>> {
39    let indexes = ProductionRuntimeIndexes {
40        anchors: table_index(&artifact.tables, "anchors")?,
41        events: table_index(&artifact.tables, "events")?,
42        activations: table_index(&artifact.tables, "activations")?,
43        activation_roots: table_index(&artifact.tables, "activation_roots")?,
44    };
45    let activation_by_event = manifest
46        .activations
47        .iter()
48        .filter_map(|activation| {
49            activation
50                .event_id
51                .as_ref()
52                .map(|event| (event.to_string(), activation.activation_id.to_string()))
53        })
54        .collect::<BTreeMap<_, _>>();
55    let mut blocks = Vec::new();
56    let mut listener_event_types = BTreeSet::new();
57    for event in &manifest.events {
58        let event_id = event.resume_event_id.to_string();
59        if !indexes.events.contains_key(&event_id)
60            || !indexes
61                .anchors
62                .contains_key(&event.exact_target_anchor_id.to_string())
63        {
64            blocks.push(ProductionBootstrapBlock::UnknownEventAnchor(
65                event_id.clone(),
66            ));
67        }
68        if activation_by_event
69            .get(&event_id)
70            .is_none_or(|activation| !indexes.activations.contains_key(activation))
71        {
72            blocks.push(ProductionBootstrapBlock::UnknownEventActivation(event_id));
73        }
74        listener_event_types.insert(event.event_type.clone());
75    }
76    blocks.sort();
77    blocks.dedup();
78    if !blocks.is_empty() {
79        return Err(blocks);
80    }
81    Ok(ProductionBootstrapPlan {
82        indexes,
83        listener_event_types: listener_event_types.into_iter().collect(),
84        ready: true,
85    })
86}
87
88fn table_index(
89    registry: &ProductionRuntimeTableRegistry,
90    table_kind: &str,
91) -> Result<BTreeMap<String, u32>, Vec<ProductionBootstrapBlock>> {
92    let Some(table) = registry
93        .tables
94        .iter()
95        .find(|table| table.table_kind == table_kind)
96    else {
97        return Err(vec![ProductionBootstrapBlock::MissingTable(
98            table_kind.to_string(),
99        )]);
100    };
101    Ok(table
102        .mappings
103        .iter()
104        .map(|mapping| (mapping.canonical_id.clone(), mapping.ordinal))
105        .collect())
106}
107
108#[cfg(test)]
109mod tests {
110    use super::*;
111    use crate::{
112        build_production_runtime_artifact, extract_production_chunk_graph,
113        ExecutableProgramFingerprint, ProductionRootChunkInput, ResumeBoundaryId, ResumeBuildId,
114        ResumeManifest, SharedChunkCandidatePlan,
115    };
116    use std::str::FromStr;
117
118    fn manifest() -> ResumeManifest {
119        ResumeManifest {
120            schema_version: 6,
121            build_id: ResumeBuildId::zero_sentinel(),
122            snapshot_schema_version: 1,
123            runtime_protocol_version: 1,
124            application_root_boundary_id: ResumeBoundaryId::from_str("resume-boundary:root")
125                .expect("boundary"),
126            boundaries: Vec::new(),
127            slot_schemas: Vec::new(),
128            capture_programs: Vec::new(),
129            restore_programs: Vec::new(),
130            chunks: Vec::new(),
131            activations: Vec::new(),
132            anchors: Vec::new(),
133            events: Vec::new(),
134            phase_i_component_resume_records: Vec::new(),
135            phase_i_form_resume_records: Vec::new(),
136        }
137    }
138
139    #[test]
140    fn k10_builds_closed_indexes_only_after_artifact_table_validation() {
141        let manifest = manifest();
142        let graph = extract_production_chunk_graph(
143            &SharedChunkCandidatePlan {
144                candidates: Vec::new(),
145                rejections: Vec::new(),
146            },
147            &[ProductionRootChunkInput {
148                activation_root_id: "root".to_string(),
149                root_kind: "interaction".to_string(),
150                programs: vec![ExecutableProgramFingerprint::for_canonical_opcode_stream(
151                    b"a",
152                )],
153            }],
154        )
155        .expect("graph")
156        .0;
157        let artifact = build_production_runtime_artifact(&manifest, &graph).expect("artifact");
158        assert!(
159            build_production_bootstrap_plan(&artifact, &manifest)
160                .expect("plan")
161                .ready
162        );
163        let mut malformed = artifact;
164        malformed
165            .tables
166            .tables
167            .retain(|table| table.table_kind != "events");
168        assert_eq!(
169            build_production_bootstrap_plan(&malformed, &manifest),
170            Err(vec![ProductionBootstrapBlock::MissingTable(
171                "events".to_string()
172            )])
173        );
174    }
175}