1use std::collections::BTreeSet;
4
5use crate::{
6 ResumeManifest, RuntimeComponentArtifact, RuntimeComputedArtifact, RuntimeContextArtifact,
7 RuntimeEffectArtifact, RuntimeFormsArtifact,
8};
9
10#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
11pub enum ProductionReachabilityReason {
12 ColdBoot,
13 ResumeCapture,
14 ResumeRestore,
15 ActivationRoot,
16 EventMapping,
17 ArtifactProgram,
18}
19
20#[derive(Clone, Debug, Eq, PartialEq)]
21pub struct ProductionExecutableRoot {
22 pub subject_id: String,
23 pub reason: ProductionReachabilityReason,
24}
25#[derive(Clone, Debug, Eq, PartialEq)]
26pub struct ProductionProgramEdge {
27 pub from: String,
28 pub to: String,
29 pub reason: ProductionReachabilityReason,
30}
31#[derive(Clone, Debug, Eq, PartialEq)]
32pub struct ProductionUnreachableRecord {
33 pub subject_id: String,
34 pub reason: String,
35}
36#[derive(Clone, Debug, Eq, PartialEq)]
37pub struct ProductionReachabilityBlock {
38 pub subject_id: String,
39 pub reason: String,
40}
41#[derive(Clone, Debug, Eq, PartialEq)]
42pub struct ProductionReachabilityGraph {
43 pub roots: Vec<ProductionExecutableRoot>,
44 pub edges: Vec<ProductionProgramEdge>,
45 pub reachable_programs: Vec<String>,
46 pub unreachable: Vec<ProductionUnreachableRecord>,
47 pub blocks: Vec<ProductionReachabilityBlock>,
48}
49
50#[must_use]
53#[allow(clippy::too_many_arguments)]
54#[allow(clippy::too_many_lines)]
55pub fn build_production_reachability_graph(
56 resume: &ResumeManifest,
57 component: &RuntimeComponentArtifact,
58 computed: &RuntimeComputedArtifact,
59 context: &RuntimeContextArtifact,
60 effect: &RuntimeEffectArtifact,
61 forms: &RuntimeFormsArtifact,
62) -> ProductionReachabilityGraph {
63 let mut roots = BTreeSet::new();
64 let mut edges = BTreeSet::new();
65 let mut programs = BTreeSet::new();
66 let mut blocks = BTreeSet::new();
67 let mut add = |from: String, to: String, reason: ProductionReachabilityReason| {
68 if to.is_empty() {
69 blocks.insert((from, "empty canonical program reference".to_string()));
70 } else {
71 programs.insert(to.clone());
72 edges.insert((from, to, reason));
73 }
74 };
75 roots.insert((
76 "cold-boot".to_string(),
77 ProductionReachabilityReason::ColdBoot,
78 ));
79 for chunk in &resume.chunks {
80 let root = chunk.chunk_id.to_string();
81 roots.insert((root.clone(), ProductionReachabilityReason::ActivationRoot));
82 for program in &chunk.provided_program_ids {
83 add(
84 root.clone(),
85 program.clone(),
86 ProductionReachabilityReason::ActivationRoot,
87 );
88 }
89 }
90 for program in &resume.capture_programs {
91 let root = program.program_id.to_string();
92 roots.insert((root.clone(), ProductionReachabilityReason::ResumeCapture));
93 add(
94 root.clone(),
95 root,
96 ProductionReachabilityReason::ResumeCapture,
97 );
98 }
99 for program in &resume.restore_programs {
100 let root = program.program_id.to_string();
101 roots.insert((root.clone(), ProductionReachabilityReason::ResumeRestore));
102 add(
103 root.clone(),
104 root,
105 ProductionReachabilityReason::ResumeRestore,
106 );
107 }
108 for event in &resume.events {
109 add(
110 event.resume_event_id.to_string(),
111 event.action_or_submit_program_id.clone(),
112 ProductionReachabilityReason::EventMapping,
113 );
114 }
115 for binding in &component.ordinary_template_bindings {
116 add(
117 "cold-boot".to_string(),
118 binding.program_id.clone(),
119 ProductionReachabilityReason::ColdBoot,
120 );
121 }
122 for event in &component.ordinary_template_events {
123 add(
124 "cold-boot".to_string(),
125 event.program_id.clone(),
126 ProductionReachabilityReason::ColdBoot,
127 );
128 }
129 for evaluation in &computed.evaluations {
130 add(
131 "cold-boot".to_string(),
132 evaluation.evaluation_function.clone(),
133 ProductionReachabilityReason::ArtifactProgram,
134 );
135 }
136 for source in &context.sources {
137 add(
138 "cold-boot".to_string(),
139 source.source_function.clone(),
140 ProductionReachabilityReason::ArtifactProgram,
141 );
142 }
143 for item in &effect.effects {
144 add(
145 "cold-boot".to_string(),
146 item.execution_function.clone(),
147 ProductionReachabilityReason::ArtifactProgram,
148 );
149 }
150 for host in &forms.hosts {
151 add(
152 host.event.clone(),
153 host.submit_action.clone(),
154 ProductionReachabilityReason::EventMapping,
155 );
156 }
157 for instance in &forms.instances {
158 for program in &instance.programs.initialize {
159 add(
160 "cold-boot".to_string(),
161 program.clone(),
162 ProductionReachabilityReason::ColdBoot,
163 );
164 }
165 for program in &instance.programs.reset {
166 add(
167 "cold-boot".to_string(),
168 program.clone(),
169 ProductionReachabilityReason::ArtifactProgram,
170 );
171 }
172 for field in &instance.programs.input {
173 for operation in &field.operations {
174 add(
175 field.field.clone(),
176 operation.clone(),
177 ProductionReachabilityReason::ArtifactProgram,
178 );
179 }
180 }
181 for field in &instance.programs.blur {
182 for operation in &field.operations {
183 add(
184 field.field.clone(),
185 operation.clone(),
186 ProductionReachabilityReason::ArtifactProgram,
187 );
188 }
189 }
190 }
191 ProductionReachabilityGraph {
192 roots: roots
193 .into_iter()
194 .map(|(subject_id, reason)| ProductionExecutableRoot { subject_id, reason })
195 .collect(),
196 edges: edges
197 .into_iter()
198 .map(|(from, to, reason)| ProductionProgramEdge { from, to, reason })
199 .collect(),
200 reachable_programs: programs.into_iter().collect(),
201 unreachable: Vec::new(),
202 blocks: blocks
203 .into_iter()
204 .map(|(subject_id, reason)| ProductionReachabilityBlock { subject_id, reason })
205 .collect(),
206 }
207}
208
209#[cfg(test)]
210mod tests {
211 use super::{build_production_reachability_graph, ProductionReachabilityReason};
212 use crate::resume_manifest::ResumeManifestChunkRootKind;
213 use crate::runtime_component_artifact::SerializedDestructionMetadata;
214 use crate::{
215 ResumeBoundaryId, ResumeBuildId, ResumeManifest, ResumeManifestChunkRecord,
216 RuntimeComponentArtifact, RuntimeComputedArtifact, RuntimeContextArtifact,
217 RuntimeEffectArtifact, RuntimeFormsArtifact,
218 };
219 use std::str::FromStr;
220
221 #[test]
222 fn k2_reachability_keeps_canonical_chunk_programs_without_elimination() {
223 let manifest = ResumeManifest {
224 schema_version: 6,
225 build_id: ResumeBuildId::zero_sentinel(),
226 snapshot_schema_version: 1,
227 runtime_protocol_version: 1,
228 application_root_boundary_id: ResumeBoundaryId::from_str("resume-boundary:root")
229 .expect("boundary"),
230 boundaries: Vec::new(),
231 slot_schemas: Vec::new(),
232 capture_programs: Vec::new(),
233 restore_programs: Vec::new(),
234 chunks: vec![ResumeManifestChunkRecord {
235 chunk_id: crate::ResumeChunkId::from_str("resume-chunk:Eager:application")
236 .expect("chunk"),
237 root_kind: ResumeManifestChunkRootKind::Eager,
238 root_id: "application".to_string(),
239 module_path: "boot.js".to_string(),
240 content_hash: "hash".to_string(),
241 required_boundary_ids: Vec::new(),
242 provided_program_ids: vec!["runtime-bootstrap".to_string()],
243 dependency_chunk_ids: Vec::new(),
244 }],
245 activations: Vec::new(),
246 anchors: Vec::new(),
247 events: Vec::new(),
248 phase_i_component_resume_records: Vec::new(),
249 phase_i_form_resume_records: Vec::new(),
250 };
251 let graph = build_production_reachability_graph(
252 &manifest,
253 &RuntimeComponentArtifact {
254 schema_version: 3,
255 component_definitions: Vec::new(),
256 instances: Vec::new(),
257 initialization_batches: Vec::new(),
258 slot_binding_programs: Vec::new(),
259 instance_context_bindings: Vec::new(),
260 ordinary_template_targets: Vec::new(),
261 ordinary_template_bindings: Vec::new(),
262 ordinary_template_events: Vec::new(),
263 destruction: SerializedDestructionMetadata {
264 operation: "destroy".to_string(),
265 enabled: true,
266 },
267 structural_programs: Vec::new(),
268 },
269 &RuntimeComputedArtifact {
270 schema_version: 3,
271 state: Vec::new(),
272 invalidations: Vec::new(),
273 resource_invalidations: Vec::new(),
274 evaluations: Vec::new(),
275 evaluation_order: Vec::new(),
276 update_batches: Vec::new(),
277 },
278 &RuntimeContextArtifact {
279 schema_version: 2,
280 sources: Vec::new(),
281 consumers: Vec::new(),
282 initial_batches: Vec::new(),
283 action_updates: Vec::new(),
284 },
285 &RuntimeEffectArtifact {
286 schema_version: 1,
287 effects: Vec::new(),
288 },
289 &RuntimeFormsArtifact {
290 schema_version: 1,
291 registry_version: 1,
292 forms: Vec::new(),
293 instances: Vec::new(),
294 hosts: Vec::new(),
295 },
296 );
297 assert!(graph
298 .reachable_programs
299 .contains(&"runtime-bootstrap".to_string()));
300 assert!(graph.unreachable.is_empty());
301 assert!(graph
302 .roots
303 .iter()
304 .any(|root| root.reason == ProductionReachabilityReason::ColdBoot));
305 }
306}