Skip to main content

presolve_compiler/
production_cleanup.rs

1//! K12 reverse-order cleanup closure for compiler-owned component runtime state.
2
3use std::collections::BTreeSet;
4
5#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
6pub enum ProductionCleanupKind {
7    ActivationDispatch,
8    EventAndBindingIndex,
9    FormControlAndBindingIndex,
10    EffectSubscription,
11    ContextConsumerBinding,
12    SlotAndStructuralRegistry,
13    ComputedCache,
14    StateStorage,
15    FormInstanceStorage,
16    ContextProviderSlot,
17    ResumeBoundaryAndAnchor,
18    ComponentInstance,
19    DomAnchor,
20}
21
22#[derive(Clone, Debug, Eq, PartialEq)]
23pub struct ProductionOwnedRuntimeRecord {
24    pub owner_id: String,
25    pub initialization_ordinal: u32,
26    pub kind: ProductionCleanupKind,
27    pub record_id: String,
28}
29
30#[derive(Clone, Debug, Eq, PartialEq)]
31pub struct ProductionDestroyPlan {
32    pub detached_activation_ids: Vec<String>,
33    pub cleanup_records: Vec<ProductionOwnedRuntimeRecord>,
34}
35
36#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
37pub enum ProductionCleanupClosureViolation {
38    DuplicateRecord(String),
39    ForeignOwner(String),
40    MissingRecord(String),
41    OrderingMismatch,
42}
43
44/// Builds a reverse-initialization cleanup plan without authored callbacks.
45#[must_use]
46pub fn build_production_destroy_plan(
47    destroyed_owner_ids: &[String],
48    records: &[ProductionOwnedRuntimeRecord],
49    pending_activation_ids: &[String],
50) -> ProductionDestroyPlan {
51    let owners = destroyed_owner_ids.iter().collect::<BTreeSet<_>>();
52    let mut cleanup_records = records
53        .iter()
54        .filter(|record| owners.contains(&record.owner_id))
55        .cloned()
56        .collect::<Vec<_>>();
57    cleanup_records.sort_by(|left, right| {
58        (right.initialization_ordinal, right.kind, &right.record_id).cmp(&(
59            left.initialization_ordinal,
60            left.kind,
61            &left.record_id,
62        ))
63    });
64    let mut detached_activation_ids = pending_activation_ids.to_vec();
65    detached_activation_ids.sort();
66    detached_activation_ids.dedup();
67    ProductionDestroyPlan {
68        detached_activation_ids,
69        cleanup_records,
70    }
71}
72
73/// Validates complete exact-owner coverage and reverse initialization order.
74#[must_use]
75pub fn validate_production_cleanup_closure(
76    destroyed_owner_ids: &[String],
77    required_records: &[ProductionOwnedRuntimeRecord],
78    plan: &ProductionDestroyPlan,
79) -> Vec<ProductionCleanupClosureViolation> {
80    let owners = destroyed_owner_ids.iter().collect::<BTreeSet<_>>();
81    let required = required_records
82        .iter()
83        .filter(|record| owners.contains(&record.owner_id))
84        .map(|record| record.record_id.clone())
85        .collect::<BTreeSet<_>>();
86    let mut actual = BTreeSet::new();
87    let mut violations = Vec::new();
88    for record in &plan.cleanup_records {
89        if !owners.contains(&record.owner_id) {
90            violations.push(ProductionCleanupClosureViolation::ForeignOwner(
91                record.record_id.clone(),
92            ));
93        }
94        if !actual.insert(record.record_id.clone()) {
95            violations.push(ProductionCleanupClosureViolation::DuplicateRecord(
96                record.record_id.clone(),
97            ));
98        }
99    }
100    violations.extend(
101        required
102            .difference(&actual)
103            .cloned()
104            .map(ProductionCleanupClosureViolation::MissingRecord),
105    );
106    if plan.cleanup_records.windows(2).any(|pair| {
107        (
108            pair[0].initialization_ordinal,
109            pair[0].kind,
110            &pair[0].record_id,
111        ) < (
112            pair[1].initialization_ordinal,
113            pair[1].kind,
114            &pair[1].record_id,
115        )
116    }) {
117        violations.push(ProductionCleanupClosureViolation::OrderingMismatch);
118    }
119    violations.sort();
120    violations.dedup();
121    violations
122}
123
124#[cfg(test)]
125mod tests {
126    use super::*;
127    #[test]
128    fn k12_releases_only_destroyed_owners_in_reverse_initialization_order() {
129        let records = vec![
130            ProductionOwnedRuntimeRecord {
131                owner_id: "a".to_string(),
132                initialization_ordinal: 1,
133                kind: ProductionCleanupKind::StateStorage,
134                record_id: "state".to_string(),
135            },
136            ProductionOwnedRuntimeRecord {
137                owner_id: "a".to_string(),
138                initialization_ordinal: 2,
139                kind: ProductionCleanupKind::DomAnchor,
140                record_id: "anchor".to_string(),
141            },
142            ProductionOwnedRuntimeRecord {
143                owner_id: "b".to_string(),
144                initialization_ordinal: 3,
145                kind: ProductionCleanupKind::ComponentInstance,
146                record_id: "other".to_string(),
147            },
148        ];
149        let plan = build_production_destroy_plan(
150            &["a".to_string()],
151            &records,
152            &["activation:a".to_string(), "activation:a".to_string()],
153        );
154        assert_eq!(
155            plan.cleanup_records
156                .iter()
157                .map(|record| record.record_id.as_str())
158                .collect::<Vec<_>>(),
159            vec!["anchor", "state"]
160        );
161        assert_eq!(plan.detached_activation_ids, vec!["activation:a"]);
162    }
163
164    #[test]
165    fn k13_covers_forms_context_effect_and_resume_without_authored_cleanup() {
166        let records = [
167            (
168                ProductionCleanupKind::FormControlAndBindingIndex,
169                "form-control",
170            ),
171            (ProductionCleanupKind::EffectSubscription, "effect"),
172            (ProductionCleanupKind::ContextConsumerBinding, "consumer"),
173            (ProductionCleanupKind::FormInstanceStorage, "form-state"),
174            (ProductionCleanupKind::ContextProviderSlot, "provider"),
175            (ProductionCleanupKind::ResumeBoundaryAndAnchor, "resume"),
176        ]
177        .into_iter()
178        .enumerate()
179        .map(
180            |(ordinal, (kind, record_id))| ProductionOwnedRuntimeRecord {
181                owner_id: "destroyed".to_string(),
182                initialization_ordinal: u32::try_from(ordinal).expect("fixture ordinal"),
183                kind,
184                record_id: record_id.to_string(),
185            },
186        )
187        .collect::<Vec<_>>();
188        let plan = build_production_destroy_plan(
189            &["destroyed".to_string()],
190            &records,
191            &["activation:destroyed".to_string()],
192        );
193        assert!(
194            validate_production_cleanup_closure(&["destroyed".to_string()], &records, &plan)
195                .is_empty()
196        );
197        assert_eq!(
198            plan.cleanup_records.first().expect("record").record_id,
199            "resume"
200        );
201    }
202
203    #[test]
204    fn k16_one_hundred_cleanup_cycles_return_every_owned_registry_to_baseline() {
205        let kinds = [
206            ProductionCleanupKind::ActivationDispatch,
207            ProductionCleanupKind::EventAndBindingIndex,
208            ProductionCleanupKind::FormControlAndBindingIndex,
209            ProductionCleanupKind::EffectSubscription,
210            ProductionCleanupKind::ContextConsumerBinding,
211            ProductionCleanupKind::SlotAndStructuralRegistry,
212            ProductionCleanupKind::ComputedCache,
213            ProductionCleanupKind::StateStorage,
214            ProductionCleanupKind::FormInstanceStorage,
215            ProductionCleanupKind::ContextProviderSlot,
216            ProductionCleanupKind::ResumeBoundaryAndAnchor,
217            ProductionCleanupKind::ComponentInstance,
218            ProductionCleanupKind::DomAnchor,
219        ];
220        let mut instance_registry = BTreeSet::new();
221        let global_program_cache = BTreeSet::from(["runtime-bootstrap", "runtime-registries"]);
222        let baseline_instance_count = instance_registry.len();
223        let baseline_global_count = global_program_cache.len();
224
225        for cycle in 0_u32..100 {
226            let owner = format!("component:{cycle}");
227            let records = kinds
228                .iter()
229                .enumerate()
230                .map(|(ordinal, kind)| ProductionOwnedRuntimeRecord {
231                    owner_id: owner.clone(),
232                    initialization_ordinal: u32::try_from(ordinal).expect("fixture ordinal"),
233                    kind: *kind,
234                    record_id: format!("{owner}:{kind:?}"),
235                })
236                .collect::<Vec<_>>();
237            instance_registry.extend(records.iter().map(|record| record.record_id.clone()));
238            let plan = build_production_destroy_plan(
239                std::slice::from_ref(&owner),
240                &records,
241                &[format!("activation:{owner}")],
242            );
243            assert!(validate_production_cleanup_closure(
244                std::slice::from_ref(&owner),
245                &records,
246                &plan
247            )
248            .is_empty());
249            for record in plan.cleanup_records {
250                assert!(instance_registry.remove(&record.record_id));
251            }
252            assert_eq!(instance_registry.len(), baseline_instance_count);
253            assert_eq!(global_program_cache.len(), baseline_global_count);
254        }
255    }
256}