Skip to main content

presolve_compiler/
production_chunk_graph.rs

1//! K7 deterministic production chunk topology from accepted K6 candidates.
2
3use std::collections::{BTreeMap, BTreeSet};
4
5use crate::{
6    ExecutableProgramFingerprint, ProductionChunkId, SharedChunkCandidate, SharedChunkCandidateId,
7    SharedChunkCandidatePlan,
8};
9
10#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
11pub enum ProductionChunkKind {
12    Eager,
13    Root,
14    Shared,
15}
16
17#[derive(Clone, Debug, Eq, PartialEq)]
18pub struct ProductionRootChunkInput {
19    pub activation_root_id: String,
20    pub root_kind: String,
21    pub programs: Vec<ExecutableProgramFingerprint>,
22}
23
24#[derive(Clone, Debug, Eq, PartialEq)]
25pub struct ProductionChunkRecord {
26    pub id: ProductionChunkId,
27    pub kind: ProductionChunkKind,
28    pub activation_roots: Vec<String>,
29    pub root_kind: Option<String>,
30    pub programs: Vec<ExecutableProgramFingerprint>,
31    pub registration_only: bool,
32    pub provisional_module_filename: String,
33}
34
35#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
36pub struct ProductionChunkDependency {
37    pub dependent_chunk_id: ProductionChunkId,
38    pub dependency_chunk_id: ProductionChunkId,
39}
40
41#[derive(Clone, Copy, Debug, Eq, PartialEq)]
42pub enum ProductionSharedChunkFailurePolicy {
43    FailDependentActivationWithoutRetry,
44}
45
46#[derive(Clone, Debug, Eq, PartialEq)]
47pub struct ProductionActivationChunkPlan {
48    pub activation_root_id: String,
49    pub root_chunk_id: ProductionChunkId,
50    pub shared_chunk_ids: Vec<ProductionChunkId>,
51    pub shared_chunk_failure_policy: ProductionSharedChunkFailurePolicy,
52}
53
54#[derive(Clone, Debug, Eq, PartialEq)]
55pub struct ProductionChunkGraph {
56    pub eager_chunk_id: ProductionChunkId,
57    pub chunks: Vec<ProductionChunkRecord>,
58    pub dependencies: Vec<ProductionChunkDependency>,
59    pub activation_plans: Vec<ProductionActivationChunkPlan>,
60}
61
62#[derive(Clone, Debug, Eq, PartialEq)]
63pub struct ProductionChunkExtractionReport {
64    pub extracted_candidate_ids: Vec<SharedChunkCandidateId>,
65    pub extracted_program_count: usize,
66    pub root_chunk_count: usize,
67    pub shared_chunk_count: usize,
68}
69
70#[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)]
71pub enum ProductionChunkGraphValidationError {
72    DuplicateActivationRoot(String),
73    DuplicateChunkId(ProductionChunkId),
74    DuplicateDependency(ProductionChunkDependency),
75    EagerChunkHasDependency(ProductionChunkId),
76    ExpectedExactlyOneEagerChunk,
77    InvalidActivationPlanRoot(ProductionChunkId),
78    InvalidChunkDependency(ProductionChunkDependency),
79    InvalidSharedChunkCandidate(SharedChunkCandidateId),
80    InvalidSharedChunkOrder(String),
81    MissingDependencyChunk(ProductionChunkId),
82    ProductionChunkCycle,
83    SharedChunkIsNotRegistrationOnly(ProductionChunkId),
84    UnknownActivationPlanChunk(ProductionChunkId),
85}
86
87#[derive(Clone, Debug, Eq, PartialEq)]
88pub struct ProductionChunkExtractionError {
89    pub validation_errors: Vec<ProductionChunkGraphValidationError>,
90}
91
92/// Creates the exact one-eager, root-to-shared depth-one topology allowed by K7.
93///
94/// # Errors
95///
96/// Returns all deterministic validation errors when candidates or roots cannot
97/// form that topology without changing their exact identities.
98#[allow(clippy::too_many_lines)]
99pub fn extract_production_chunk_graph(
100    candidate_plan: &SharedChunkCandidatePlan,
101    roots: &[ProductionRootChunkInput],
102) -> Result<(ProductionChunkGraph, ProductionChunkExtractionReport), ProductionChunkExtractionError>
103{
104    let mut root_inputs = roots.to_vec();
105    root_inputs.sort_by(|left, right| left.activation_root_id.cmp(&right.activation_root_id));
106    let mut extraction_errors = Vec::new();
107    if root_inputs
108        .windows(2)
109        .any(|pair| pair[0].activation_root_id == pair[1].activation_root_id)
110    {
111        extraction_errors.extend(
112            root_inputs
113                .windows(2)
114                .filter(|pair| pair[0].activation_root_id == pair[1].activation_root_id)
115                .map(|pair| {
116                    ProductionChunkGraphValidationError::DuplicateActivationRoot(
117                        pair[0].activation_root_id.clone(),
118                    )
119                }),
120        );
121    }
122
123    let root_programs = root_inputs
124        .iter()
125        .map(|root| {
126            (
127                root.activation_root_id.clone(),
128                root.programs.iter().cloned().collect::<BTreeSet<_>>(),
129            )
130        })
131        .collect::<BTreeMap<_, _>>();
132    let root_ids = root_programs.keys().cloned().collect::<BTreeSet<_>>();
133    let mut candidates = candidate_plan.candidates.clone();
134    candidates.sort_by(|left, right| left.id.cmp(&right.id));
135    let mut extracted_by_root = BTreeMap::<String, BTreeSet<ExecutableProgramFingerprint>>::new();
136    for candidate in &candidates {
137        validate_candidate_consumers(
138            candidate,
139            &root_ids,
140            &root_programs,
141            &mut extracted_by_root,
142            &mut extraction_errors,
143        );
144    }
145    if !extraction_errors.is_empty() {
146        extraction_errors.sort();
147        extraction_errors.dedup();
148        return Err(ProductionChunkExtractionError {
149            validation_errors: extraction_errors,
150        });
151    }
152
153    let eager_chunk_id = ProductionChunkId::eager_runtime_v1();
154    let mut chunks = vec![ProductionChunkRecord {
155        id: eager_chunk_id.clone(),
156        kind: ProductionChunkKind::Eager,
157        activation_roots: vec!["eager-runtime".to_string()],
158        root_kind: None,
159        programs: Vec::new(),
160        registration_only: true,
161        provisional_module_filename: "boot.pending-content-hash.js".to_string(),
162    }];
163    let mut dependencies = Vec::new();
164    let mut shared_chunk_ids_by_root = BTreeMap::<String, Vec<ProductionChunkId>>::new();
165    for candidate in &candidates {
166        let Some(shared_chunk_id) = ProductionChunkId::for_activation_roots_and_programs(
167            "shared",
168            &candidate
169                .consumers
170                .iter()
171                .map(|consumer| consumer.root_id.clone())
172                .collect::<Vec<_>>(),
173            &candidate.programs,
174        ) else {
175            return Err(ProductionChunkExtractionError {
176                validation_errors: vec![
177                    ProductionChunkGraphValidationError::InvalidSharedChunkCandidate(
178                        candidate.id.clone(),
179                    ),
180                ],
181            });
182        };
183        for consumer in &candidate.consumers {
184            shared_chunk_ids_by_root
185                .entry(consumer.root_id.clone())
186                .or_default()
187                .push(shared_chunk_id.clone());
188        }
189        chunks.push(ProductionChunkRecord {
190            id: shared_chunk_id.clone(),
191            kind: ProductionChunkKind::Shared,
192            activation_roots: candidate
193                .consumers
194                .iter()
195                .map(|consumer| consumer.root_id.clone())
196                .collect(),
197            root_kind: None,
198            programs: candidate.programs.clone(),
199            registration_only: true,
200            provisional_module_filename: provisional_filename("shared", &shared_chunk_id),
201        });
202        dependencies.push(ProductionChunkDependency {
203            dependent_chunk_id: shared_chunk_id,
204            dependency_chunk_id: eager_chunk_id.clone(),
205        });
206    }
207
208    let mut activation_plans = Vec::new();
209    for root in root_inputs {
210        let extracted = extracted_by_root
211            .get(&root.activation_root_id)
212            .cloned()
213            .unwrap_or_default();
214        let mut programs = root
215            .programs
216            .into_iter()
217            .filter(|program| !extracted.contains(program))
218            .collect::<Vec<_>>();
219        programs.sort();
220        programs.dedup();
221        let root_chunk_id = ProductionChunkId::for_activation_roots_and_programs(
222            &root.root_kind,
223            std::slice::from_ref(&root.activation_root_id),
224            &programs,
225        )
226        .ok_or_else(|| ProductionChunkExtractionError {
227            validation_errors: vec![
228                ProductionChunkGraphValidationError::InvalidActivationPlanRoot(
229                    ProductionChunkId::eager_runtime_v1(),
230                ),
231            ],
232        })?;
233        let mut shared_chunk_ids = shared_chunk_ids_by_root
234            .remove(&root.activation_root_id)
235            .unwrap_or_default();
236        shared_chunk_ids.sort();
237        shared_chunk_ids.dedup();
238        chunks.push(ProductionChunkRecord {
239            id: root_chunk_id.clone(),
240            kind: ProductionChunkKind::Root,
241            activation_roots: vec![root.activation_root_id.clone()],
242            root_kind: Some(root.root_kind.clone()),
243            programs,
244            registration_only: false,
245            provisional_module_filename: provisional_filename(
246                &format!("root.{}", root.root_kind),
247                &root_chunk_id,
248            ),
249        });
250        dependencies.push(ProductionChunkDependency {
251            dependent_chunk_id: root_chunk_id.clone(),
252            dependency_chunk_id: eager_chunk_id.clone(),
253        });
254        dependencies.extend(shared_chunk_ids.iter().cloned().map(|dependency_chunk_id| {
255            ProductionChunkDependency {
256                dependent_chunk_id: root_chunk_id.clone(),
257                dependency_chunk_id,
258            }
259        }));
260        activation_plans.push(ProductionActivationChunkPlan {
261            activation_root_id: root.activation_root_id,
262            root_chunk_id,
263            shared_chunk_ids,
264            shared_chunk_failure_policy:
265                ProductionSharedChunkFailurePolicy::FailDependentActivationWithoutRetry,
266        });
267    }
268    chunks.sort_by(|left, right| left.id.cmp(&right.id));
269    dependencies.sort();
270    dependencies.dedup();
271    activation_plans.sort_by(|left, right| left.activation_root_id.cmp(&right.activation_root_id));
272    let graph = ProductionChunkGraph {
273        eager_chunk_id,
274        chunks,
275        dependencies,
276        activation_plans,
277    };
278    validate_production_chunk_graph(&graph)
279        .map_err(|validation_errors| ProductionChunkExtractionError { validation_errors })?;
280    let report = ProductionChunkExtractionReport {
281        extracted_candidate_ids: candidates
282            .iter()
283            .map(|candidate| candidate.id.clone())
284            .collect(),
285        extracted_program_count: candidates
286            .iter()
287            .map(|candidate| candidate.programs.len())
288            .sum(),
289        root_chunk_count: roots.len(),
290        shared_chunk_count: candidates.len(),
291    };
292    Ok((graph, report))
293}
294
295/// Validates ordering, identity, dependency depth, and activation safety.
296///
297/// # Errors
298///
299/// Returns stable validation evidence when the graph is not Section 12 topology.
300pub fn validate_production_chunk_graph(
301    graph: &ProductionChunkGraph,
302) -> Result<(), Vec<ProductionChunkGraphValidationError>> {
303    let mut errors = Vec::new();
304    let chunks = graph
305        .chunks
306        .iter()
307        .map(|chunk| (chunk.id.clone(), chunk))
308        .collect::<BTreeMap<_, _>>();
309    if graph.chunks.windows(2).any(|pair| pair[0].id == pair[1].id) {
310        errors.extend(
311            graph
312                .chunks
313                .windows(2)
314                .filter(|pair| pair[0].id == pair[1].id)
315                .map(|pair| {
316                    ProductionChunkGraphValidationError::DuplicateChunkId(pair[0].id.clone())
317                }),
318        );
319    }
320    let eager_chunks = graph
321        .chunks
322        .iter()
323        .filter(|chunk| chunk.kind == ProductionChunkKind::Eager)
324        .collect::<Vec<_>>();
325    if eager_chunks.len() != 1
326        || eager_chunks.first().map(|chunk| &chunk.id) != Some(&graph.eager_chunk_id)
327    {
328        errors.push(ProductionChunkGraphValidationError::ExpectedExactlyOneEagerChunk);
329    }
330    errors.extend(
331        graph
332            .chunks
333            .iter()
334            .filter(|chunk| chunk.kind == ProductionChunkKind::Shared && !chunk.registration_only)
335            .map(|chunk| {
336                ProductionChunkGraphValidationError::SharedChunkIsNotRegistrationOnly(
337                    chunk.id.clone(),
338                )
339            }),
340    );
341    let mut seen_dependencies = BTreeSet::new();
342    for dependency in &graph.dependencies {
343        if !seen_dependencies.insert(dependency.clone()) {
344            errors.push(ProductionChunkGraphValidationError::DuplicateDependency(
345                dependency.clone(),
346            ));
347        }
348        let Some(dependent) = chunks.get(&dependency.dependent_chunk_id) else {
349            errors.push(ProductionChunkGraphValidationError::MissingDependencyChunk(
350                dependency.dependent_chunk_id.clone(),
351            ));
352            continue;
353        };
354        let Some(target) = chunks.get(&dependency.dependency_chunk_id) else {
355            errors.push(ProductionChunkGraphValidationError::MissingDependencyChunk(
356                dependency.dependency_chunk_id.clone(),
357            ));
358            continue;
359        };
360        let valid = matches!(
361            (dependent.kind, target.kind),
362            (ProductionChunkKind::Shared, ProductionChunkKind::Eager)
363                | (
364                    ProductionChunkKind::Root,
365                    ProductionChunkKind::Eager | ProductionChunkKind::Shared
366                )
367        );
368        if !valid {
369            errors.push(if dependent.kind == ProductionChunkKind::Eager {
370                ProductionChunkGraphValidationError::EagerChunkHasDependency(dependent.id.clone())
371            } else {
372                ProductionChunkGraphValidationError::InvalidChunkDependency(dependency.clone())
373            });
374        }
375    }
376    errors.extend(validate_activation_plans(graph, &chunks));
377    errors.extend(validate_cycle(graph, &chunks));
378    errors.sort();
379    errors.dedup();
380    errors.is_empty().then_some(()).ok_or(errors)
381}
382
383fn validate_candidate_consumers(
384    candidate: &SharedChunkCandidate,
385    root_ids: &BTreeSet<String>,
386    root_programs: &BTreeMap<String, BTreeSet<ExecutableProgramFingerprint>>,
387    extracted_by_root: &mut BTreeMap<String, BTreeSet<ExecutableProgramFingerprint>>,
388    errors: &mut Vec<ProductionChunkGraphValidationError>,
389) {
390    for consumer in &candidate.consumers {
391        if !root_ids.contains(&consumer.root_id) {
392            errors.push(
393                ProductionChunkGraphValidationError::UnknownActivationPlanChunk(
394                    ProductionChunkId::eager_runtime_v1(),
395                ),
396            );
397            continue;
398        }
399        let Some(programs) = root_programs.get(&consumer.root_id) else {
400            continue;
401        };
402        if !candidate
403            .programs
404            .iter()
405            .all(|program| programs.contains(program))
406        {
407            errors.push(
408                ProductionChunkGraphValidationError::InvalidActivationPlanRoot(
409                    ProductionChunkId::eager_runtime_v1(),
410                ),
411            );
412            continue;
413        }
414        extracted_by_root
415            .entry(consumer.root_id.clone())
416            .or_default()
417            .extend(candidate.programs.iter().cloned());
418    }
419}
420
421fn validate_activation_plans(
422    graph: &ProductionChunkGraph,
423    chunks: &BTreeMap<ProductionChunkId, &ProductionChunkRecord>,
424) -> Vec<ProductionChunkGraphValidationError> {
425    let mut errors = Vec::new();
426    let mut roots = BTreeSet::new();
427    for plan in &graph.activation_plans {
428        if !roots.insert(plan.activation_root_id.clone()) {
429            errors.push(
430                ProductionChunkGraphValidationError::DuplicateActivationRoot(
431                    plan.activation_root_id.clone(),
432                ),
433            );
434        }
435        if chunks.get(&plan.root_chunk_id).map(|chunk| chunk.kind)
436            != Some(ProductionChunkKind::Root)
437        {
438            errors.push(
439                ProductionChunkGraphValidationError::InvalidActivationPlanRoot(
440                    plan.root_chunk_id.clone(),
441                ),
442            );
443        }
444        let mut shared = plan.shared_chunk_ids.clone();
445        shared.sort();
446        shared.dedup();
447        if shared != plan.shared_chunk_ids {
448            errors.push(
449                ProductionChunkGraphValidationError::InvalidSharedChunkOrder(
450                    plan.activation_root_id.clone(),
451                ),
452            );
453        }
454        for shared_chunk_id in &plan.shared_chunk_ids {
455            match chunks.get(shared_chunk_id) {
456                Some(chunk)
457                    if chunk.kind == ProductionChunkKind::Shared && chunk.registration_only => {}
458                Some(_) => errors.push(
459                    ProductionChunkGraphValidationError::SharedChunkIsNotRegistrationOnly(
460                        shared_chunk_id.clone(),
461                    ),
462                ),
463                None => errors.push(
464                    ProductionChunkGraphValidationError::UnknownActivationPlanChunk(
465                        shared_chunk_id.clone(),
466                    ),
467                ),
468            }
469        }
470    }
471    errors
472}
473
474fn validate_cycle(
475    graph: &ProductionChunkGraph,
476    chunks: &BTreeMap<ProductionChunkId, &ProductionChunkRecord>,
477) -> Vec<ProductionChunkGraphValidationError> {
478    fn visit(
479        current: &ProductionChunkId,
480        dependencies: &BTreeMap<ProductionChunkId, Vec<ProductionChunkId>>,
481        visiting: &mut BTreeSet<ProductionChunkId>,
482        visited: &mut BTreeSet<ProductionChunkId>,
483    ) -> bool {
484        if !visiting.insert(current.clone()) {
485            return true;
486        }
487        if let Some(next) = dependencies.get(current) {
488            for dependency in next {
489                if !visited.contains(dependency)
490                    && visit(dependency, dependencies, visiting, visited)
491                {
492                    return true;
493                }
494            }
495        }
496        visiting.remove(current);
497        visited.insert(current.clone());
498        false
499    }
500
501    let mut dependencies = BTreeMap::<ProductionChunkId, Vec<ProductionChunkId>>::new();
502    for dependency in &graph.dependencies {
503        dependencies
504            .entry(dependency.dependent_chunk_id.clone())
505            .or_default()
506            .push(dependency.dependency_chunk_id.clone());
507    }
508    let mut visiting = BTreeSet::new();
509    let mut visited = BTreeSet::new();
510    for chunk_id in chunks.keys() {
511        if !visited.contains(chunk_id)
512            && visit(chunk_id, &dependencies, &mut visiting, &mut visited)
513        {
514            return vec![ProductionChunkGraphValidationError::ProductionChunkCycle];
515        }
516    }
517    Vec::new()
518}
519
520fn provisional_filename(prefix: &str, chunk_id: &ProductionChunkId) -> String {
521    let short_id = chunk_id
522        .as_str()
523        .rsplit_once(':')
524        .map_or(chunk_id.as_str(), |(_, suffix)| suffix)
525        .chars()
526        .take(12)
527        .collect::<String>();
528    format!("{prefix}.{short_id}.pending-content-hash.js")
529}
530
531#[cfg(test)]
532mod tests {
533    use super::*;
534    use crate::{plan_shared_lazy_chunk_candidates, SharedChunkProgramOccurrence};
535
536    fn fingerprint(value: &str) -> ExecutableProgramFingerprint {
537        ExecutableProgramFingerprint::for_canonical_opcode_stream(value.as_bytes())
538    }
539
540    fn root(
541        root_id: &str,
542        programs: Vec<ExecutableProgramFingerprint>,
543    ) -> ProductionRootChunkInput {
544        ProductionRootChunkInput {
545            activation_root_id: root_id.to_string(),
546            root_kind: "interaction".to_string(),
547            programs,
548        }
549    }
550
551    fn occurrence(root_id: &str, program: &str) -> SharedChunkProgramOccurrence {
552        SharedChunkProgramOccurrence {
553            root_id: root_id.to_string(),
554            fingerprint: fingerprint(program),
555            canonical_bytes: vec![b'x'; 300],
556            eager_required: false,
557            root_specific: false,
558            captures_mutable_identity: false,
559            registration_only: true,
560            runtime_protocol: "v1".to_string(),
561        }
562    }
563
564    #[test]
565    fn k7_extracts_shared_programs_and_preserves_root_identity() {
566        let shared = fingerprint("shared");
567        let candidates = plan_shared_lazy_chunk_candidates(
568            &[
569                occurrence("root-a", "shared"),
570                occurrence("root-b", "shared"),
571            ],
572            0,
573            0,
574        );
575        let (graph, report) = extract_production_chunk_graph(
576            &candidates,
577            &[
578                root("root-b", vec![shared.clone(), fingerprint("b-only")]),
579                root("root-a", vec![shared.clone(), fingerprint("a-only")]),
580            ],
581        )
582        .expect("eligible candidate extracts");
583        assert_eq!(report.shared_chunk_count, 1);
584        assert_eq!(graph.activation_plans.len(), 2);
585        assert_ne!(
586            graph.activation_plans[0].root_chunk_id,
587            graph.activation_plans[1].root_chunk_id
588        );
589        assert_eq!(
590            graph.activation_plans[0].shared_chunk_failure_policy,
591            ProductionSharedChunkFailurePolicy::FailDependentActivationWithoutRetry
592        );
593        assert_eq!(
594            graph.activation_plans[0].shared_chunk_ids,
595            graph.activation_plans[1].shared_chunk_ids
596        );
597        assert!(graph
598            .chunks
599            .iter()
600            .filter(|chunk| chunk.kind == ProductionChunkKind::Root)
601            .all(|chunk| !chunk.programs.contains(&shared)));
602        assert!(graph.chunks.iter().any(|chunk| {
603            chunk.kind == ProductionChunkKind::Shared
604                && chunk.registration_only
605                && chunk.programs == vec![shared.clone()]
606        }));
607        assert_eq!(
608            graph
609                .chunks
610                .iter()
611                .filter(|chunk| chunk.kind == ProductionChunkKind::Shared)
612                .count(),
613            1
614        );
615    }
616
617    #[test]
618    fn k7_keeps_roots_independent_when_no_candidate_extracts() {
619        let (graph, report) = extract_production_chunk_graph(
620            &SharedChunkCandidatePlan {
621                candidates: Vec::new(),
622                rejections: Vec::new(),
623            },
624            &[
625                root("root-a", vec![fingerprint("a")]),
626                root("root-b", vec![fingerprint("b")]),
627            ],
628        )
629        .expect("roots without sharing remain valid");
630        assert_eq!(report.shared_chunk_count, 0);
631        assert!(graph
632            .activation_plans
633            .iter()
634            .all(|plan| plan.shared_chunk_ids.is_empty()));
635        assert_eq!(
636            graph
637                .chunks
638                .iter()
639                .filter(|chunk| chunk.kind == ProductionChunkKind::Eager)
640                .count(),
641            1
642        );
643    }
644
645    #[test]
646    fn k7_rejects_cycle_and_depth_two_dependencies() {
647        let (mut graph, _) = extract_production_chunk_graph(
648            &SharedChunkCandidatePlan {
649                candidates: Vec::new(),
650                rejections: Vec::new(),
651            },
652            &[root("root-a", vec![fingerprint("a")])],
653        )
654        .expect("base graph");
655        let root_chunk = graph.activation_plans[0].root_chunk_id.clone();
656        graph.dependencies.push(ProductionChunkDependency {
657            dependent_chunk_id: graph.eager_chunk_id.clone(),
658            dependency_chunk_id: root_chunk,
659        });
660        let errors =
661            validate_production_chunk_graph(&graph).expect_err("cycle and eager edge reject");
662        assert!(errors.contains(&ProductionChunkGraphValidationError::ProductionChunkCycle));
663        assert!(errors.iter().any(|error| matches!(
664            error,
665            ProductionChunkGraphValidationError::EagerChunkHasDependency(_)
666        )));
667    }
668
669    #[test]
670    fn k7_rejects_shared_to_shared_depth() {
671        let shared_one = fingerprint("shared-one");
672        let shared_two = fingerprint("shared-two");
673        let candidates = plan_shared_lazy_chunk_candidates(
674            &[
675                occurrence("root-a", "shared-one"),
676                occurrence("root-b", "shared-one"),
677                occurrence("root-b", "shared-two"),
678                occurrence("root-c", "shared-two"),
679            ],
680            0,
681            0,
682        );
683        let (mut graph, _) = extract_production_chunk_graph(
684            &candidates,
685            &[
686                root("root-a", vec![shared_one]),
687                root("root-b", vec![shared_two, fingerprint("shared-one")]),
688                root("root-c", vec![fingerprint("shared-two")]),
689            ],
690        )
691        .expect("two exact candidate root sets");
692        let shared_chunks = graph
693            .chunks
694            .iter()
695            .filter(|chunk| chunk.kind == ProductionChunkKind::Shared)
696            .map(|chunk| chunk.id.clone())
697            .collect::<Vec<_>>();
698        assert_eq!(shared_chunks.len(), 2);
699        graph.dependencies.push(ProductionChunkDependency {
700            dependent_chunk_id: shared_chunks[0].clone(),
701            dependency_chunk_id: shared_chunks[1].clone(),
702        });
703        let errors = validate_production_chunk_graph(&graph).expect_err("depth two rejects");
704        assert!(errors.iter().any(|error| matches!(
705            error,
706            ProductionChunkGraphValidationError::InvalidChunkDependency(_)
707        )));
708    }
709
710    #[test]
711    fn k7_graph_is_deterministic_under_reversed_input_order() {
712        let shared = fingerprint("shared");
713        let candidates = plan_shared_lazy_chunk_candidates(
714            &[
715                occurrence("root-a", "shared"),
716                occurrence("root-b", "shared"),
717            ],
718            0,
719            0,
720        );
721        let first = extract_production_chunk_graph(
722            &candidates,
723            &[
724                root("root-a", vec![shared.clone()]),
725                root("root-b", vec![shared.clone()]),
726            ],
727        );
728        let second = extract_production_chunk_graph(
729            &candidates,
730            &[
731                root("root-b", vec![shared.clone()]),
732                root("root-a", vec![shared]),
733            ],
734        );
735        assert_eq!(first, second);
736    }
737}