Skip to main content

presolve_compiler/
production_reachability.rs

1//! K2 descriptive reachability over immutable Phase A-J executable products.
2
3use 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/// Consumes only already-built artifacts. K2 describes closure; it never removes
51/// a record or reads source syntax.
52#[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}