presolve_compiler/
production_bootstrap.rs1use 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
29pub 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}