Skip to main content

omena_parser/closed_world/
authority.rs

1//! Closed-world linking authority: builds sealed bundles from linked-module facts.
2
3use std::collections::{BTreeMap, BTreeSet, VecDeque};
4
5use super::contract::{
6    ClosedWorldBundleBuildErrorV0, ClosedWorldBundleEvidenceV0, ClosedWorldBundleV0,
7    ClosedWorldComposesEdgeV0, ClosedWorldComposesScanStateV0,
8    ClosedWorldInterfaceHashAvailabilityV0, ClosedWorldInterfaceHashEntryV0,
9    ClosedWorldInterfaceHashSetV0, ClosedWorldLinkedModuleV0, ClosedWorldModuleMetadataV0,
10    ClosedWorldModuleReachabilityEvidenceV0, ClosedWorldReachabilityBitsetParityReportV0,
11    ClosedWorldSourcePrecisionSummaryV0, ModuleInstanceKeyV0, ModuleQualifiedSymbolSetV0,
12    ReachabilityIndexV0,
13};
14
15impl ClosedWorldBundleV0 {
16    pub fn try_from_linked_modules(
17        entrypoints: Vec<ModuleInstanceKeyV0>,
18        linked_modules: Vec<ClosedWorldLinkedModuleV0>,
19    ) -> Result<Self, ClosedWorldBundleBuildErrorV0> {
20        Self::try_from_linked_modules_with_metadata(entrypoints, linked_modules, Vec::new())
21    }
22
23    pub fn try_from_linked_modules_with_metadata(
24        entrypoints: Vec<ModuleInstanceKeyV0>,
25        linked_modules: Vec<ClosedWorldLinkedModuleV0>,
26        module_metadata: Vec<ClosedWorldModuleMetadataV0>,
27    ) -> Result<Self, ClosedWorldBundleBuildErrorV0> {
28        if entrypoints.is_empty() {
29            return Err(ClosedWorldBundleBuildErrorV0::EmptyEntrypoints);
30        }
31
32        let mut by_instance = BTreeMap::new();
33        for module in linked_modules {
34            by_instance.insert(module.instance.clone(), module);
35        }
36        let metadata_by_instance = module_metadata
37            .into_iter()
38            .map(|metadata| (metadata.module_instance().clone(), metadata))
39            .collect::<BTreeMap<_, _>>();
40
41        let reachability = compute_reachability(entrypoints.as_slice(), &by_instance)?;
42        let linked_modules = reachability.module_instances().to_vec();
43        let interface_hashes =
44            interface_hashes_for_reachable_modules(&linked_modules, &metadata_by_instance);
45        let source_precision =
46            source_precision_for_reachable_modules(&linked_modules, &metadata_by_instance);
47        let module_reachability_evidence =
48            module_reachability_evidence_for_modules(&by_instance, &metadata_by_instance);
49        let composes_edges = composes_edges_for_workspace_modules(&by_instance);
50        let composes_scan_state =
51            composes_scan_state_for_workspace_modules(&by_instance, &metadata_by_instance);
52        let composes_edge_observation_count = by_instance
53            .values()
54            .map(|module| module.composes_edge_observation_count)
55            .sum();
56        let composes_origin_class_names = by_instance
57            .iter()
58            .map(|(instance, module)| {
59                (
60                    instance.clone(),
61                    dedupe_symbol_names(module.class_names.as_slice()),
62                )
63            })
64            .collect();
65        let closure_hash = stable_closure_hash(entrypoints.as_slice(), &by_instance, &reachability);
66        #[cfg(feature = "test-support")]
67        crate::record_closed_world_bundle_construction_for_test();
68
69        Ok(Self::seal(
70            entrypoints,
71            linked_modules,
72            reachability,
73            closure_hash,
74            ClosedWorldBundleEvidenceV0 {
75                interface_hashes,
76                source_precision,
77                composes_edges,
78                composes_scan_state,
79                composes_edge_observation_count,
80                composes_origin_class_names,
81                module_reachability_evidence,
82            },
83        ))
84    }
85}
86
87fn composes_scan_state_for_workspace_modules(
88    by_instance: &BTreeMap<ModuleInstanceKeyV0, ClosedWorldLinkedModuleV0>,
89    metadata_by_instance: &BTreeMap<ModuleInstanceKeyV0, ClosedWorldModuleMetadataV0>,
90) -> ClosedWorldComposesScanStateV0 {
91    if by_instance.keys().all(|instance| {
92        metadata_by_instance.get(instance).is_some_and(|metadata| {
93            metadata.composes_scan_state() == ClosedWorldComposesScanStateV0::ScannedClosed
94        })
95    }) {
96        ClosedWorldComposesScanStateV0::ScannedClosed
97    } else {
98        ClosedWorldComposesScanStateV0::SourceSetOpen
99    }
100}
101
102fn composes_edges_for_workspace_modules(
103    by_instance: &BTreeMap<ModuleInstanceKeyV0, ClosedWorldLinkedModuleV0>,
104) -> Vec<ClosedWorldComposesEdgeV0> {
105    let mut edges = by_instance
106        .values()
107        .flat_map(|module| module.composes_edges.iter().cloned())
108        .collect::<Vec<_>>();
109    edges.sort_by(|left, right| {
110        (
111            &left.from_module,
112            &left.from_symbol,
113            &left.to_module,
114            &left.to_symbol,
115        )
116            .cmp(&(
117                &right.from_module,
118                &right.from_symbol,
119                &right.to_module,
120                &right.to_symbol,
121            ))
122    });
123    edges.dedup();
124    edges
125}
126
127fn module_reachability_evidence_for_modules(
128    by_instance: &BTreeMap<ModuleInstanceKeyV0, ClosedWorldLinkedModuleV0>,
129    metadata_by_instance: &BTreeMap<ModuleInstanceKeyV0, ClosedWorldModuleMetadataV0>,
130) -> BTreeMap<ModuleInstanceKeyV0, ClosedWorldModuleReachabilityEvidenceV0> {
131    by_instance
132        .keys()
133        .cloned()
134        .map(|instance| {
135            let evidence = metadata_by_instance
136                .get(&instance)
137                .map(ClosedWorldModuleMetadataV0::reachability_evidence)
138                .unwrap_or_default();
139            (instance, evidence)
140        })
141        .collect()
142}
143
144fn interface_hashes_for_reachable_modules(
145    reachable: &[ModuleInstanceKeyV0],
146    metadata_by_instance: &BTreeMap<ModuleInstanceKeyV0, ClosedWorldModuleMetadataV0>,
147) -> ClosedWorldInterfaceHashSetV0 {
148    ClosedWorldInterfaceHashSetV0::new(
149        reachable
150            .iter()
151            .map(|instance| ClosedWorldInterfaceHashEntryV0 {
152                module_instance: instance.clone(),
153                availability: metadata_by_instance
154                    .get(instance)
155                    .and_then(|metadata| metadata.interface_hash())
156                    .map_or(
157                        ClosedWorldInterfaceHashAvailabilityV0::Absent,
158                        |interface_hash| ClosedWorldInterfaceHashAvailabilityV0::Known {
159                            interface_hash: interface_hash.to_string(),
160                        },
161                    ),
162            })
163            .collect(),
164    )
165}
166
167fn source_precision_for_reachable_modules(
168    reachable: &[ModuleInstanceKeyV0],
169    metadata_by_instance: &BTreeMap<ModuleInstanceKeyV0, ClosedWorldModuleMetadataV0>,
170) -> Option<ClosedWorldSourcePrecisionSummaryV0> {
171    let mut aggregate = ClosedWorldSourcePrecisionSummaryV0::default();
172    let mut observed = false;
173    for instance in reachable {
174        if let Some(source_precision) = metadata_by_instance
175            .get(instance)
176            .and_then(ClosedWorldModuleMetadataV0::source_precision)
177        {
178            aggregate.merge(source_precision);
179            observed = true;
180        }
181    }
182    observed.then_some(aggregate)
183}
184
185pub fn summarize_closed_world_reachability_bitset_parity_v0(
186    entrypoints: Vec<ModuleInstanceKeyV0>,
187    linked_modules: Vec<ClosedWorldLinkedModuleV0>,
188) -> Result<ClosedWorldReachabilityBitsetParityReportV0, ClosedWorldBundleBuildErrorV0> {
189    if entrypoints.is_empty() {
190        return Err(ClosedWorldBundleBuildErrorV0::EmptyEntrypoints);
191    }
192
193    let mut by_instance = BTreeMap::new();
194    for module in linked_modules {
195        by_instance.insert(module.instance.clone(), module);
196    }
197
198    let btreeset_reachability = compute_reachability(entrypoints.as_slice(), &by_instance)?;
199    let bitset_reachability = compute_reachability_bitset(entrypoints.as_slice(), &by_instance)?;
200    let btreeset_closure_hash =
201        stable_closure_hash(entrypoints.as_slice(), &by_instance, &btreeset_reachability);
202    let bitset_closure_hash =
203        stable_closure_hash(entrypoints.as_slice(), &by_instance, &bitset_reachability);
204    let symbol_name_count = bitset_reachability
205        .class_names()
206        .len()
207        .saturating_add(bitset_reachability.keyframe_names().len())
208        .saturating_add(bitset_reachability.value_names().len())
209        .saturating_add(bitset_reachability.custom_property_names().len());
210
211    Ok(ClosedWorldReachabilityBitsetParityReportV0 {
212        schema_version: "0",
213        product: "omena-parser.closed-world-reachability-bitset-parity",
214        module_instance_count: bitset_reachability.module_instances().len(),
215        symbol_name_count,
216        module_qualified_symbols: btreeset_reachability.module_qualified_symbols().to_vec(),
217        reachability_equal: btreeset_reachability == bitset_reachability,
218        closure_hash_equal: btreeset_closure_hash == bitset_closure_hash,
219        btreeset_closure_hash,
220        bitset_closure_hash,
221    })
222}
223
224fn compute_reachability(
225    entrypoints: &[ModuleInstanceKeyV0],
226    by_instance: &BTreeMap<ModuleInstanceKeyV0, ClosedWorldLinkedModuleV0>,
227) -> Result<ReachabilityIndexV0, ClosedWorldBundleBuildErrorV0> {
228    let mut queue = VecDeque::new();
229    let mut seen = BTreeSet::new();
230    for entrypoint in entrypoints {
231        if !by_instance.contains_key(entrypoint) {
232            return Err(ClosedWorldBundleBuildErrorV0::MissingEntrypoint {
233                module: entrypoint.clone(),
234            });
235        }
236        queue.push_back(entrypoint.clone());
237    }
238
239    while let Some(instance) = queue.pop_front() {
240        if !seen.insert(instance.clone()) {
241            continue;
242        }
243        let Some(module) = by_instance.get(&instance) else {
244            return Err(ClosedWorldBundleBuildErrorV0::MissingEntrypoint { module: instance });
245        };
246        for dependency in &module.dependencies {
247            if !by_instance.contains_key(dependency) {
248                return Err(ClosedWorldBundleBuildErrorV0::MissingDependency {
249                    module: instance.clone(),
250                    dependency: dependency.clone(),
251                });
252            }
253            queue.push_back(dependency.clone());
254        }
255    }
256
257    reachability_index_from_seen(&seen, by_instance)
258}
259
260fn compute_reachability_bitset(
261    entrypoints: &[ModuleInstanceKeyV0],
262    by_instance: &BTreeMap<ModuleInstanceKeyV0, ClosedWorldLinkedModuleV0>,
263) -> Result<ReachabilityIndexV0, ClosedWorldBundleBuildErrorV0> {
264    for entrypoint in entrypoints {
265        if !by_instance.contains_key(entrypoint) {
266            return Err(ClosedWorldBundleBuildErrorV0::MissingEntrypoint {
267                module: entrypoint.clone(),
268            });
269        }
270    }
271    let dense_index = DenseModuleInstanceIndexV0::from_modules(entrypoints, by_instance);
272    let mut seen = DenseModuleInstanceBitsetV0::new(dense_index.len());
273    let mut queue = entrypoints
274        .iter()
275        .filter_map(|entrypoint| dense_index.index_of(entrypoint))
276        .collect::<VecDeque<_>>();
277
278    while let Some(instance_index) = queue.pop_front() {
279        if !seen.insert(instance_index) {
280            continue;
281        }
282        let instance = dense_index.instance(instance_index);
283        let Some(module) = by_instance.get(instance) else {
284            return Err(ClosedWorldBundleBuildErrorV0::MissingEntrypoint {
285                module: instance.clone(),
286            });
287        };
288        for dependency in &module.dependencies {
289            if !by_instance.contains_key(dependency) {
290                return Err(ClosedWorldBundleBuildErrorV0::MissingDependency {
291                    module: instance.clone(),
292                    dependency: dependency.clone(),
293                });
294            }
295            if let Some(dependency_index) = dense_index.index_of(dependency) {
296                queue.push_back(dependency_index);
297            }
298        }
299    }
300
301    let seen_instances = dense_index.instances_for_bitset(&seen);
302    reachability_index_from_seen(&seen_instances, by_instance)
303}
304
305fn reachability_index_from_seen(
306    seen: &BTreeSet<ModuleInstanceKeyV0>,
307    by_instance: &BTreeMap<ModuleInstanceKeyV0, ClosedWorldLinkedModuleV0>,
308) -> Result<ReachabilityIndexV0, ClosedWorldBundleBuildErrorV0> {
309    let mut class_names = BTreeSet::new();
310    let mut keyframe_names = BTreeSet::new();
311    let mut value_names = BTreeSet::new();
312    let mut custom_property_names = BTreeSet::new();
313    let mut module_qualified_symbols = Vec::with_capacity(by_instance.len());
314
315    for (instance, module) in by_instance {
316        let reachable = seen.contains(instance);
317        if reachable {
318            class_names.extend(module.class_names.iter().cloned());
319            keyframe_names.extend(module.keyframe_names.iter().cloned());
320            value_names.extend(module.value_names.iter().cloned());
321            custom_property_names.extend(module.custom_property_names.iter().cloned());
322        }
323        let qualified_class_names = if reachable {
324            dedupe_symbol_names(&module.class_names)
325        } else {
326            Vec::new()
327        };
328        let qualified_keyframe_names = if reachable {
329            dedupe_symbol_names(&module.keyframe_names)
330        } else {
331            Vec::new()
332        };
333        let qualified_value_names = if reachable {
334            dedupe_symbol_names(&module.value_names)
335        } else {
336            Vec::new()
337        };
338        let qualified_custom_property_names = if reachable {
339            dedupe_symbol_names(&module.custom_property_names)
340        } else {
341            Vec::new()
342        };
343        module_qualified_symbols.push(ModuleQualifiedSymbolSetV0::new(
344            instance.clone(),
345            reachable,
346            qualified_class_names,
347            qualified_keyframe_names,
348            qualified_value_names,
349            qualified_custom_property_names,
350        ));
351    }
352
353    Ok(ReachabilityIndexV0::from_parts(
354        seen.iter().cloned().collect(),
355        module_qualified_symbols,
356        class_names.into_iter().collect(),
357        keyframe_names.into_iter().collect(),
358        value_names.into_iter().collect(),
359        custom_property_names.into_iter().collect(),
360    ))
361}
362
363fn dedupe_symbol_names(names: &[String]) -> Vec<String> {
364    names
365        .iter()
366        .cloned()
367        .collect::<BTreeSet<_>>()
368        .into_iter()
369        .collect()
370}
371
372#[derive(Debug, Clone)]
373struct DenseModuleInstanceIndexV0 {
374    instances: Vec<ModuleInstanceKeyV0>,
375    positions: BTreeMap<ModuleInstanceKeyV0, usize>,
376}
377
378impl DenseModuleInstanceIndexV0 {
379    fn from_modules(
380        entrypoints: &[ModuleInstanceKeyV0],
381        by_instance: &BTreeMap<ModuleInstanceKeyV0, ClosedWorldLinkedModuleV0>,
382    ) -> Self {
383        let mut instances = BTreeSet::new();
384        instances.extend(entrypoints.iter().cloned());
385        for (instance, module) in by_instance {
386            instances.insert(instance.clone());
387            instances.extend(module.dependencies.iter().cloned());
388        }
389        let instances = instances.into_iter().collect::<Vec<_>>();
390        let positions = instances
391            .iter()
392            .enumerate()
393            .map(|(index, instance)| (instance.clone(), index))
394            .collect::<BTreeMap<_, _>>();
395        Self {
396            instances,
397            positions,
398        }
399    }
400
401    fn len(&self) -> usize {
402        self.instances.len()
403    }
404
405    fn index_of(&self, instance: &ModuleInstanceKeyV0) -> Option<usize> {
406        self.positions.get(instance).copied()
407    }
408
409    fn instance(&self, index: usize) -> &ModuleInstanceKeyV0 {
410        &self.instances[index]
411    }
412
413    fn instances_for_bitset(
414        &self,
415        bitset: &DenseModuleInstanceBitsetV0,
416    ) -> BTreeSet<ModuleInstanceKeyV0> {
417        self.instances
418            .iter()
419            .enumerate()
420            .filter(|(index, _)| bitset.contains(*index))
421            .map(|(_, instance)| instance.clone())
422            .collect()
423    }
424}
425
426#[derive(Debug, Clone)]
427struct DenseModuleInstanceBitsetV0 {
428    words: Vec<u64>,
429}
430
431impl DenseModuleInstanceBitsetV0 {
432    fn new(len: usize) -> Self {
433        Self {
434            words: vec![0; len.div_ceil(64)],
435        }
436    }
437
438    fn insert(&mut self, index: usize) -> bool {
439        let word_index = index / 64;
440        let mask = 1u64 << (index % 64);
441        let word = &mut self.words[word_index];
442        let was_empty = *word & mask == 0;
443        *word |= mask;
444        was_empty
445    }
446
447    fn contains(&self, index: usize) -> bool {
448        self.words
449            .get(index / 64)
450            .is_some_and(|word| word & (1u64 << (index % 64)) != 0)
451    }
452}
453
454fn stable_closure_hash(
455    entrypoints: &[ModuleInstanceKeyV0],
456    by_instance: &BTreeMap<ModuleInstanceKeyV0, ClosedWorldLinkedModuleV0>,
457    reachability: &ReachabilityIndexV0,
458) -> String {
459    let mut hash = StableFnv64::new();
460    hash.piece("omena-parser.closed-world-bundle");
461    for entrypoint in entrypoints {
462        hash.instance(entrypoint);
463    }
464    for instance in reachability.module_instances() {
465        hash.instance(instance);
466        if let Some(module) = by_instance.get(instance) {
467            for dependency in &module.dependencies {
468                hash.instance(dependency);
469            }
470        }
471    }
472    for edge in composes_edges_for_workspace_modules(by_instance) {
473        hash.piece("composes");
474        hash.instance(&edge.from_module);
475        hash.piece(&edge.from_symbol);
476        hash.instance(&edge.to_module);
477        hash.piece(&edge.to_symbol);
478    }
479    for name in reachability.class_names() {
480        hash.piece("class");
481        hash.piece(name);
482    }
483    for name in reachability.keyframe_names() {
484        hash.piece("keyframe");
485        hash.piece(name);
486    }
487    for name in reachability.value_names() {
488        hash.piece("value");
489        hash.piece(name);
490    }
491    for name in reachability.custom_property_names() {
492        hash.piece("custom-property");
493        hash.piece(name);
494    }
495    hash.finish_hex()
496}
497
498struct StableFnv64(u64);
499
500impl StableFnv64 {
501    fn new() -> Self {
502        Self(0xcbf2_9ce4_8422_2325)
503    }
504
505    fn piece(&mut self, value: &str) {
506        for byte in value.as_bytes().iter().copied().chain([0]) {
507            self.0 ^= u64::from(byte);
508            self.0 = self.0.wrapping_mul(0x0000_0100_0000_01b3);
509        }
510    }
511
512    fn instance(&mut self, instance: &ModuleInstanceKeyV0) {
513        self.piece(instance.module().as_str());
514        self.piece(instance.configuration().as_str());
515    }
516
517    fn finish_hex(self) -> String {
518        format!("{:016x}", self.0)
519    }
520}