Skip to main content

projectatlas_symbols/
resolution_keys.rs

1//! Canonical import facts and resolution-key projection for extracted symbol graphs.
2
3use crate::ConfiguredModuleResolution;
4use crate::configured_modules::ConfiguredModuleSource;
5use crate::semantic;
6use crate::{SourceCallBinding, SourceCallFact, SourceSemanticFacts};
7use blake3::Hasher;
8use projectatlas_core::graph::{
9    CanonicalResolutionKey, GraphContractError, GraphIdentityText, GraphLimits, GraphRelationKind,
10    ProjectInstanceId, ResolutionKeyDomain, SourceSpan,
11};
12use projectatlas_core::language::{LANGUAGE_CAPABILITIES, SemanticProviderOwner};
13use projectatlas_core::symbols::{RelationKind, SymbolGraph, SymbolKind};
14use std::collections::{BTreeMap, HashSet};
15use std::error::Error;
16use std::fmt;
17use std::sync::Arc;
18
19/// Maximum canonical keys emitted for one source, symbol, or relation fact.
20pub const MAX_RESOLUTION_KEYS_PER_FACT: usize = 64;
21/// Version of the currently implemented semantic relation-resolution contract.
22pub const SEMANTIC_RESOLUTION_CONTRACT_VERSION: u32 = 23;
23/// Disjoint ECMAScript lookup namespaces for explicit files and inferred modules.
24const ECMA_FILE_SCOPE: &str = "file:";
25/// Legacy extensionless and package-entry aliases never identify an explicit file.
26const ECMA_MODULE_SCOPE: &str = "module:";
27
28/// Return a deterministic digest of the live semantic resolution-key contract.
29///
30/// This deliberately describes only the provider-backed `imports`, `calls`, and
31/// Cargo `depends-on` behavior implemented here. The broader accepted relation-
32/// family inventory remains a later, independently versioned responsibility.
33#[must_use]
34pub fn semantic_resolution_contract_digest() -> String {
35    semantic_resolution_contract_digest_for_identifier_profile(unicode_ident::UNICODE_VERSION)
36}
37
38/// Bind the Python identifier property's data version without duplicating its tables.
39fn semantic_resolution_contract_digest_for_identifier_profile(
40    identifier_version: (u8, u8, u8),
41) -> String {
42    let mut hasher = Hasher::new();
43    hasher.update(&SEMANTIC_RESOLUTION_CONTRACT_VERSION.to_le_bytes());
44    hasher.update(&(MAX_RESOLUTION_KEYS_PER_FACT as u64).to_le_bytes());
45    let mut providers = LANGUAGE_CAPABILITIES
46        .iter()
47        .filter_map(|capability| capability.effective_semantic_provider())
48        .collect::<Vec<_>>();
49    providers.sort_by_key(|provider| provider.as_str());
50    providers.dedup();
51    for provider in providers {
52        hash_contract_value(&mut hasher, provider.as_str());
53        hash_contract_value(
54            &mut hasher,
55            provider.resolution_family().unwrap_or("unavailable"),
56        );
57        if provider == SemanticProviderOwner::Python {
58            hash_contract_value(&mut hasher, "python_xid_unicode_version");
59            let (major, minor, patch) = identifier_version;
60            hasher.update(&[major, minor, patch]);
61        }
62        if semantic::supports_source_dependencies(provider) {
63            hash_relation_contract(
64                &mut hasher,
65                RelationKind::Imports,
66                ResolutionKeyDomain::Module,
67            );
68            hash_relation_contract(
69                &mut hasher,
70                RelationKind::Calls,
71                ResolutionKeyDomain::Declaration,
72            );
73        }
74        if semantic::supports_package_dependencies(provider) {
75            hash_relation_contract(
76                &mut hasher,
77                RelationKind::DependsOn,
78                ResolutionKeyDomain::Package,
79            );
80        }
81    }
82    for outcome in ["resolved", "ambiguous", "unresolved", "external"] {
83        hash_contract_value(&mut hasher, outcome);
84    }
85    hasher.finalize().to_hex().to_string()
86}
87
88/// Hash one supported relation and its target-identity domain.
89fn hash_relation_contract(
90    hasher: &mut Hasher,
91    relation: RelationKind,
92    domain: ResolutionKeyDomain,
93) {
94    hash_contract_value(hasher, &relation.to_string());
95    hash_contract_value(hasher, domain.as_str());
96}
97
98/// Hash one length-delimited contract label.
99fn hash_contract_value(hasher: &mut Hasher, value: &str) {
100    hasher.update(&(value.len() as u64).to_le_bytes());
101    hasher.update(value.as_bytes());
102}
103/// Canonical resolution keys associated with one extracted symbol index.
104#[derive(Clone, Debug, Eq, PartialEq)]
105pub struct SymbolResolutionKeys {
106    /// Index of the source symbol fact owning these keys.
107    symbol_index: usize,
108    /// Sorted canonical export keys emitted for the symbol.
109    keys: Vec<CanonicalResolutionKey>,
110}
111
112impl SymbolResolutionKeys {
113    /// Return the corresponding index in [`SymbolGraph::symbols`].
114    #[must_use]
115    pub const fn symbol_index(&self) -> usize {
116        self.symbol_index
117    }
118
119    /// Borrow the sorted, deduplicated export keys for this symbol fact.
120    #[must_use]
121    pub fn keys(&self) -> &[CanonicalResolutionKey] {
122        &self.keys
123    }
124}
125
126/// Lookup keys for one explicit forwarding slot, owned by its source file.
127///
128/// A matching file is export evidence to follow, never a callable declaration.
129#[derive(Clone, Debug, Eq, PartialEq)]
130pub struct ExportResolutionKeys {
131    /// Ordinal in the source semantic export vector.
132    export_index: usize,
133    /// Separately bounded keys for this slot rather than one aggregate file budget.
134    keys: Vec<CanonicalResolutionKey>,
135}
136
137impl ExportResolutionKeys {
138    /// Return the explicit source export ordinal owning these lookup keys.
139    #[must_use]
140    pub const fn export_index(&self) -> usize {
141        self.export_index
142    }
143
144    /// Borrow the slot's sorted, deduplicated file-owned lookup keys.
145    #[must_use]
146    pub fn keys(&self) -> &[CanonicalResolutionKey] {
147        &self.keys
148    }
149}
150
151/// Canonical dependency keys associated with one extracted relation index.
152#[derive(Clone, Debug, Eq, PartialEq)]
153pub struct RelationResolutionKeys {
154    /// Index of the source relation fact owning these keys.
155    relation_index: usize,
156    /// Sorted canonical dependency keys emitted for the relation.
157    keys: Vec<CanonicalResolutionKey>,
158}
159
160impl RelationResolutionKeys {
161    /// Return the corresponding index in [`SymbolGraph::relations`].
162    #[must_use]
163    pub const fn relation_index(&self) -> usize {
164        self.relation_index
165    }
166
167    /// Borrow the sorted, deduplicated dependency keys for this relation fact.
168    #[must_use]
169    pub fn keys(&self) -> &[CanonicalResolutionKey] {
170        &self.keys
171    }
172}
173
174/// Parser-owned canonical keys ready for runtime entity/relation binding.
175#[derive(Clone, Debug, Eq, PartialEq)]
176pub struct ResolutionKeyProjection {
177    /// Canonical module exports owned by the source file.
178    source: Vec<CanonicalResolutionKey>,
179    /// Canonical declaration exports associated with symbol facts.
180    symbols: Vec<SymbolResolutionKeys>,
181    /// File-owned lookup evidence for explicit forwarding slots.
182    exports: Vec<ExportResolutionKeys>,
183    /// Canonical dependencies associated with relation facts.
184    relations: Vec<RelationResolutionKeys>,
185    /// Source-bound call decisions; absence retains legacy graph-only helper behavior.
186    source_calls: Option<Vec<SourceCallFact>>,
187    /// Exact admitted occurrence coordinates by raw relation ordinal.
188    source_spans: BTreeMap<usize, SourceSpan>,
189    /// Native Rust import outcomes; absent metadata retains graph-only compatibility.
190    rust_toolchain_imports: Option<BTreeMap<usize, Option<ImportReference>>>,
191}
192
193/// Repository configuration supplied to provider-owned semantic projection.
194#[derive(Clone, Copy, Debug, Default)]
195pub struct ResolutionProjectionContext<'a> {
196    /// Validated configured-module snapshot, when the runtime supplied one.
197    configured_modules: Option<&'a ConfiguredModuleResolution>,
198    /// Exact admitted parser proof for this graph, when supplied by the runtime.
199    source_semantics: Option<&'a SourceSemanticFacts>,
200    /// Owning Cargo manifest and repository-relative package root, when established.
201    rust_package: Option<(&'a str, &'a str)>,
202    /// Shared parent identity for this exact manifest-listed sibling root.
203    rust_root_cohort: Option<&'a str>,
204    /// Remaining operation allowance supplied by the projection owner.
205    retained_bytes_limit: Option<u64>,
206}
207
208/// One derivation's retained ownership and its first byte-admission refusal.
209struct ProjectionBudget {
210    /// Absence preserves the general library entrypoint's existing contract.
211    limit: Option<u64>,
212    /// Admitted projection payload and coexisting lookup state.
213    used: u64,
214    /// A key helper can defer propagation only until the next owning checkpoint.
215    failure: Option<(u64, u64)>,
216}
217
218impl ProjectionBudget {
219    /// Start one derivation with the caller's remaining allowance.
220    const fn new(limit: Option<u64>) -> Self {
221        Self {
222            limit,
223            used: 0,
224            failure: None,
225        }
226    }
227
228    /// Admit a new owner before insertion; retain the first refusal for helpers.
229    fn charge(&mut self, bytes: u64) -> bool {
230        if self.failure.is_some() {
231            return false;
232        }
233        let Some(limit) = self.limit else { return true };
234        let requested = self.used.saturating_add(bytes);
235        if requested > limit {
236            self.failure = Some((limit, requested));
237            return false;
238        }
239        self.used = requested;
240        true
241    }
242
243    /// Propagate byte refusal without returning a partial projection as authority.
244    fn check(&self) -> Result<(), ResolutionProjectionError> {
245        if let Some((limit, requested)) = self.failure {
246            return Err(ResolutionProjectionError::MemoryLimit { limit, requested });
247        }
248        Ok(())
249    }
250
251    /// Reserve fallible top-level payloads before allocating their owners.
252    fn reserve(&mut self, bytes: u64) -> Result<(), ResolutionProjectionError> {
253        self.charge(bytes);
254        self.check()
255    }
256
257    /// Include inline key-vector spare capacity and actual string allocation.
258    fn key(&mut self, key: &CanonicalResolutionKey) -> bool {
259        self.charge(
260            key.retained_heap_bytes()
261                .saturating_add((4 * std::mem::size_of::<CanonicalResolutionKey>()) as u64),
262        )
263    }
264}
265
266/// Provider scopes expanded once per unique import in one source graph.
267type ImportScopeCache = BTreeMap<String, BTreeMap<String, Vec<String>>>;
268
269/// Bounded contract failures retained while a graph keeps valid siblings.
270struct ContractFailures {
271    /// The same per-graph allowance covers successful and rejected facts.
272    budget: ProjectionBudget,
273    /// Rejected derived-key facts retained for typed adapter detail.
274    failures: Vec<ResolutionProjectionFactFailure>,
275    /// Number of distinct rejected derived-key facts observed.
276    rejected_count: usize,
277    /// Whether the source fact has already been rejected.
278    source_seen: bool,
279    /// Rejection flags for dense symbol ordinals.
280    symbol_seen: Vec<bool>,
281    /// Rejection flags for dense relation ordinals.
282    relation_seen: Vec<bool>,
283    /// Rejected explicit export ordinals.
284    export_seen: Vec<bool>,
285}
286
287impl ContractFailures {
288    /// Allocate closed ordinal tracking for one parser graph.
289    fn new(
290        symbol_count: usize,
291        relation_count: usize,
292        export_count: usize,
293        mut budget: ProjectionBudget,
294    ) -> Result<Self, ResolutionProjectionError> {
295        budget.reserve(
296            (std::mem::size_of::<Self>() as u64).saturating_add(
297                symbol_count
298                    .saturating_add(relation_count)
299                    .saturating_add(export_count)
300                    .saturating_mul(std::mem::size_of::<bool>()) as u64,
301            ),
302        )?;
303        Ok(Self {
304            budget,
305            failures: Vec::new(),
306            rejected_count: 0,
307            source_seen: false,
308            symbol_seen: vec![false; symbol_count],
309            relation_seen: vec![false; relation_count],
310            export_seen: vec![false; export_count],
311        })
312    }
313
314    /// Record the first failure for one dense parser fact.
315    fn remember(&mut self, fact: ResolutionProjectionFact, error: GraphContractError) {
316        let seen = match fact {
317            ResolutionProjectionFact::Source => {
318                let seen = self.source_seen;
319                self.source_seen = true;
320                !seen
321            }
322            ResolutionProjectionFact::Symbol(index) => {
323                self.symbol_seen.get_mut(index).is_some_and(|seen| {
324                    let was_seen = *seen;
325                    *seen = true;
326                    !was_seen
327                })
328            }
329            ResolutionProjectionFact::Export(index) => {
330                self.export_seen.get_mut(index).is_some_and(|seen| {
331                    let was_seen = *seen;
332                    *seen = true;
333                    !was_seen
334                })
335            }
336            ResolutionProjectionFact::Relation(index) => {
337                self.relation_seen.get_mut(index).is_some_and(|seen| {
338                    let was_seen = *seen;
339                    *seen = true;
340                    !was_seen
341                })
342            }
343        };
344        if !seen {
345            return;
346        }
347        self.rejected_count = self.rejected_count.saturating_add(1);
348        if self.failures.len() < MAX_RESOLUTION_PROJECTION_FAILURES {
349            if !self
350                .budget
351                .charge((4 * std::mem::size_of::<ResolutionProjectionFactFailure>()) as u64)
352            {
353                return;
354            }
355            self.failures
356                .push(ResolutionProjectionFactFailure { error, fact });
357        }
358    }
359}
360
361impl<'a> ResolutionProjectionContext<'a> {
362    /// Construct a projection context with one validated configured-module snapshot.
363    #[must_use]
364    pub const fn with_configured_modules(
365        configured_modules: &'a ConfiguredModuleResolution,
366    ) -> Self {
367        Self {
368            configured_modules: Some(configured_modules),
369            source_semantics: None,
370            rust_package: None,
371            rust_root_cohort: None,
372            retained_bytes_limit: None,
373        }
374    }
375
376    /// Attach source-owned ordinals for exact lexical/import projection.
377    #[must_use]
378    pub const fn with_source_semantics(mut self, semantics: &'a SourceSemanticFacts) -> Self {
379        self.source_semantics = Some(semantics);
380        self
381    }
382
383    /// Attach actual Cargo ownership for automatic-target invalidation keys.
384    #[must_use]
385    pub const fn with_rust_package(mut self, manifest: &'a str, root: &'a str) -> Self {
386        self.rust_package = Some((manifest, root));
387        self
388    }
389
390    /// Attach an admitted cohort only for this exact Cargo root source.
391    #[must_use]
392    pub const fn with_rust_root_cohort(mut self, directory: &'a str) -> Self {
393        self.rust_root_cohort = Some(directory);
394        self
395    }
396
397    /// Bound derivation by the bytes still available in the owning operation.
398    ///
399    /// Exhaustion returns [`ResolutionProjectionError::MemoryLimit`] without a
400    /// partially admitted projection. The owner supplies its existing ceiling.
401    #[must_use]
402    pub const fn with_retained_bytes_limit(mut self, limit: u64) -> Self {
403        self.retained_bytes_limit = Some(limit);
404        self
405    }
406}
407
408impl ResolutionKeyProjection {
409    /// Count owned vector capacities, canonical text, and source-proof sidecars.
410    ///
411    /// Inline projection metadata is charged by its containing map or owner.
412    #[must_use]
413    pub fn retained_bytes(&self) -> u64 {
414        fn keys_bytes(keys: &Vec<CanonicalResolutionKey>) -> u64 {
415            keys.iter().fold(
416                (keys.capacity() as u64)
417                    .saturating_mul(std::mem::size_of::<CanonicalResolutionKey>() as u64),
418                |bytes, key| bytes.saturating_add(key.retained_heap_bytes()),
419            )
420        }
421        keys_bytes(&self.source)
422            .saturating_add(
423                (self.symbols.capacity() * std::mem::size_of::<SymbolResolutionKeys>()) as u64,
424            )
425            .saturating_add(
426                (self.exports.capacity() * std::mem::size_of::<ExportResolutionKeys>()) as u64,
427            )
428            .saturating_add(
429                (self.relations.capacity() * std::mem::size_of::<RelationResolutionKeys>()) as u64,
430            )
431            .saturating_add(
432                self.symbols
433                    .iter()
434                    .map(|row| keys_bytes(&row.keys))
435                    .fold(0, u64::saturating_add),
436            )
437            .saturating_add(
438                self.exports
439                    .iter()
440                    .map(|row| keys_bytes(&row.keys))
441                    .fold(0, u64::saturating_add),
442            )
443            .saturating_add(
444                self.relations
445                    .iter()
446                    .map(|row| keys_bytes(&row.keys))
447                    .fold(0, u64::saturating_add),
448            )
449            .saturating_add(self.source_calls_retained_bytes())
450            .saturating_add(self.source_spans_retained_bytes())
451            .saturating_add(self.rust_toolchain_imports_retained_bytes())
452    }
453
454    /// Construct an empty projection when one parser graph has no admissible
455    /// canonical identity keys. Entity and relation rows can still be staged;
456    /// the graph publication layer records the rejected key provenance.
457    #[must_use]
458    pub const fn empty() -> Self {
459        Self {
460            source: Vec::new(),
461            symbols: Vec::new(),
462            exports: Vec::new(),
463            relations: Vec::new(),
464            source_calls: None,
465            source_spans: BTreeMap::new(),
466            rust_toolchain_imports: None,
467        }
468    }
469
470    /// Borrow module keys exported by the source file itself.
471    #[must_use]
472    pub fn source_keys(&self) -> &[CanonicalResolutionKey] {
473        &self.source
474    }
475
476    /// Borrow symbol-index-associated export keys.
477    #[must_use]
478    pub fn symbol_keys(&self) -> &[SymbolResolutionKeys] {
479        &self.symbols
480    }
481
482    /// Estimate retained lookup vector metadata separately from canonical key text.
483    #[must_use]
484    pub fn export_lookup_vector_bytes(&self) -> u64 {
485        (self.exports.capacity() as u64)
486            .saturating_mul(std::mem::size_of::<ExportResolutionKeys>() as u64)
487            .saturating_add(
488                self.exports
489                    .iter()
490                    .map(|export| {
491                        (export.keys.capacity() as u64)
492                            .saturating_mul(std::mem::size_of::<CanonicalResolutionKey>() as u64)
493                    })
494                    .fold(0_u64, u64::saturating_add),
495            )
496    }
497
498    /// Borrow separately bounded lookup keys owned by explicit source export slots.
499    #[must_use]
500    pub fn export_keys(&self) -> &[ExportResolutionKeys] {
501        &self.exports
502    }
503
504    /// Borrow relation-index-associated dependency keys.
505    #[must_use]
506    pub fn relation_keys(&self) -> &[RelationResolutionKeys] {
507        &self.relations
508    }
509
510    /// Borrow source-owned call policies, including an explicitly empty proof set.
511    #[must_use]
512    pub fn source_calls(&self) -> Option<&[SourceCallFact]> {
513        self.source_calls.as_deref()
514    }
515
516    /// Estimate retained call-policy vector and payload capacities, including shared sets.
517    #[must_use]
518    pub fn source_calls_retained_bytes(&self) -> u64 {
519        self.source_calls.as_ref().map_or(0, |calls| {
520            (calls.capacity() as u64)
521                .saturating_mul(std::mem::size_of::<SourceCallFact>() as u64)
522                .saturating_add(crate::source_semantics::call_binding_heap_bytes(calls))
523        })
524    }
525
526    /// Return exact source coordinates only for the corresponding admitted relation.
527    #[must_use]
528    pub fn source_span(&self, relation: usize) -> Option<SourceSpan> {
529        self.source_spans.get(&relation).copied()
530    }
531
532    /// Account conservative retained coordinate map rows, excluding inline map metadata.
533    #[must_use]
534    pub fn source_spans_retained_bytes(&self) -> u64 {
535        (self.source_spans.len() as u64).saturating_mul(
536            (std::mem::size_of::<(usize, SourceSpan)>() + 4 * std::mem::size_of::<usize>()) as u64,
537        )
538    }
539
540    /// Borrow source-proven Rust import ownership, including explicit non-toolchain rows.
541    #[must_use]
542    pub fn rust_toolchain_imports(&self) -> Option<&BTreeMap<usize, Option<ImportReference>>> {
543        self.rust_toolchain_imports.as_ref()
544    }
545
546    /// Account the bounded Rust import policy map and its owned canonical paths.
547    #[must_use]
548    pub fn rust_toolchain_imports_retained_bytes(&self) -> u64 {
549        self.rust_toolchain_imports.as_ref().map_or(0, |imports| {
550            let mut modules = HashSet::new();
551            imports.values().fold(0_u64, |bytes, reference| {
552                bytes.saturating_add(128).saturating_add(
553                    reference
554                        .as_ref()
555                        .map_or(0, |reference| reference.retained_heap_bytes(&mut modules)),
556                )
557            })
558        })
559    }
560}
561
562/// Parser fact whose derived canonical key was rejected by graph admission.
563#[derive(Clone, Copy, Debug, Eq, PartialEq)]
564pub enum ResolutionProjectionFact {
565    /// A source-file export scope.
566    Source,
567    /// A symbol-index-associated export.
568    Symbol(usize),
569    /// A relation-index-associated dependency.
570    Relation(usize),
571    /// A source-semantic export-slot lookup.
572    Export(usize),
573}
574
575/// Maximum rejected derived-key facts retained from one parser graph.
576pub const MAX_RESOLUTION_PROJECTION_FAILURES: usize = GraphLimits::MAX_ROWS as usize;
577
578/// One parser fact whose derived canonical key violated the graph contract.
579#[derive(Debug)]
580pub struct ResolutionProjectionFactFailure {
581    /// Stable contract error category for the rejected derived key.
582    error: GraphContractError,
583    /// Exact parser fact that produced the rejected derived key.
584    fact: ResolutionProjectionFact,
585}
586
587impl ResolutionProjectionFactFailure {
588    /// Return the graph-contract error without exposing rejected identity text.
589    #[must_use]
590    pub fn error(&self) -> &GraphContractError {
591        &self.error
592    }
593
594    /// Return the parser fact that produced the rejected derived key.
595    #[must_use]
596    pub const fn fact(&self) -> ResolutionProjectionFact {
597        self.fact
598    }
599
600    /// Consume one failure at an adapter boundary that needs its typed error.
601    #[must_use]
602    pub fn into_parts(self) -> (ResolutionProjectionFact, GraphContractError) {
603        (self.fact, self.error)
604    }
605}
606
607/// Bounded rejected derived-key facts and the partial valid projection.
608#[derive(Debug)]
609pub struct ResolutionProjectionFailure {
610    /// Every retained rejected derived-key fact in deterministic traversal order.
611    failures: Vec<ResolutionProjectionFactFailure>,
612    /// Number of rejected derived-key facts, including rows beyond the detail bound.
613    rejected_count: usize,
614    /// All valid canonical keys retained from the graph.
615    projection: ResolutionKeyProjection,
616}
617
618impl ResolutionProjectionFailure {
619    /// Borrow every retained rejected derived-key fact.
620    #[must_use]
621    pub fn failures(&self) -> &[ResolutionProjectionFactFailure] {
622        &self.failures
623    }
624
625    /// Return the total rejected derived-key count before bounded detail retention.
626    #[must_use]
627    pub const fn rejected_count(&self) -> usize {
628        self.rejected_count
629    }
630
631    /// Borrow the projection containing all valid keys derived before and after the failure.
632    #[must_use]
633    pub fn projection(&self) -> &ResolutionKeyProjection {
634        &self.projection
635    }
636
637    /// Consume the failure at an adapter boundary that needs all retained state.
638    #[must_use]
639    pub fn into_parts(
640        self,
641    ) -> (
642        Vec<ResolutionProjectionFactFailure>,
643        usize,
644        ResolutionKeyProjection,
645    ) {
646        (self.failures, self.rejected_count, self.projection)
647    }
648}
649
650/// Failure while deriving bounded canonical keys from parser facts.
651#[derive(Debug)]
652pub enum ResolutionProjectionError {
653    /// Retained projection growth would exceed the caller's remaining allowance.
654    MemoryLimit {
655        /// Remaining byte allowance provided by the owning operation.
656        limit: u64,
657        /// Bytes requested at the first refused ownership insertion.
658        requested: u64,
659    },
660    /// Source ordinals do not belong to the supplied admitted graph.
661    SourceEvidenceMismatch,
662    /// An extracted or caller-supplied identity violated the graph contract.
663    Contract(Box<ResolutionProjectionFailure>),
664    /// One source fact would exceed the bounded key fan-out.
665    KeyLimit {
666        /// Kind of parser fact being projected.
667        fact: &'static str,
668        /// Index in the owning symbol or relation collection when applicable.
669        index: usize,
670        /// Number of distinct keys requested by the fact.
671        requested: usize,
672    },
673}
674
675impl fmt::Display for ResolutionProjectionError {
676    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
677        match self {
678            Self::MemoryLimit { limit, requested } => write!(
679                formatter,
680                "resolution projection requires {requested} retained bytes, exceeding its {limit}-byte allowance"
681            ),
682            Self::SourceEvidenceMismatch => {
683                formatter.write_str("source semantic evidence does not match the projection graph")
684            }
685            Self::Contract(failure) => match failure.failures.first() {
686                Some(failure) => {
687                    write!(formatter, "invalid resolution identity: {}", failure.error)
688                }
689                None => formatter.write_str("invalid resolution identity"),
690            },
691            Self::KeyLimit {
692                fact,
693                index,
694                requested,
695            } => write!(
696                formatter,
697                "{fact} fact {index} requires {requested} canonical keys, exceeding the per-fact limit of {MAX_RESOLUTION_KEYS_PER_FACT}"
698            ),
699        }
700    }
701}
702
703impl Error for ResolutionProjectionError {
704    fn source(&self) -> Option<&(dyn Error + 'static)> {
705        match self {
706            Self::Contract(failure) => failure
707                .failures
708                .first()
709                .map(|failure| &failure.error as &(dyn Error + 'static)),
710            Self::MemoryLimit { .. } | Self::KeyLimit { .. } | Self::SourceEvidenceMismatch => None,
711        }
712    }
713}
714
715impl From<GraphContractError> for ResolutionProjectionError {
716    fn from(value: GraphContractError) -> Self {
717        Self::Contract(Box::new(ResolutionProjectionFailure {
718            failures: vec![ResolutionProjectionFactFailure {
719                error: value,
720                fact: ResolutionProjectionFact::Source,
721            }],
722            rejected_count: 1,
723            projection: ResolutionKeyProjection::empty(),
724        }))
725    }
726}
727
728/// Language syntax that produced one import reference.
729#[derive(Clone, Copy, Debug, Eq, PartialEq)]
730pub enum ImportSyntax {
731    /// Rust `use` syntax.
732    Rust,
733    /// JavaScript or TypeScript `import` syntax.
734    EcmaScript,
735    /// Python `import` or `from ... import ...` syntax.
736    Python,
737}
738
739/// One normalized imported module or declaration and its caller-local name.
740#[derive(Clone, Debug, Eq, PartialEq)]
741pub struct ImportReference {
742    /// Source-language syntax that produced the reference.
743    syntax: ImportSyntax,
744    /// Immutable module text shared by source slots and consumer clones.
745    module: Arc<str>,
746    /// Imported declaration name for named imports.
747    imported: Option<String>,
748    /// Name visible to callers in the importing source file.
749    local: String,
750}
751
752impl ImportReference {
753    /// Construct one normalized provider-owned import reference.
754    pub(crate) fn new(
755        syntax: ImportSyntax,
756        module: &str,
757        imported: Option<&str>,
758        local: &str,
759    ) -> Self {
760        Self::with_shared_module(syntax, Arc::from(module), imported, local)
761    }
762
763    /// Retain one statement's module allocation without copying it per slot.
764    pub(crate) fn with_shared_module(
765        syntax: ImportSyntax,
766        module: Arc<str>,
767        imported: Option<&str>,
768        local: &str,
769    ) -> Self {
770        Self {
771            syntax,
772            module,
773            imported: imported.map(ToString::to_string),
774            local: local.to_string(),
775        }
776    }
777
778    /// Return the source-language import syntax.
779    #[must_use]
780    pub const fn syntax(&self) -> ImportSyntax {
781        self.syntax
782    }
783
784    /// Borrow the normalized module specifier or path.
785    #[must_use]
786    pub fn module(&self) -> &str {
787        &self.module
788    }
789
790    /// Share the normalized module allocation without copying its source-owned bytes.
791    #[must_use]
792    pub fn shared_module(&self) -> Arc<str> {
793        Arc::clone(&self.module)
794    }
795
796    /// Borrow the imported declaration name, or `None` for a module/namespace import.
797    #[must_use]
798    pub fn imported(&self) -> Option<&str> {
799        self.imported.as_deref()
800    }
801
802    /// Borrow the name used by calls inside the importing source file.
803    #[must_use]
804    pub fn local(&self) -> &str {
805        &self.local
806    }
807
808    /// Charge a shared module once within its containing source estimate.
809    pub(crate) fn retained_heap_bytes(&self, modules: &mut HashSet<*const str>) -> u64 {
810        let module = if modules.insert(Arc::as_ptr(&self.module)) {
811            (self.module.len() as u64).saturating_add((2 * std::mem::size_of::<usize>()) as u64)
812        } else {
813            0
814        };
815        module
816            .saturating_add(self.imported.as_ref().map_or(0, String::capacity) as u64)
817            .saturating_add(self.local.capacity() as u64)
818    }
819}
820
821/// Parse one Rust, JavaScript/TypeScript, or Python import statement.
822///
823/// Malformed and unsupported forms return no references instead of inventing
824/// identities from the complete display statement.
825#[must_use]
826pub fn parse_import_references(import_text: &str) -> Vec<ImportReference> {
827    let mut references = semantic::parse_display_import(import_text);
828    references.sort_by(|left, right| {
829        left.module
830            .cmp(&right.module)
831            .then_with(|| left.imported.cmp(&right.imported))
832            .then_with(|| left.local.cmp(&right.local))
833    });
834    references.dedup();
835    references
836}
837
838/// Resolve one relative ECMAScript module specifier against a repository path.
839#[must_use]
840pub fn resolve_relative_import_path(caller_path: &str, module_spec: &str) -> Option<String> {
841    semantic::ecmascript::resolve_relative_import_path(caller_path, module_spec)
842}
843
844/// Return deterministic module aliases inferred from a repository source path.
845#[must_use]
846pub fn module_aliases_for_path(path: &str) -> Vec<String> {
847    let mut aliases = Vec::new();
848    for stem in source_stems_for_path(path) {
849        let mut components = stem
850            .split('/')
851            .filter(|component| !component.is_empty())
852            .collect::<Vec<_>>();
853        if components
854            .first()
855            .is_some_and(|component| *component == "src")
856        {
857            components.remove(0);
858        }
859        if components.last().is_some_and(|component| {
860            matches!(*component, "lib" | "main" | "mod" | "index" | "__init__")
861        }) {
862            components.pop();
863        }
864        if components.is_empty() {
865            continue;
866        }
867        aliases.push(components.join("::"));
868        aliases.push(components.join("."));
869        if let Some(last) = components.last() {
870            aliases.push((*last).to_string());
871        }
872    }
873    aliases.sort();
874    aliases.dedup();
875    aliases
876}
877
878/// Return source path stems, including package-entry aliases.
879#[must_use]
880pub fn source_stems_for_path(path: &str) -> Vec<String> {
881    let stem = strip_known_source_extension(path);
882    let mut stems = vec![stem.clone()];
883    if let Some((parent, entry_name)) = stem.rsplit_once('/')
884        && matches!(entry_name, "index" | "__init__" | "mod")
885    {
886        stems.push(parent.to_string());
887    }
888    stems.sort();
889    stems.dedup();
890    stems
891}
892
893/// Derive bounded canonical export and dependency keys from one extracted graph.
894///
895/// The returned symbol and relation indices associate parser facts with keys;
896/// runtime graph normalization binds those keys to final entity and logical-
897/// relation owners after resolution.
898///
899/// # Errors
900///
901/// Returns an error when a graph identity is invalid or one parser fact exceeds
902/// the bounded canonical-key fan-out.
903pub fn derive_resolution_keys(
904    project: ProjectInstanceId,
905    package: Option<&str>,
906    graph: &SymbolGraph,
907) -> Result<ResolutionKeyProjection, ResolutionProjectionError> {
908    derive_resolution_keys_with_context(
909        project,
910        package,
911        graph,
912        ResolutionProjectionContext::default(),
913    )
914}
915
916/// Derive bounded canonical keys with explicit repository module configuration.
917///
918/// # Errors
919///
920/// Returns an error when a graph identity is invalid, one parser fact exceeds
921/// the bounded canonical-key fan-out, or an explicitly supplied operation
922/// allowance is exhausted before retaining the complete projection.
923pub fn derive_resolution_keys_with_context(
924    project: ProjectInstanceId,
925    package: Option<&str>,
926    graph: &SymbolGraph,
927    context: ResolutionProjectionContext<'_>,
928) -> Result<ResolutionKeyProjection, ResolutionProjectionError> {
929    if context
930        .source_semantics
931        .is_some_and(|facts| !facts.matches_graph(graph))
932    {
933        return Err(ResolutionProjectionError::SourceEvidenceMismatch);
934    }
935    let mut budget = ProjectionBudget::new(context.retained_bytes_limit);
936    budget.reserve(std::mem::size_of::<ResolutionKeyProjection>() as u64)?;
937    if let Some(facts) = context.source_semantics {
938        budget.reserve(
939            (facts.calls().len() as u64)
940                .saturating_mul((2 * std::mem::size_of::<SourceCallFact>()) as u64)
941                .saturating_add(crate::source_semantics::call_binding_heap_bytes(
942                    facts.calls(),
943                )),
944        )?;
945    }
946    let source_calls = context.source_semantics.map(|facts| facts.calls().to_vec());
947    let mut source_spans = BTreeMap::new();
948    if let Some(facts) = context.source_semantics {
949        for (relation, span) in facts
950            .calls()
951            .iter()
952            .map(|fact| (fact.relation, fact.source_span))
953            .chain(
954                facts
955                    .imports()
956                    .iter()
957                    .map(|fact| (fact.relation, fact.source_span)),
958            )
959        {
960            if let Some(span) = span {
961                if let Some(previous) = source_spans.get(&relation) {
962                    if *previous != span {
963                        return Err(ResolutionProjectionError::SourceEvidenceMismatch);
964                    }
965                } else {
966                    budget.reserve(128)?;
967                    source_spans.insert(relation, span);
968                }
969            }
970        }
971    }
972    let rust_toolchain_imports = context
973        .source_semantics
974        .filter(|_| semantic::provider_for_graph(graph) == Some(SemanticProviderOwner::Rust))
975        .map(|facts| {
976            let mut modules = HashSet::new();
977            let bytes = facts.imports().iter().fold(0_u64, |bytes, import| {
978                bytes.saturating_add(128).saturating_add(
979                    import
980                        .rust_toolchain
981                        .as_deref()
982                        .map_or(0, |reference| reference.retained_heap_bytes(&mut modules)),
983                )
984            });
985            budget.reserve(bytes)?;
986            Ok::<_, ResolutionProjectionError>(
987                facts
988                    .imports()
989                    .iter()
990                    .map(|import| (import.relation, import.rust_toolchain.as_deref().cloned()))
991                    .collect::<BTreeMap<_, _>>(),
992            )
993        })
994        .transpose()?;
995    let Some(provider_owner) = semantic::provider_for_graph(graph) else {
996        return Ok(ResolutionKeyProjection {
997            source: Vec::new(),
998            symbols: Vec::new(),
999            exports: Vec::new(),
1000            relations: Vec::new(),
1001            source_calls,
1002            source_spans,
1003            rust_toolchain_imports,
1004        });
1005    };
1006    let provider = GraphIdentityText::new(provider_owner.as_str())?;
1007    let Some(resolution_family) = provider_owner.resolution_family() else {
1008        return Ok(ResolutionKeyProjection {
1009            source: Vec::new(),
1010            symbols: Vec::new(),
1011            exports: Vec::new(),
1012            relations: Vec::new(),
1013            source_calls,
1014            source_spans,
1015            rust_toolchain_imports,
1016        });
1017    };
1018    let mut contract_failures = ContractFailures::new(
1019        graph.symbols.len(),
1020        graph.relations.len(),
1021        context
1022            .source_semantics
1023            .map_or(0, |facts| facts.exports().len()),
1024        budget,
1025    )?;
1026    let language = GraphIdentityText::new(resolution_family)?;
1027    let package = package.map(GraphIdentityText::new).transpose()?;
1028
1029    let source_scopes = canonical_source_scopes(&graph.path, provider_owner);
1030    let configured_source_scopes =
1031        if provider_owner == SemanticProviderOwner::EcmaScript && package.is_some() {
1032            canonical_configured_source_scopes(&graph.path)
1033        } else {
1034            Vec::new()
1035        };
1036    let emits_source_module_keys = semantic::emits_source_module_keys(graph)
1037        || (provider_owner == SemanticProviderOwner::EcmaScript
1038            && context.source_semantics.is_some_and(|facts| {
1039                !facts.imports().is_empty()
1040                    || !facts.calls().is_empty()
1041                    || !facts.exports().is_empty()
1042            }));
1043    let mut source_keys = Vec::new();
1044    if emits_source_module_keys {
1045        for scope in &source_scopes {
1046            if let Some(key) = retain_derived_key(
1047                canonical_key(
1048                    project,
1049                    ResolutionKeyDomain::Module,
1050                    &provider,
1051                    &language,
1052                    package.as_ref(),
1053                    None,
1054                    RelationKind::Imports,
1055                    scope,
1056                ),
1057                ResolutionProjectionFact::Source,
1058                &mut contract_failures,
1059            ) {
1060                source_keys.push(key);
1061            }
1062        }
1063        for scope in &configured_source_scopes {
1064            if let Some(key) = retain_derived_key(
1065                canonical_key(
1066                    project,
1067                    ResolutionKeyDomain::Module,
1068                    &provider,
1069                    &language,
1070                    None,
1071                    None,
1072                    RelationKind::Imports,
1073                    scope,
1074                ),
1075                ResolutionProjectionFact::Source,
1076                &mut contract_failures,
1077            ) {
1078                source_keys.push(key);
1079            }
1080        }
1081    }
1082    if provider_owner == SemanticProviderOwner::Rust
1083        && let Some((manifest, root)) = context.rust_package
1084        && let Some(package) = package.as_ref()
1085        && let Some(directory) = crate::cargo_automatic_target_directory(&graph.path, root)
1086        && let Some(key) = retain_derived_key(
1087            crate::cargo_target_inventory_key(project, package.as_str(), manifest, directory),
1088            ResolutionProjectionFact::Source,
1089            &mut contract_failures,
1090        )
1091    {
1092        source_keys.push(key);
1093    }
1094    if provider_owner == SemanticProviderOwner::Rust
1095        && let Some((manifest, _root)) = context.rust_package
1096        && let Some(package) = package.as_ref()
1097        && let Some(directory) = context.rust_root_cohort
1098        && let Some(key) = retain_derived_key(
1099            crate::cargo_root_cohort_key(project, package.as_str(), manifest, directory),
1100            ResolutionProjectionFact::Source,
1101            &mut contract_failures,
1102        )
1103    {
1104        source_keys.push(key);
1105    }
1106    contract_failures.budget.check()?;
1107    let source_keys = bounded_keys(source_keys, "source", 0)?;
1108
1109    let explicit_export_names = context
1110        .source_semantics
1111        .filter(|_| {
1112            matches!(
1113                provider_owner,
1114                SemanticProviderOwner::EcmaScript | SemanticProviderOwner::Python
1115            )
1116        })
1117        .map(|facts| {
1118            let mut names = BTreeMap::<usize, Vec<&str>>::new();
1119            if !facts.exports_complete() {
1120                return (0..graph.symbols.len())
1121                    .map(|symbol| (symbol, Vec::new()))
1122                    .collect();
1123            }
1124            let mut slots = BTreeMap::<&str, usize>::new();
1125            for export in facts
1126                .exports()
1127                .iter()
1128                .filter(|export| export.namespace == crate::SourceExportNamespace::Value)
1129            {
1130                *slots.entry(export.name.as_str()).or_default() += 1;
1131            }
1132            for export in facts
1133                .exports()
1134                .iter()
1135                .filter(|export| export.namespace == crate::SourceExportNamespace::Value)
1136            {
1137                match &export.binding {
1138                    crate::SourceExportBinding::LocalDeclaration(symbol) => {
1139                        let names = names.entry(*symbol).or_default();
1140                        if slots.get(export.name.as_str()) == Some(&1) {
1141                            names.push(export.name.as_str());
1142                        }
1143                    }
1144                    crate::SourceExportBinding::AmbiguousDeclarations(symbols)
1145                    | crate::SourceExportBinding::UnresolvedDeclarations(symbols) => {
1146                        for symbol in symbols.iter() {
1147                            names.entry(*symbol).or_default();
1148                        }
1149                    }
1150                    crate::SourceExportBinding::ReExport(_)
1151                    | crate::SourceExportBinding::RustReExport(_)
1152                    | crate::SourceExportBinding::Unresolved => {}
1153                }
1154            }
1155            for names in names.values_mut() {
1156                names.sort_unstable();
1157                names.dedup();
1158            }
1159            names
1160        });
1161    let mut symbol_keys = Vec::new();
1162    for (symbol_index, symbol) in graph.symbols.iter().enumerate() {
1163        contract_failures.budget.check()?;
1164        let mut keys = Vec::new();
1165        match symbol.kind {
1166            SymbolKind::Package if provider_owner == SemanticProviderOwner::Cargo => {
1167                if let Some(key) = retain_derived_key(
1168                    canonical_key(
1169                        project,
1170                        ResolutionKeyDomain::Package,
1171                        &provider,
1172                        &language,
1173                        None,
1174                        None,
1175                        RelationKind::DependsOn,
1176                        &symbol.name,
1177                    ),
1178                    ResolutionProjectionFact::Symbol(symbol_index),
1179                    &mut contract_failures,
1180                ) {
1181                    keys.push(key);
1182                }
1183            }
1184            SymbolKind::Dependency | SymbolKind::Import | SymbolKind::Workspace => {}
1185            _ if emits_source_module_keys
1186                && context
1187                    .source_semantics
1188                    .is_none_or(|facts| !facts.bindings_incomplete)
1189                && !semantic::blocks_direct_call_export(provider_owner, symbol.kind)
1190                && (semantic::is_export_candidate(provider_owner, graph, symbol_index)
1191                    || explicit_export_names
1192                        .as_ref()
1193                        .is_some_and(|names| names.contains_key(&symbol_index))) =>
1194            {
1195                let names = match explicit_export_names
1196                    .as_ref()
1197                    .and_then(|names| names.get(&symbol_index))
1198                {
1199                    Some(names) => names
1200                        .iter()
1201                        .map(|name| std::borrow::Cow::Borrowed(*name))
1202                        .collect::<Vec<_>>(),
1203                    None => vec![if provider_owner == SemanticProviderOwner::EcmaScript {
1204                        semantic::ecmascript_identifier_value(&symbol.name).map_or(
1205                            std::borrow::Cow::Borrowed(symbol.name.as_str()),
1206                            std::borrow::Cow::Owned,
1207                        )
1208                    } else {
1209                        std::borrow::Cow::Borrowed(symbol.name.as_str())
1210                    }],
1211                };
1212                for name in names {
1213                    let identity = match GraphIdentityText::new(name.into_owned()) {
1214                        Ok(identity) => identity,
1215                        Err(error) => {
1216                            remember_contract_failure(
1217                                &mut contract_failures,
1218                                ResolutionProjectionFact::Symbol(symbol_index),
1219                                error,
1220                            );
1221                            continue;
1222                        }
1223                    };
1224                    let mut scopes = Vec::new();
1225                    if let Some(parent) = symbol.parent.as_deref() {
1226                        scopes.extend(
1227                            source_scopes.iter().map(|scope| {
1228                                normalize_repository_scope(&format!("{scope}/{parent}"))
1229                            }),
1230                        );
1231                        scopes.push(normalize_repository_scope(parent));
1232                    } else {
1233                        scopes.extend(source_scopes.iter().cloned());
1234                        scopes.push(String::new());
1235                    }
1236                    scopes.sort();
1237                    scopes.dedup();
1238                    for scope in &scopes {
1239                        let scope = if scope.is_empty() {
1240                            None
1241                        } else {
1242                            match GraphIdentityText::new(scope.clone()) {
1243                                Ok(scope) => Some(scope),
1244                                Err(error) => {
1245                                    remember_contract_failure(
1246                                        &mut contract_failures,
1247                                        ResolutionProjectionFact::Symbol(symbol_index),
1248                                        error,
1249                                    );
1250                                    continue;
1251                                }
1252                            }
1253                        };
1254                        let key = CanonicalResolutionKey::new(
1255                            project,
1256                            ResolutionKeyDomain::Declaration,
1257                            &provider,
1258                            &language,
1259                            package.as_ref(),
1260                            scope.as_ref(),
1261                            Some(GraphRelationKind::from_legacy(RelationKind::Calls)),
1262                            &identity,
1263                        );
1264                        if contract_failures.budget.key(&key) {
1265                            keys.push(key);
1266                        }
1267                        contract_failures.budget.check()?;
1268                    }
1269                    let configured_scopes = configured_source_scopes.iter().map(|scope| {
1270                        symbol.parent.as_deref().map_or_else(
1271                            || scope.clone(),
1272                            |parent| normalize_repository_scope(&format!("{scope}/{parent}")),
1273                        )
1274                    });
1275                    for scope in configured_scopes {
1276                        let scope = match GraphIdentityText::new(scope) {
1277                            Ok(scope) => scope,
1278                            Err(error) => {
1279                                remember_contract_failure(
1280                                    &mut contract_failures,
1281                                    ResolutionProjectionFact::Symbol(symbol_index),
1282                                    error,
1283                                );
1284                                continue;
1285                            }
1286                        };
1287                        let key = CanonicalResolutionKey::new(
1288                            project,
1289                            ResolutionKeyDomain::Declaration,
1290                            &provider,
1291                            &language,
1292                            None,
1293                            Some(&scope),
1294                            Some(GraphRelationKind::from_legacy(RelationKind::Calls)),
1295                            &identity,
1296                        );
1297                        if contract_failures.budget.key(&key) {
1298                            keys.push(key);
1299                        }
1300                        contract_failures.budget.check()?;
1301                    }
1302                    if keys.len() > MAX_RESOLUTION_KEYS_PER_FACT {
1303                        keys = bounded_keys(keys, "symbol", symbol_index)?;
1304                    }
1305                }
1306            }
1307            _ => {}
1308        }
1309        let keys = bounded_keys(keys, "symbol", symbol_index)?;
1310        if !keys.is_empty() {
1311            contract_failures
1312                .budget
1313                .reserve((4 * std::mem::size_of::<SymbolResolutionKeys>()) as u64)?;
1314            symbol_keys.push(SymbolResolutionKeys { symbol_index, keys });
1315        }
1316    }
1317
1318    let mut export_keys = Vec::new();
1319    if matches!(
1320        provider_owner,
1321        SemanticProviderOwner::EcmaScript | SemanticProviderOwner::Rust
1322    ) && let Some(facts) = context
1323        .source_semantics
1324        .filter(|facts| facts.exports_complete())
1325    {
1326        for (export_index, export) in facts.exports().iter().enumerate() {
1327            contract_failures.budget.check()?;
1328            if export.namespace != crate::SourceExportNamespace::Value
1329                || !matches!(
1330                    export.binding,
1331                    crate::SourceExportBinding::ReExport(_)
1332                        | crate::SourceExportBinding::RustReExport(_)
1333                        | crate::SourceExportBinding::UnresolvedDeclarations(_)
1334                        | crate::SourceExportBinding::Unresolved
1335                )
1336            {
1337                continue;
1338            }
1339            let fact = ResolutionProjectionFact::Export(export_index);
1340            let mut keys = Vec::new();
1341            let rust_module = match &export.binding {
1342                crate::SourceExportBinding::RustReExport(binding) => binding.module.as_ref(),
1343                _ => &[],
1344            };
1345            for scope in source_scopes.iter().map(|scope| {
1346                if rust_module.is_empty() {
1347                    scope.clone()
1348                } else {
1349                    format!("{scope}/{}", rust_module.join("/"))
1350                }
1351            }) {
1352                if let Some(key) = retain_derived_key(
1353                    canonical_key(
1354                        project,
1355                        ResolutionKeyDomain::Declaration,
1356                        &provider,
1357                        &language,
1358                        package.as_ref(),
1359                        Some(&scope),
1360                        RelationKind::Calls,
1361                        &export.name,
1362                    ),
1363                    fact,
1364                    &mut contract_failures,
1365                ) {
1366                    keys.push(key);
1367                }
1368            }
1369            for scope in &configured_source_scopes {
1370                if let Some(key) = retain_derived_key(
1371                    canonical_key(
1372                        project,
1373                        ResolutionKeyDomain::Declaration,
1374                        &provider,
1375                        &language,
1376                        None,
1377                        Some(scope),
1378                        RelationKind::Calls,
1379                        &export.name,
1380                    ),
1381                    fact,
1382                    &mut contract_failures,
1383                ) {
1384                    keys.push(key);
1385                }
1386            }
1387            let keys = bounded_keys(keys, "export", export_index)?;
1388            if !keys.is_empty() {
1389                contract_failures
1390                    .budget
1391                    .reserve((4 * std::mem::size_of::<ExportResolutionKeys>()) as u64)?;
1392                export_keys.push(ExportResolutionKeys { export_index, keys });
1393            }
1394        }
1395    }
1396
1397    contract_failures.budget.reserve(
1398        (graph.relations.len() as u64)
1399            .saturating_mul(std::mem::size_of::<Vec<ImportReference>>() as u64),
1400    )?;
1401    let mut parsed_imports = vec![Vec::new(); graph.relations.len()];
1402    if let Some(source) = context.source_semantics {
1403        for import in source.imports() {
1404            reserve_import_references(&import.references, &mut contract_failures.budget)?;
1405            parsed_imports[import.relation].clone_from(&import.references);
1406        }
1407    } else {
1408        for (relation, references) in graph.relations.iter().zip(&mut parsed_imports) {
1409            if relation.kind == RelationKind::Imports {
1410                let parsed = semantic::parse_imports(provider_owner, &relation.target_name);
1411                reserve_import_references(&parsed, &mut contract_failures.budget)?;
1412                *references = parsed;
1413            }
1414        }
1415    }
1416    let mut aliases: BTreeMap<&str, Vec<&ImportReference>> = BTreeMap::new();
1417    for references in &parsed_imports {
1418        for reference in references {
1419            contract_failures
1420                .budget
1421                .reserve(128 + (4 * std::mem::size_of::<&ImportReference>()) as u64)?;
1422            aliases
1423                .entry(reference.local())
1424                .or_default()
1425                .push(reference);
1426        }
1427    }
1428    let configured_source = context
1429        .configured_modules
1430        .map(|configured| configured.for_source(&graph.path));
1431    let mut import_scopes = build_import_scope_cache(
1432        provider_owner,
1433        graph,
1434        &parsed_imports,
1435        configured_source.as_ref(),
1436        &mut contract_failures.budget,
1437    )?;
1438    contract_failures
1439        .budget
1440        .reserve(context.source_semantics.map_or(0, |facts| {
1441            (facts.calls().len().saturating_mul(256) + facts.imports().len().saturating_mul(128))
1442                as u64
1443        }))?;
1444    let rust_calls = context
1445        .source_semantics
1446        .map(|facts| {
1447            facts
1448                .calls()
1449                .iter()
1450                .filter_map(|call| {
1451                    if let SourceCallBinding::RustPath(path) = &call.binding {
1452                        path.reference().map(|reference| (call.relation, reference))
1453                    } else {
1454                        None
1455                    }
1456                })
1457                .collect::<BTreeMap<_, _>>()
1458        })
1459        .unwrap_or_default();
1460    for reference in rust_calls.values().chain(
1461        context
1462            .source_semantics
1463            .into_iter()
1464            .flat_map(SourceSemanticFacts::calls)
1465            .filter_map(|call| {
1466                if let SourceCallBinding::Qualified(reference) = &call.binding {
1467                    Some(reference)
1468                } else {
1469                    None
1470                }
1471            }),
1472    ) {
1473        cache_import_scopes(
1474            &mut import_scopes,
1475            provider_owner,
1476            &graph.path,
1477            reference,
1478            configured_source.as_ref(),
1479            &mut contract_failures.budget,
1480        )?;
1481    }
1482
1483    let mut relation_keys = Vec::new();
1484    let source_import_index = context.source_semantics.map(|facts| {
1485        facts
1486            .imports()
1487            .iter()
1488            .map(|fact| (fact.relation, fact))
1489            .collect::<BTreeMap<_, _>>()
1490    });
1491    let source_call_index = source_calls.as_ref().map(|calls| {
1492        calls
1493            .iter()
1494            .map(|fact| (fact.relation, fact))
1495            .collect::<BTreeMap<_, _>>()
1496    });
1497    for (relation_index, relation) in graph.relations.iter().enumerate() {
1498        contract_failures.budget.check()?;
1499        let mut keys = match relation.kind {
1500            RelationKind::Imports if semantic::supports_source_dependencies(provider_owner) => {
1501                if provider_owner == SemanticProviderOwner::EcmaScript
1502                    && let Some(module) = source_import_index
1503                        .as_ref()
1504                        .and_then(|imports| imports.get(&relation_index))
1505                        .and_then(|fact| fact.module.as_deref())
1506                {
1507                    semantic::ecmascript_module_scopes(
1508                        &relation.path,
1509                        module,
1510                        configured_source.as_ref(),
1511                    )
1512                    .iter()
1513                    .filter_map(|scope| {
1514                        retain_derived_key(
1515                            canonical_key(
1516                                project,
1517                                ResolutionKeyDomain::Module,
1518                                &provider,
1519                                &language,
1520                                dependency_package(provider_owner, module, package.as_ref()),
1521                                None,
1522                                RelationKind::Imports,
1523                                scope,
1524                            ),
1525                            ResolutionProjectionFact::Relation(relation_index),
1526                            &mut contract_failures,
1527                        )
1528                    })
1529                    .collect()
1530                } else {
1531                    import_dependency_keys(
1532                        project,
1533                        provider_owner,
1534                        &provider,
1535                        &language,
1536                        package.as_ref(),
1537                        &relation.path,
1538                        &parsed_imports[relation_index],
1539                        &import_scopes,
1540                        relation_index,
1541                        &mut contract_failures,
1542                    )
1543                }
1544            }
1545            RelationKind::Calls if semantic::supports_source_dependencies(provider_owner) => {
1546                if let Some(calls) = &source_call_index {
1547                    if let Some(SourceCallFact {
1548                        binding:
1549                            SourceCallBinding::Imported {
1550                                import,
1551                                reference,
1552                                member,
1553                                ..
1554                            },
1555                        ..
1556                    }) = calls.get(&relation_index).copied()
1557                    {
1558                        let reference = &context
1559                            .source_semantics
1560                            .ok_or(ResolutionProjectionError::SourceEvidenceMismatch)?
1561                            .imports()[*import]
1562                            .references[*reference];
1563                        imported_reference_call_keys(
1564                            project,
1565                            provider_owner,
1566                            &provider,
1567                            &language,
1568                            package.as_ref(),
1569                            &relation.path,
1570                            reference,
1571                            member.as_deref(),
1572                            &import_scopes,
1573                            relation_index,
1574                            &mut contract_failures,
1575                        )
1576                    } else if let Some(SourceCallFact {
1577                        binding: SourceCallBinding::Qualified(reference),
1578                        ..
1579                    }) = calls.get(&relation_index).copied()
1580                    {
1581                        imported_reference_call_keys(
1582                            project,
1583                            provider_owner,
1584                            &provider,
1585                            &language,
1586                            package.as_ref(),
1587                            &relation.path,
1588                            reference,
1589                            None,
1590                            &import_scopes,
1591                            relation_index,
1592                            &mut contract_failures,
1593                        )
1594                    } else if let Some(reference) = rust_calls.get(&relation_index) {
1595                        imported_reference_call_keys(
1596                            project,
1597                            provider_owner,
1598                            &provider,
1599                            &language,
1600                            package.as_ref(),
1601                            &relation.path,
1602                            reference,
1603                            None,
1604                            &import_scopes,
1605                            relation_index,
1606                            &mut contract_failures,
1607                        )
1608                    } else {
1609                        Vec::new()
1610                    }
1611                } else {
1612                    call_dependency_keys(
1613                        project,
1614                        provider_owner,
1615                        &provider,
1616                        &language,
1617                        package.as_ref(),
1618                        &relation.path,
1619                        &relation.target_name,
1620                        &aliases,
1621                        &import_scopes,
1622                        relation_index,
1623                        &mut contract_failures,
1624                    )
1625                }
1626            }
1627            RelationKind::DependsOn if semantic::supports_package_dependencies(provider_owner) => {
1628                retain_derived_key(
1629                    canonical_key(
1630                        project,
1631                        ResolutionKeyDomain::Package,
1632                        &provider,
1633                        &language,
1634                        None,
1635                        None,
1636                        RelationKind::DependsOn,
1637                        &relation.target_name,
1638                    ),
1639                    ResolutionProjectionFact::Relation(relation_index),
1640                    &mut contract_failures,
1641                )
1642                .into_iter()
1643                .collect()
1644            }
1645            RelationKind::Contains => continue,
1646            RelationKind::Imports | RelationKind::Calls | RelationKind::DependsOn => Vec::new(),
1647        };
1648        keys = bounded_keys(keys, "relation", relation_index)?;
1649        contract_failures.budget.check()?;
1650        contract_failures
1651            .budget
1652            .reserve((4 * std::mem::size_of::<RelationResolutionKeys>()) as u64)?;
1653        relation_keys.push(RelationResolutionKeys {
1654            relation_index,
1655            keys,
1656        });
1657    }
1658
1659    contract_failures.budget.check()?;
1660    let projection = ResolutionKeyProjection {
1661        source: source_keys,
1662        symbols: symbol_keys,
1663        exports: export_keys,
1664        relations: relation_keys,
1665        source_calls,
1666        source_spans,
1667        rust_toolchain_imports,
1668    };
1669    if contract_failures.failures.is_empty() {
1670        Ok(projection)
1671    } else {
1672        Err(ResolutionProjectionError::Contract(Box::new(
1673            ResolutionProjectionFailure {
1674                failures: contract_failures.failures,
1675                rejected_count: contract_failures.rejected_count,
1676                projection,
1677            },
1678        )))
1679    }
1680}
1681
1682/// Expand each unique import scope once for the complete source graph.
1683fn build_import_scope_cache(
1684    provider_owner: SemanticProviderOwner,
1685    graph: &SymbolGraph,
1686    parsed_imports: &[Vec<ImportReference>],
1687    configured_source: Option<&ConfiguredModuleSource<'_>>,
1688    budget: &mut ProjectionBudget,
1689) -> Result<ImportScopeCache, ResolutionProjectionError> {
1690    let mut cache = ImportScopeCache::new();
1691    for (relation, references) in graph.relations.iter().zip(parsed_imports) {
1692        for reference in references {
1693            cache_import_scopes(
1694                &mut cache,
1695                provider_owner,
1696                &relation.path,
1697                reference,
1698                configured_source,
1699                budget,
1700            )?;
1701        }
1702    }
1703    Ok(cache)
1704}
1705
1706/// Charge cloned import payload before retaining one relation's references.
1707fn reserve_import_references(
1708    references: &[ImportReference],
1709    budget: &mut ProjectionBudget,
1710) -> Result<(), ResolutionProjectionError> {
1711    let mut modules = HashSet::new();
1712    budget.reserve(
1713        references.iter().fold(
1714            (references.len() as u64)
1715                .saturating_mul((2 * std::mem::size_of::<ImportReference>()) as u64),
1716            |bytes, reference| bytes.saturating_add(reference.retained_heap_bytes(&mut modules)),
1717        ),
1718    )
1719}
1720
1721/// Retain each bounded provider expansion only after admitting its cache owner.
1722fn cache_import_scopes(
1723    cache: &mut ImportScopeCache,
1724    provider: SemanticProviderOwner,
1725    caller: &str,
1726    reference: &ImportReference,
1727    configured: Option<&ConfiguredModuleSource<'_>>,
1728    budget: &mut ProjectionBudget,
1729) -> Result<(), ResolutionProjectionError> {
1730    if cache
1731        .get(caller)
1732        .is_some_and(|modules| modules.contains_key(reference.module()))
1733    {
1734        return Ok(());
1735    }
1736    // A provider expansion has the existing per-fact fan-out bound. Only this
1737    // one temporary expansion exists before its aggregate cache reservation.
1738    let scopes = semantic::import_scopes(provider, caller, reference, configured);
1739    let bytes = scopes
1740        .iter()
1741        .fold(
1742            128_u64
1743                .saturating_add(reference.module().len() as u64)
1744                .saturating_add((scopes.capacity() * std::mem::size_of::<String>()) as u64),
1745            |bytes, scope| bytes.saturating_add(scope.capacity() as u64),
1746        )
1747        .saturating_add(if cache.contains_key(caller) {
1748            0
1749        } else {
1750            128_u64.saturating_add(caller.len() as u64)
1751        });
1752    budget.reserve(bytes)?;
1753    cache
1754        .entry(caller.to_string())
1755        .or_default()
1756        .insert(reference.module().to_string(), scopes);
1757    Ok(())
1758}
1759
1760/// Borrow one pre-expanded import scope list without allocating lookup keys.
1761fn cached_import_scopes<'a>(
1762    cache: &'a ImportScopeCache,
1763    caller_path: &str,
1764    reference: &ImportReference,
1765) -> &'a [String] {
1766    cache
1767        .get(caller_path)
1768        .and_then(|modules| modules.get(reference.module()))
1769        .map_or(&[], Vec::as_slice)
1770}
1771
1772/// Derive canonical module-target keys for import references.
1773fn import_dependency_keys(
1774    project: ProjectInstanceId,
1775    provider_owner: SemanticProviderOwner,
1776    provider: &GraphIdentityText,
1777    language: &GraphIdentityText,
1778    package: Option<&GraphIdentityText>,
1779    caller_path: &str,
1780    references: &[ImportReference],
1781    import_scopes: &ImportScopeCache,
1782    relation_index: usize,
1783    contract_failures: &mut ContractFailures,
1784) -> Vec<CanonicalResolutionKey> {
1785    let mut keys = Vec::new();
1786    for reference in references {
1787        let scopes = cached_import_scopes(import_scopes, caller_path, reference);
1788        let package = dependency_package(provider_owner, reference.module(), package);
1789        for scope in scopes {
1790            if let Some(key) = retain_derived_key(
1791                canonical_key(
1792                    project,
1793                    ResolutionKeyDomain::Module,
1794                    provider,
1795                    language,
1796                    package,
1797                    None,
1798                    RelationKind::Imports,
1799                    scope,
1800                ),
1801                ResolutionProjectionFact::Relation(relation_index),
1802                contract_failures,
1803            ) {
1804                keys.push(key);
1805            }
1806        }
1807    }
1808    keys
1809}
1810
1811/// Derive canonical declaration keys for one call, including local aliases.
1812fn call_dependency_keys(
1813    project: ProjectInstanceId,
1814    provider_owner: SemanticProviderOwner,
1815    provider: &GraphIdentityText,
1816    language: &GraphIdentityText,
1817    package: Option<&GraphIdentityText>,
1818    caller_path: &str,
1819    target: &str,
1820    aliases: &BTreeMap<&str, Vec<&ImportReference>>,
1821    import_scopes: &ImportScopeCache,
1822    relation_index: usize,
1823    contract_failures: &mut ContractFailures,
1824) -> Vec<CanonicalResolutionKey> {
1825    let (prefix, remainder) = split_qualified_target(target);
1826    let alias = prefix.unwrap_or(target).trim();
1827    if let Some(references) = aliases.get(alias) {
1828        let mut keys = Vec::new();
1829        for reference in references {
1830            keys.extend(imported_reference_call_keys(
1831                project,
1832                provider_owner,
1833                provider,
1834                language,
1835                package,
1836                caller_path,
1837                reference,
1838                remainder,
1839                import_scopes,
1840                relation_index,
1841                contract_failures,
1842            ));
1843        }
1844        return keys;
1845    }
1846
1847    let (scope, identity) = split_qualified_target(target);
1848    if scope.is_some() {
1849        return Vec::new();
1850    }
1851    let Some(identity) = identity.or_else(|| (!target.trim().is_empty()).then_some(target.trim()))
1852    else {
1853        return Vec::new();
1854    };
1855    retain_derived_key(
1856        canonical_key(
1857            project,
1858            ResolutionKeyDomain::Declaration,
1859            provider,
1860            language,
1861            package,
1862            None,
1863            RelationKind::Calls,
1864            identity,
1865        ),
1866        ResolutionProjectionFact::Relation(relation_index),
1867        contract_failures,
1868    )
1869    .into_iter()
1870    .collect()
1871}
1872
1873/// Derive one call from its selected source import, without a file-wide alias guess.
1874fn imported_reference_call_keys(
1875    project: ProjectInstanceId,
1876    provider_owner: SemanticProviderOwner,
1877    provider: &GraphIdentityText,
1878    language: &GraphIdentityText,
1879    package: Option<&GraphIdentityText>,
1880    caller_path: &str,
1881    reference: &ImportReference,
1882    member: Option<&str>,
1883    import_scopes: &ImportScopeCache,
1884    relation_index: usize,
1885    contract_failures: &mut ContractFailures,
1886) -> Vec<CanonicalResolutionKey> {
1887    let mut keys = Vec::new();
1888    let (member_parent, identity) = match member {
1889        Some(member) if provider_owner == SemanticProviderOwner::Rust => member
1890            .rsplit_once("::")
1891            .map_or((None, Some(member)), |(parent, name)| {
1892                (Some(parent), Some(name))
1893            }),
1894        _ => (None, member.or_else(|| reference.imported())),
1895    };
1896    let Some(identity) = identity else {
1897        return Vec::new();
1898    };
1899    let scopes = cached_import_scopes(import_scopes, caller_path, reference);
1900    let package = dependency_package(provider_owner, reference.module(), package);
1901    for scope in scopes {
1902        let mut scope = std::borrow::Cow::Borrowed(scope.as_str());
1903        if member.is_some()
1904            && let Some(imported_parent) = reference.imported()
1905        {
1906            scope = std::borrow::Cow::Owned(format!("{scope}/{imported_parent}"));
1907        }
1908        if let Some(parent) = member_parent {
1909            scope =
1910                std::borrow::Cow::Owned(format!("{scope}/{}", normalize_repository_scope(parent)));
1911        }
1912        if let Some(key) = retain_derived_key(
1913            canonical_key(
1914                project,
1915                ResolutionKeyDomain::Declaration,
1916                provider,
1917                language,
1918                package,
1919                Some(scope.as_ref()),
1920                RelationKind::Calls,
1921                identity,
1922            ),
1923            ResolutionProjectionFact::Relation(relation_index),
1924            contract_failures,
1925        ) {
1926            keys.push(key);
1927        }
1928    }
1929    keys
1930}
1931
1932/// Configured ECMAScript targets are repository paths, so caller Cargo
1933/// ownership must not prevent them from matching a sibling package export.
1934fn dependency_package<'a>(
1935    provider_owner: SemanticProviderOwner,
1936    module: &str,
1937    package: Option<&'a GraphIdentityText>,
1938) -> Option<&'a GraphIdentityText> {
1939    if provider_owner == SemanticProviderOwner::EcmaScript
1940        && !(module.starts_with("./") || module.starts_with("../"))
1941    {
1942        None
1943    } else {
1944        package
1945    }
1946}
1947
1948#[allow(clippy::too_many_arguments)]
1949/// Construct one validated canonical key from parser-owned identity parts.
1950fn canonical_key(
1951    project: ProjectInstanceId,
1952    domain: ResolutionKeyDomain,
1953    provider: &GraphIdentityText,
1954    language: &GraphIdentityText,
1955    package: Option<&GraphIdentityText>,
1956    scope: Option<&str>,
1957    relation: RelationKind,
1958    identity: &str,
1959) -> Result<CanonicalResolutionKey, GraphContractError> {
1960    let scope = scope.map(GraphIdentityText::new).transpose()?;
1961    let identity = GraphIdentityText::new(identity.to_string())?;
1962    Ok(CanonicalResolutionKey::new(
1963        project,
1964        domain,
1965        provider,
1966        language,
1967        package,
1968        scope.as_ref(),
1969        Some(GraphRelationKind::from_legacy(relation)),
1970        &identity,
1971    ))
1972}
1973
1974/// Retain one valid derived key while remembering the first rejected fact.
1975fn retain_derived_key(
1976    result: Result<CanonicalResolutionKey, GraphContractError>,
1977    fact: ResolutionProjectionFact,
1978    contract_failures: &mut ContractFailures,
1979) -> Option<CanonicalResolutionKey> {
1980    match result {
1981        Ok(key) => contract_failures.budget.key(&key).then_some(key),
1982        Err(error) => {
1983            remember_contract_failure(contract_failures, fact, error);
1984            None
1985        }
1986    }
1987}
1988
1989/// Keep one deterministic contract failure without retaining rejected identity text.
1990fn remember_contract_failure(
1991    contract_failures: &mut ContractFailures,
1992    fact: ResolutionProjectionFact,
1993    error: GraphContractError,
1994) {
1995    contract_failures.remember(fact, error);
1996}
1997
1998/// Sort, deduplicate, and enforce the per-fact canonical-key limit.
1999fn bounded_keys(
2000    mut keys: Vec<CanonicalResolutionKey>,
2001    fact: &'static str,
2002    index: usize,
2003) -> Result<Vec<CanonicalResolutionKey>, ResolutionProjectionError> {
2004    keys.sort();
2005    keys.dedup();
2006    if keys.len() > MAX_RESOLUTION_KEYS_PER_FACT {
2007        return Err(ResolutionProjectionError::KeyLimit {
2008            fact,
2009            index,
2010            requested: keys.len(),
2011        });
2012    }
2013    Ok(keys)
2014}
2015
2016/// Return normalized canonical scopes exported by a repository source path.
2017fn canonical_source_scopes(path: &str, provider: SemanticProviderOwner) -> Vec<String> {
2018    if provider == SemanticProviderOwner::EcmaScript {
2019        return canonical_configured_source_scopes(path);
2020    }
2021    let mut scopes = source_stems_for_path(path);
2022    scopes.extend(module_aliases_for_path(path));
2023    normalize_scopes(scopes, normalize_repository_scope)
2024}
2025
2026/// Return only repository-exact scopes used by configured ECMAScript targets.
2027fn canonical_configured_source_scopes(path: &str) -> Vec<String> {
2028    let mut scopes = source_stems_for_path(path)
2029        .into_iter()
2030        .filter(|scope| !scope.is_empty())
2031        .collect::<Vec<_>>();
2032    scopes.sort();
2033    scopes.dedup();
2034    ecmascript_source_scopes(path, scopes)
2035}
2036
2037/// Frame extensionless aliases separately from explicit file identities.
2038///
2039/// Keep only the established JavaScript-output source substitutions. These keys
2040/// represent candidate sets, not compiler search order; competing files remain
2041/// ambiguous. In particular `.mjs`, `.cjs` and embedded hosts never share them.
2042fn ecmascript_source_scopes(path: &str, module_scopes: Vec<String>) -> Vec<String> {
2043    let mut scopes = module_scopes
2044        .into_iter()
2045        .map(|scope| format!("{ECMA_MODULE_SCOPE}{scope}"))
2046        .collect::<Vec<_>>();
2047    scopes.push(format!("{ECMA_FILE_SCOPE}{path}"));
2048    let lower = path.to_ascii_lowercase();
2049    let substitution = [".d.ts", ".tsx", ".ts", ".jsx"]
2050        .into_iter()
2051        .find(|extension| lower.ends_with(extension));
2052    if let Some(extension) = substitution {
2053        let stem = &path[..path.len() - extension.len()];
2054        scopes.push(format!("{ECMA_FILE_SCOPE}{stem}.js"));
2055        if matches!(extension, ".d.ts" | ".tsx") {
2056            scopes.push(format!("{ECMA_FILE_SCOPE}{stem}.jsx"));
2057        }
2058    }
2059    scopes.sort();
2060    scopes.dedup();
2061    scopes
2062}
2063
2064/// Preserve explicit terminal suffixes independently of automatic language
2065/// detection; directory dots do not make an extensionless module a file lookup.
2066pub(super) fn ecmascript_module_scope(path: &str) -> String {
2067    let explicit = path
2068        .rsplit('/')
2069        .next()
2070        .is_some_and(|name| name.contains('.'));
2071    let namespace = if explicit {
2072        ECMA_FILE_SCOPE
2073    } else {
2074        ECMA_MODULE_SCOPE
2075    };
2076    format!("{namespace}{path}")
2077}
2078
2079/// Normalize, expand, sort, and deduplicate candidate scopes.
2080fn normalize_scopes(scopes: Vec<String>, normalize: fn(&str) -> String) -> Vec<String> {
2081    let mut normalized = Vec::new();
2082    for scope in scopes {
2083        let scope = normalize(&scope);
2084        if scope.is_empty() {
2085            continue;
2086        }
2087        normalized.push(scope.clone());
2088        if let Some(stripped) = scope.strip_prefix("src/") {
2089            normalized.push(stripped.to_string());
2090        }
2091        if let Some(last) = scope.rsplit('/').next() {
2092            normalized.push(last.to_string());
2093        }
2094    }
2095    normalized.sort();
2096    normalized.dedup();
2097    normalized
2098}
2099
2100/// Normalize repository path and Rust-style alias separators without changing dots.
2101fn normalize_repository_scope(value: &str) -> String {
2102    value
2103        .trim()
2104        .trim_start_matches("./")
2105        .replace("::", "/")
2106        .trim_matches('/')
2107        .to_string()
2108}
2109
2110/// Split a qualified call target into its scope and final identity.
2111fn split_qualified_target(target: &str) -> (Option<&str>, Option<&str>) {
2112    let target = target.trim();
2113    let rust = target.rsplit_once("::");
2114    let dotted = target.rsplit_once('.');
2115    match (rust, dotted) {
2116        (Some(rust), Some(dotted)) => {
2117            if rust.0.len() >= dotted.0.len() {
2118                (Some(rust.0), Some(rust.1))
2119            } else {
2120                (Some(dotted.0), Some(dotted.1))
2121            }
2122        }
2123        (Some((scope, identity)), None) | (None, Some((scope, identity))) => {
2124            (Some(scope), Some(identity))
2125        }
2126        (None, None) => (None, None),
2127    }
2128}
2129
2130/// Strip a terminal source extension while preserving dotted directory names.
2131pub(super) fn strip_known_source_extension(path: &str) -> String {
2132    for extension in [
2133        ".d.ts", ".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".py", ".rs",
2134    ] {
2135        if let Some(stem) = path.strip_suffix(extension) {
2136            return stem.to_string();
2137        }
2138    }
2139    path.rsplit_once('.').map_or_else(
2140        || path.to_string(),
2141        |(stem, extension)| {
2142            if extension.contains('/') {
2143                path.to_string()
2144            } else {
2145                stem.to_string()
2146            }
2147        },
2148    )
2149}
2150
2151#[cfg(test)]
2152mod tests {
2153    use super::{
2154        ImportReference, ImportSyntax, ResolutionProjectionContext, ResolutionProjectionError,
2155        SEMANTIC_RESOLUTION_CONTRACT_VERSION, build_import_scope_cache, derive_resolution_keys,
2156        derive_resolution_keys_with_context, parse_import_references, resolve_relative_import_path,
2157        semantic_resolution_contract_digest,
2158    };
2159    use crate::{
2160        ConfiguredModuleResolution, EcmaScriptConfigKind, EcmaScriptModuleConfig,
2161        EcmaScriptPathMapping, extract_symbol_graph,
2162    };
2163    use projectatlas_core::graph::{CanonicalResolutionKey, ProjectInstanceId};
2164    use projectatlas_core::language::SemanticProviderOwner;
2165    use projectatlas_core::symbols::{
2166        CodeSymbol, ParserKind, RelationKind, SymbolGraph, SymbolKind, SymbolRelation,
2167    };
2168    use std::error::Error;
2169    use std::io;
2170
2171    fn reference(
2172        syntax: ImportSyntax,
2173        module: &str,
2174        imported: Option<&str>,
2175        local: &str,
2176    ) -> ImportReference {
2177        ImportReference::new(syntax, module, imported, local)
2178    }
2179
2180    #[test]
2181    fn explicit_forwarding_slots_have_separate_file_owned_lookup_budgets()
2182    -> Result<(), Box<dyn Error>> {
2183        use std::fmt::Write;
2184        let project = project_id(91)?;
2185        let control = projectatlas_core::IndexWorkControl::new(
2186            projectatlas_core::IndexCancellation::new(),
2187            None,
2188        );
2189        let mut source = String::new();
2190        for index in 0..80 {
2191            writeln!(
2192                source,
2193                "export {{ work as slot{index} }} from './worker.js';"
2194            )?;
2195        }
2196        source.push_str("export type { Item as typed } from './types'; export * from './wild';");
2197        let extracted = crate::extract_symbol_graph_with_semantics_controlled(
2198            "src/barrel.ts",
2199            Some("typescript"),
2200            &source,
2201            &control,
2202        )?;
2203        let projection = derive_resolution_keys_with_context(
2204            project,
2205            None,
2206            extracted.graph(),
2207            ResolutionProjectionContext::default().with_source_semantics(extracted.semantics()),
2208        )?;
2209        require(
2210            projection.export_keys().len() == 80,
2211            "forwarding slots used an aggregate file fanout budget or admitted a type/wildcard slot",
2212        )?;
2213        require(
2214            projection.symbol_keys().is_empty(),
2215            "forwarding slot synthesized declaration keys",
2216        )?;
2217        require(
2218            projection.export_lookup_vector_bytes() > 0,
2219            "slot vector capacity not accounted",
2220        )?;
2221        for export in projection.export_keys() {
2222            require(
2223                !export.keys().is_empty()
2224                    && export.keys().len() <= super::MAX_RESOLUTION_KEYS_PER_FACT,
2225                "per-slot key bound",
2226            )?;
2227            require(
2228                matches!(
2229                    extracted.semantics().exports()[export.export_index()].binding,
2230                    crate::SourceExportBinding::ReExport(_)
2231                ),
2232                "lookup keys lost source association",
2233            )?;
2234        }
2235        let caller = crate::extract_symbol_graph_with_semantics_controlled(
2236            "src/caller.ts",
2237            Some("typescript"),
2238            "import {slot79} from './barrel'; function caller() {slot79();}",
2239            &control,
2240        )?;
2241        let caller_projection = derive_resolution_keys_with_context(
2242            project,
2243            None,
2244            caller.graph(),
2245            ResolutionProjectionContext::default().with_source_semantics(caller.semantics()),
2246        )?;
2247        require(
2248            projection.export_keys().iter().any(|slot| {
2249                keys_intersect(
2250                    relation_keys(caller.graph(), &caller_projection, "slot79"),
2251                    slot.keys(),
2252                )
2253            }),
2254            "consumer cannot find file-owned forwarding slot",
2255        )?;
2256        Ok(())
2257    }
2258
2259    #[test]
2260    fn invalid_forwarding_identity_retains_separately_valid_export_slots()
2261    -> Result<(), Box<dyn Error>> {
2262        let project = project_id(92)?;
2263        let control = projectatlas_core::IndexWorkControl::new(
2264            projectatlas_core::IndexCancellation::new(),
2265            None,
2266        );
2267        let extracted = crate::extract_symbol_graph_with_semantics_controlled(
2268            "src/barrel.js",
2269            Some("javascript"),
2270            "export { work as '' } from './worker';\nexport {work as good} from './worker';",
2271            &control,
2272        )?;
2273        let Err(ResolutionProjectionError::Contract(failure)) = derive_resolution_keys_with_context(
2274            project,
2275            None,
2276            extracted.graph(),
2277            ResolutionProjectionContext::default().with_source_semantics(extracted.semantics()),
2278        ) else {
2279            return Err("invalid export lookup identity did not reach admission".into());
2280        };
2281        require(
2282            failure.rejected_count() == 1
2283                && failure.failures()[0].fact() == super::ResolutionProjectionFact::Export(0),
2284            "export rejection did not retain exact source ordinal",
2285        )?;
2286        require(
2287            failure.projection().export_keys().len() == 1
2288                && failure.projection().export_keys()[0].export_index() == 1,
2289            "invalid slot erased valid sibling",
2290        )?;
2291        Ok(())
2292    }
2293
2294    #[test]
2295    fn duplicate_default_slots_never_narrow_to_one_surviving_declaration()
2296    -> Result<(), Box<dyn Error>> {
2297        let project = project_id(87)?;
2298        let control = projectatlas_core::IndexWorkControl::new(
2299            projectatlas_core::IndexCancellation::new(),
2300            None,
2301        );
2302        for (language, path) in [
2303            ("javascript", "target.js"),
2304            ("typescript", "target.ts"),
2305            ("tsx", "target.tsx"),
2306            ("vue", "target.vue"),
2307            ("svelte", "target.svelte"),
2308            ("html", "target.html"),
2309        ] {
2310            for second in [
2311                "export default class Second {}",
2312                "export default function () {}",
2313                "export default {};",
2314                "export default function First() {}",
2315                "export { secondary as default };",
2316                "export { secondary as 'default' };",
2317                "export { default } from './other';",
2318                "export * as default from './other';",
2319            ] {
2320                let script = format!(
2321                    "export default function First() {{}}\n{second}\nexport function secondary() {{}}"
2322                );
2323                let content = if matches!(language, "vue" | "svelte" | "html") {
2324                    let role = match language {
2325                        "html" => "type=module",
2326                        "svelte" => "module",
2327                        _ => "",
2328                    };
2329                    format!("<script {role}>{script}</script>")
2330                } else {
2331                    script
2332                };
2333                let extracted = crate::extract_symbol_graph_with_semantics_controlled(
2334                    path,
2335                    Some(language),
2336                    &content,
2337                    &control,
2338                )?;
2339                let projection = derive_resolution_keys_with_context(
2340                    project,
2341                    None,
2342                    extracted.graph(),
2343                    ResolutionProjectionContext::default()
2344                        .with_source_semantics(extracted.semantics()),
2345                )?;
2346                if !symbol_keys(extracted.graph(), &projection, "First").is_empty() {
2347                    return Err(
2348                        format!("duplicate default gained a target: {language}: {second}").into(),
2349                    );
2350                }
2351                if symbol_keys(extracted.graph(), &projection, "secondary").is_empty() {
2352                    return Err(format!(
2353                        "invalid default removed independent named export: {language}: {second}"
2354                    )
2355                    .into());
2356                }
2357                let (_, mut graph, mut facts) = extracted.into_parts();
2358                let symbols = graph
2359                    .symbols
2360                    .iter()
2361                    .map(|symbol| symbol.name != "Second")
2362                    .collect::<Vec<_>>();
2363                let relations = vec![true; graph.relations.len()];
2364                facts.retain_graph_facts(&mut graph, &symbols, &relations)?;
2365                let projection = derive_resolution_keys_with_context(
2366                    project,
2367                    None,
2368                    &graph,
2369                    ResolutionProjectionContext::default().with_source_semantics(&facts),
2370                )?;
2371                require(
2372                    symbol_keys(&graph, &projection, "First").is_empty(),
2373                    "filtering a competing default narrowed export proof",
2374                )?;
2375            }
2376        }
2377        Ok(())
2378    }
2379
2380    #[test]
2381    fn duplicate_export_slots_do_not_erase_independent_names_for_one_declaration()
2382    -> Result<(), Box<dyn Error>> {
2383        let project = project_id(88)?;
2384        let control = projectatlas_core::IndexWorkControl::new(
2385            projectatlas_core::IndexCancellation::new(),
2386            None,
2387        );
2388        let caller = crate::extract_symbol_graph_with_semantics_controlled(
2389            "src/caller.js",
2390            Some("javascript"),
2391            "import Entry, { named, duplicate, Actual } from './target'; function caller() { Entry(); named(); duplicate(); Actual(); }",
2392            &control,
2393        )?;
2394        let caller_keys = derive_resolution_keys_with_context(
2395            project,
2396            None,
2397            caller.graph(),
2398            ResolutionProjectionContext::default().with_source_semantics(caller.semantics()),
2399        )?;
2400        for source in [
2401            "export default function Actual() {} export { Actual as named, Actual as duplicate }; export { missing as default, missing as duplicate };",
2402            "function Actual() {} export { Actual as default, Actual as named, Actual as duplicate }; export { missing as default, missing as duplicate };",
2403        ] {
2404            let target = crate::extract_symbol_graph_with_semantics_controlled(
2405                "src/target.js",
2406                Some("javascript"),
2407                source,
2408                &control,
2409            )?;
2410            let target_keys = derive_resolution_keys_with_context(
2411                project,
2412                None,
2413                target.graph(),
2414                ResolutionProjectionContext::default().with_source_semantics(target.semantics()),
2415            )?;
2416            let actual = symbol_keys(target.graph(), &target_keys, "Actual");
2417            require(
2418                keys_intersect(relation_keys(caller.graph(), &caller_keys, "named"), actual),
2419                "duplicate removed independent named slot",
2420            )?;
2421            for name in ["Entry", "duplicate", "Actual"] {
2422                require(
2423                    !keys_intersect(relation_keys(caller.graph(), &caller_keys, name), actual),
2424                    "duplicate or absent slot fabricated a target",
2425                )?;
2426            }
2427        }
2428        Ok(())
2429    }
2430
2431    #[test]
2432    fn python_rebound_module_names_withhold_only_their_export_keys() -> Result<(), Box<dyn Error>> {
2433        let project = project_id(93)?;
2434        let control = projectatlas_core::IndexWorkControl::new(
2435            projectatlas_core::IndexCancellation::new(),
2436            None,
2437        );
2438        for (suffix, rebound) in [
2439            ("def mutate():\n global run\n run = unknown\n", true),
2440            ("run = unknown\n", true),
2441            (
2442                "def invalid():\n run()\n global run\n run = unknown\n",
2443                false,
2444            ),
2445        ] {
2446            let source =
2447                format!("def run(): pass\ndef safe(): pass\ndef _private(): pass\n{suffix}");
2448            let extracted = crate::extract_symbol_graph_with_semantics_controlled(
2449                "worker.py",
2450                Some("python"),
2451                &source,
2452                &control,
2453            )?;
2454            let keys = derive_resolution_keys_with_context(
2455                project,
2456                None,
2457                extracted.graph(),
2458                ResolutionProjectionContext::default().with_source_semantics(extracted.semantics()),
2459            )?;
2460            require(
2461                symbol_keys(extracted.graph(), &keys, "run").is_empty() == rebound,
2462                "Python export authority disagreed with valid writer evidence",
2463            )?;
2464            require(
2465                symbol_keys(extracted.graph(), &keys, "_private").is_empty(),
2466                "source export roles exposed a private Python declaration",
2467            )?;
2468            require(
2469                !symbol_keys(extracted.graph(), &keys, "safe").is_empty(),
2470                "unrelated Python export lost its callable keys",
2471            )?;
2472        }
2473        Ok(())
2474    }
2475
2476    #[test]
2477    fn type_and_ordinary_value_exports_do_not_supply_callable_keys() -> Result<(), Box<dyn Error>> {
2478        let project = project_id(90)?;
2479        let control = projectatlas_core::IndexWorkControl::new(
2480            projectatlas_core::IndexCancellation::new(),
2481            None,
2482        );
2483        for (language, extension, source) in [
2484            (
2485                "typescript",
2486                "ts",
2487                "export type Alias = () => void; export interface Shape {} export class Runtime {} export enum Values { One } export const scalar = 1; export function run() {}",
2488            ),
2489            (
2490                "tsx",
2491                "tsx",
2492                "export type Alias = () => void; export interface Shape {} export class Runtime {} export enum Values { One } export const scalar = 1; export function run() {}",
2493            ),
2494            (
2495                "rust",
2496                "rs",
2497                "pub type Alias = fn(); pub trait Shape {} pub struct Runtime(); pub enum Values { One } pub const scalar: i32 = 1; pub fn run() {}",
2498            ),
2499            (
2500                "rust-build-script",
2501                "rs",
2502                "pub type Alias = fn(); pub trait Shape {} pub struct Runtime(); pub enum Values { One } pub const scalar: i32 = 1; pub fn run() {}",
2503            ),
2504        ] {
2505            let target = crate::extract_symbol_graph_with_semantics_controlled(
2506                &format!("src/worker.{extension}"),
2507                Some(language),
2508                source,
2509                &control,
2510            )?;
2511            let keys = derive_resolution_keys_with_context(
2512                project,
2513                None,
2514                target.graph(),
2515                ResolutionProjectionContext::default().with_source_semantics(target.semantics()),
2516            )?;
2517            for name in ["Alias", "Shape", "scalar"] {
2518                require(
2519                    symbol_keys(target.graph(), &keys, name).is_empty(),
2520                    "type-only declaration supplied call keys",
2521                )?;
2522            }
2523            require(
2524                !symbol_keys(target.graph(), &keys, "run").is_empty(),
2525                "function positive lost callable keys",
2526            )?;
2527            require(
2528                !symbol_keys(target.graph(), &keys, "Runtime").is_empty(),
2529                "runtime constructor positive lost callable keys",
2530            )?;
2531            require(
2532                !symbol_keys(target.graph(), &keys, "Values").is_empty(),
2533                "runtime enum export compatibility lost keys",
2534            )?;
2535        }
2536        Ok(())
2537    }
2538
2539    #[test]
2540    fn type_only_export_names_do_not_suppress_runtime_callable_exports()
2541    -> Result<(), Box<dyn Error>> {
2542        let project = project_id(89)?;
2543        let control = projectatlas_core::IndexWorkControl::new(
2544            projectatlas_core::IndexCancellation::new(),
2545            None,
2546        );
2547        let caller = crate::extract_symbol_graph_with_semantics_controlled(
2548            "src/caller.ts",
2549            Some("typescript"),
2550            "import { named } from './target'; function caller() { named(); }",
2551            &control,
2552        )?;
2553        let caller_keys = derive_resolution_keys_with_context(
2554            project,
2555            None,
2556            caller.graph(),
2557            ResolutionProjectionContext::default().with_source_semantics(caller.semantics()),
2558        )?;
2559        let target = crate::extract_symbol_graph_with_semantics_controlled(
2560            "src/target.ts",
2561            Some("typescript"),
2562            "type Item = unknown; function run() {} export type {Item as named}; export {type Item as typed, run as named};",
2563            &control,
2564        )?;
2565        let target_keys = derive_resolution_keys_with_context(
2566            project,
2567            None,
2568            target.graph(),
2569            ResolutionProjectionContext::default().with_source_semantics(target.semantics()),
2570        )?;
2571        require(
2572            keys_intersect(
2573                relation_keys(caller.graph(), &caller_keys, "named"),
2574                symbol_keys(target.graph(), &target_keys, "run"),
2575            ),
2576            "type-only slot suppressed runtime export",
2577        )?;
2578        require(
2579            symbol_keys(target.graph(), &target_keys, "Item").is_empty(),
2580            "type-only export became callable",
2581        )?;
2582        Ok(())
2583    }
2584
2585    #[test]
2586    fn source_default_slots_target_named_declarations_without_named_export_guessing()
2587    -> Result<(), Box<dyn Error>> {
2588        let project = ProjectInstanceId::from_bytes([81; 16])?;
2589        let control = projectatlas_core::IndexWorkControl::new(
2590            projectatlas_core::IndexCancellation::new(),
2591            None,
2592        );
2593        for (language, extension) in [("javascript", "js"), ("typescript", "ts"), ("tsx", "tsx")] {
2594            let caller = crate::extract_symbol_graph_with_semantics_controlled(
2595                &format!("src/caller.{extension}"),
2596                Some(language),
2597                "import Entry, { secondary as named } from './target'; function caller() { Entry(); named(); }",
2598                &control,
2599            )?;
2600            let caller_keys = derive_resolution_keys_with_context(
2601                project,
2602                None,
2603                caller.graph(),
2604                ResolutionProjectionContext::default().with_source_semantics(caller.semantics()),
2605            )?;
2606            let leaked = crate::extract_symbol_graph_with_semantics_controlled(
2607                &format!("src/leaked.{extension}"),
2608                Some(language),
2609                "import { Actual as leaked } from './target'; function caller() { leaked(); }",
2610                &control,
2611            )?;
2612            let leaked_keys = derive_resolution_keys_with_context(
2613                project,
2614                None,
2615                leaked.graph(),
2616                ResolutionProjectionContext::default().with_source_semantics(leaked.semantics()),
2617            )?;
2618            for declaration in ["function Actual() {}", "class Actual {}"] {
2619                let target = crate::extract_symbol_graph_with_semantics_controlled(
2620                    &format!("src/target.{extension}"),
2621                    Some(language),
2622                    &format!("export default {declaration}\nexport function secondary() {{}}"),
2623                    &control,
2624                )?;
2625                let target_keys = derive_resolution_keys_with_context(
2626                    project,
2627                    None,
2628                    target.graph(),
2629                    ResolutionProjectionContext::default()
2630                        .with_source_semantics(target.semantics()),
2631                )?;
2632                let actual = symbol_keys(target.graph(), &target_keys, "Actual");
2633                require(
2634                    keys_intersect(relation_keys(caller.graph(), &caller_keys, "Entry"), actual),
2635                    "default slot did not identify actual declaration",
2636                )?;
2637                require(
2638                    !keys_intersect(
2639                        relation_keys(leaked.graph(), &leaked_keys, "leaked"),
2640                        actual,
2641                    ),
2642                    "default declaration fabricated a named export",
2643                )?;
2644                require(
2645                    keys_intersect(
2646                        relation_keys(caller.graph(), &caller_keys, "named"),
2647                        symbol_keys(target.graph(), &target_keys, "secondary"),
2648                    ),
2649                    "default slot lost named sibling",
2650                )?;
2651            }
2652        }
2653        Ok(())
2654    }
2655
2656    #[test]
2657    fn default_export_roles_survive_hosts_and_never_narrow_coalesced_declarations()
2658    -> Result<(), Box<dyn Error>> {
2659        let project = ProjectInstanceId::from_bytes([82; 16])?;
2660        let control = projectatlas_core::IndexWorkControl::new(
2661            projectatlas_core::IndexCancellation::new(),
2662            None,
2663        );
2664        let caller = crate::extract_symbol_graph_with_semantics_controlled(
2665            "src/caller.js",
2666            Some("javascript"),
2667            "import Entry, { Actual as leaked } from './target'; function caller() { Entry(); leaked(); }",
2668            &control,
2669        )?;
2670        let caller_keys = derive_resolution_keys_with_context(
2671            project,
2672            None,
2673            caller.graph(),
2674            ResolutionProjectionContext::default().with_source_semantics(caller.semantics()),
2675        )?;
2676        for (language, attributes, admits_default) in [
2677            ("vue", "", true),
2678            ("vue", "setup", false),
2679            ("svelte", "module", false),
2680            ("svelte", "", false),
2681            ("html", "type=module", true),
2682            ("html", "", false),
2683        ] {
2684            for (source, valid) in [
2685                ("export default function Actual() {}", true),
2686                (
2687                    "export default function Actual() {} export default function Actual() {}",
2688                    false,
2689                ),
2690            ] {
2691                let target = crate::extract_symbol_graph_with_semantics_controlled(
2692                    &format!("src/target.{language}"),
2693                    Some(language),
2694                    &format!("<script {attributes}>{source}</script>"),
2695                    &control,
2696                )?;
2697                let keys = derive_resolution_keys_with_context(
2698                    project,
2699                    None,
2700                    target.graph(),
2701                    ResolutionProjectionContext::default()
2702                        .with_source_semantics(target.semantics()),
2703                )?;
2704                let actual = symbol_keys(target.graph(), &keys, "Actual");
2705                require(
2706                    keys_intersect(relation_keys(caller.graph(), &caller_keys, "Entry"), actual)
2707                        == (valid && admits_default),
2708                    "host default target correspondence changed",
2709                )?;
2710                require(
2711                    !keys_intersect(
2712                        relation_keys(caller.graph(), &caller_keys, "leaked"),
2713                        actual,
2714                    ),
2715                    "host coalescing fabricated the default declaration as a named export",
2716                )?;
2717                require(
2718                    !keys.source_keys().is_empty(),
2719                    "default ambiguity discarded admitted module evidence",
2720                )?;
2721            }
2722        }
2723        Ok(())
2724    }
2725
2726    #[test]
2727    fn component_export_targets_require_unambiguous_script_roles() -> Result<(), Box<dyn Error>> {
2728        let project = project_id(90)?;
2729        let control = projectatlas_core::IndexWorkControl::new(
2730            projectatlas_core::IndexCancellation::new(),
2731            None,
2732        );
2733        let caller = crate::extract_symbol_graph_with_semantics_controlled(
2734            "caller.js",
2735            Some("javascript"),
2736            "import {run} from './target'; function caller(){run();}",
2737            &control,
2738        )?;
2739        let caller_keys = derive_resolution_keys_with_context(
2740            project,
2741            None,
2742            caller.graph(),
2743            ResolutionProjectionContext::default().with_source_semantics(caller.semantics()),
2744        )?;
2745        let verify = |language: &str, source: &str, valid: bool| -> Result<(), Box<dyn Error>> {
2746            let target = crate::extract_symbol_graph_with_semantics_controlled(
2747                &format!("target.{language}"),
2748                Some(language),
2749                source,
2750                &control,
2751            )?;
2752            let keys = derive_resolution_keys_with_context(
2753                project,
2754                None,
2755                target.graph(),
2756                ResolutionProjectionContext::default().with_source_semantics(target.semantics()),
2757            )?;
2758            require(
2759                keys_intersect(
2760                    relation_keys(caller.graph(), &caller_keys, "run"),
2761                    symbol_keys(target.graph(), &keys, "run"),
2762                ) == valid,
2763                "host role changed its exported target authority",
2764            )
2765        };
2766        for (language, attributes, valid) in [
2767            ("svelte", "context=instance context=module", false),
2768            ("svelte", "context=module context=module", false),
2769            ("svelte", "module module", false),
2770            ("svelte", "module context=instance", false),
2771            ("svelte", "module='false'", false),
2772            ("vue", "setup setup", false),
2773            ("vue", "", true),
2774            ("svelte", "module", true),
2775            ("svelte", "context=module", true),
2776            ("svelte", "module context=module", true),
2777        ] {
2778            verify(
2779                language,
2780                &format!("<script {attributes}>export function run(){{}}</script>"),
2781                valid,
2782            )?;
2783        }
2784        for (language, source) in [
2785            (
2786                "vue",
2787                "<script>export function run(){}</script><script>function other(){}</script>",
2788            ),
2789            (
2790                "svelte",
2791                "<script module>export function run(){}</script><script module>function other(){}</script>",
2792            ),
2793        ] {
2794            verify(language, source, false)?;
2795        }
2796
2797        Ok(())
2798    }
2799
2800    #[test]
2801    fn default_expressions_and_anonymous_exports_do_not_select_a_named_sibling()
2802    -> Result<(), Box<dyn Error>> {
2803        let project = ProjectInstanceId::from_bytes([83; 16])?;
2804        let control = projectatlas_core::IndexWorkControl::new(
2805            projectatlas_core::IndexCancellation::new(),
2806            None,
2807        );
2808        let caller = crate::extract_symbol_graph_with_semantics_controlled(
2809            "src/caller.js",
2810            Some("javascript"),
2811            "import Entry from './target'; function caller() { Entry(); }",
2812            &control,
2813        )?;
2814        let caller_keys = derive_resolution_keys_with_context(
2815            project,
2816            None,
2817            caller.graph(),
2818            ResolutionProjectionContext::default().with_source_semantics(caller.semantics()),
2819        )?;
2820        for source in [
2821            "export function Only() {} export default Only;",
2822            "export function Only() {} export default function() {}",
2823            "export function Only() {} export default { run() {} };",
2824        ] {
2825            let target = crate::extract_symbol_graph_with_semantics_controlled(
2826                "src/target.js",
2827                Some("javascript"),
2828                source,
2829                &control,
2830            )?;
2831            let keys = derive_resolution_keys_with_context(
2832                project,
2833                None,
2834                target.graph(),
2835                ResolutionProjectionContext::default().with_source_semantics(target.semantics()),
2836            )?;
2837            for symbol in keys.symbol_keys() {
2838                require(
2839                    !keys_intersect(
2840                        relation_keys(caller.graph(), &caller_keys, "Entry"),
2841                        symbol.keys(),
2842                    ),
2843                    "unsupported default expression guessed an unrelated declaration",
2844                )?;
2845            }
2846            require(
2847                !keys.source_keys().is_empty(),
2848                "unsupported default lost module dependency evidence",
2849            )?;
2850        }
2851        Ok(())
2852    }
2853
2854    #[test]
2855    fn contract_rejection_flags_admit_actual_bool_vector_storage() -> Result<(), Box<dyn Error>> {
2856        let counts = [19_usize, 23, 29];
2857        let header = std::mem::size_of::<super::ContractFailures>() as u64;
2858        let flags = (counts.iter().sum::<usize>() * std::mem::size_of::<bool>()) as u64;
2859        let exact = header + flags;
2860        let admitted = super::ContractFailures::new(
2861            counts[0],
2862            counts[1],
2863            counts[2],
2864            super::ProjectionBudget::new(Some(exact)),
2865        )?;
2866        let actual = header
2867            + ([
2868                admitted.symbol_seen.capacity(),
2869                admitted.relation_seen.capacity(),
2870                admitted.export_seen.capacity(),
2871            ]
2872            .iter()
2873            .sum::<usize>()
2874                * std::mem::size_of::<bool>()) as u64;
2875        require(
2876            actual <= exact && admitted.budget.used >= actual,
2877            "dense rejection flags were charged as packed bits instead of owned bool bytes",
2878        )?;
2879        require(
2880            matches!(
2881                super::ContractFailures::new(
2882                    counts[0],
2883                    counts[1],
2884                    counts[2],
2885                    super::ProjectionBudget::new(Some(exact - 1))
2886                ),
2887                Err(ResolutionProjectionError::MemoryLimit { .. })
2888            ),
2889            "flag vectors were allocated past the remaining byte allowance",
2890        )?;
2891        super::ContractFailures::new(
2892            counts[0],
2893            counts[1],
2894            counts[2],
2895            super::ProjectionBudget::new(None),
2896        )?;
2897        Ok(())
2898    }
2899
2900    #[test]
2901    fn semantic_resolution_contract_digest_is_bounded_and_deterministic() {
2902        const PRE_ARROW_CONTEXT_DIGEST: &str =
2903            "66cab1855f1689308cdc5bb858a63e8d3ed42e825b073d62cada7347383731a7";
2904        const PRE_SOURCE_CONTEXT_DIGEST: &str =
2905            "784435da80445735c78b112bebab6352f74c660d550399a9617dae87d2df0424";
2906        const PRE_GENERATOR_DECLARATION_DIGEST: &str =
2907            "4542cd9a22a1df432d3a3d212f2ee5b6c52067411c3f54ca77221566a8f2ab7d";
2908        const PRE_MODULE_CALLBACK_DIGEST: &str =
2909            "487625adf2f9ec76f98034d4ef5667e707960b6b8afd280b213021cb64a0f10f";
2910        let first = semantic_resolution_contract_digest();
2911        assert_eq!(SEMANTIC_RESOLUTION_CONTRACT_VERSION, 23);
2912        assert_eq!(first.len(), 64);
2913        assert!(first.bytes().all(|byte| byte.is_ascii_hexdigit()));
2914        assert_eq!(first, semantic_resolution_contract_digest());
2915        assert_ne!(first, PRE_MODULE_CALLBACK_DIGEST);
2916        assert_ne!(first, PRE_GENERATOR_DECLARATION_DIGEST);
2917        assert_ne!(first, PRE_SOURCE_CONTEXT_DIGEST);
2918        assert_ne!(first, PRE_ARROW_CONTEXT_DIGEST);
2919    }
2920
2921    #[test]
2922    fn semantic_resolution_contract_tracks_identifier_data_version() {
2923        let before = super::semantic_resolution_contract_digest_for_identifier_profile((17, 0, 0));
2924        let after = super::semantic_resolution_contract_digest_for_identifier_profile((18, 0, 0));
2925        assert_ne!(before, after);
2926        assert_eq!(
2927            semantic_resolution_contract_digest(),
2928            super::semantic_resolution_contract_digest_for_identifier_profile(
2929                unicode_ident::UNICODE_VERSION
2930            )
2931        );
2932    }
2933
2934    #[test]
2935    fn parses_language_import_aliases_without_display_statement_identity() {
2936        assert_eq!(
2937            parse_import_references("use crate::worker::{run as execute, stop};"),
2938            vec![
2939                reference(ImportSyntax::Rust, "crate::worker", Some("run"), "execute"),
2940                reference(ImportSyntax::Rust, "crate::worker", Some("stop"), "stop"),
2941            ]
2942        );
2943        assert_eq!(
2944            parse_import_references("use crate::worker::run_function_alias as run_rust_function;"),
2945            vec![reference(
2946                ImportSyntax::Rust,
2947                "crate::worker",
2948                Some("run_function_alias"),
2949                "run_rust_function"
2950            ),]
2951        );
2952        assert_eq!(
2953            parse_import_references("import * as reader from './reader';"),
2954            vec![reference(
2955                ImportSyntax::EcmaScript,
2956                "./reader",
2957                None,
2958                "reader",
2959            )]
2960        );
2961        assert_eq!(
2962            parse_import_references("import { runAlias as runLocal } from './worker';"),
2963            vec![reference(
2964                ImportSyntax::EcmaScript,
2965                "./worker",
2966                Some("runAlias"),
2967                "runLocal",
2968            )]
2969        );
2970        assert_eq!(
2971            parse_import_references("from package.reader import read as load, close"),
2972            vec![
2973                reference(
2974                    ImportSyntax::Python,
2975                    "package.reader",
2976                    Some("close"),
2977                    "close"
2978                ),
2979                reference(ImportSyntax::Python, "package.reader", Some("read"), "load"),
2980            ]
2981        );
2982        assert_eq!(
2983            parse_import_references("from package.worker import run_alias as execute"),
2984            vec![reference(
2985                ImportSyntax::Python,
2986                "package.worker",
2987                Some("run_alias"),
2988                "execute",
2989            )]
2990        );
2991        assert!(parse_import_references("import { broken from './reader'").is_empty());
2992        assert!(parse_import_references("use crate::worker::run as ;").is_empty());
2993        assert!(parse_import_references("import { read } from module './reader'").is_empty());
2994        for malformed in [
2995            "use crate::worker::{run, nested::{start, stop}};",
2996            "use crate::worker::{run, broken as };",
2997            "import defaultValue, { read } unexpected from './reader';",
2998            "import { read, nested { broken } } from './reader';",
2999            "import { read as load as fetch } from './reader';",
3000            "from package.reader import read, broken as ",
3001            "import package.reader as reader as other",
3002        ] {
3003            assert!(
3004                parse_import_references(malformed).is_empty(),
3005                "unsupported syntax fabricated references: {malformed}"
3006            );
3007        }
3008    }
3009
3010    #[test]
3011    fn normalizes_flat_parenthesized_python_imports_without_guessing() {
3012        let expected = vec![
3013            reference(
3014                ImportSyntax::Python,
3015                "package.reader",
3016                Some("close"),
3017                "close",
3018            ),
3019            reference(ImportSyntax::Python, "package.reader", Some("read"), "load"),
3020        ];
3021        for statement in [
3022            "from package.reader import (read as load, close)",
3023            "from package.reader import (read as load, close,)",
3024            "from package.reader import (\n    read as load,\n    close,\n)",
3025        ] {
3026            assert_eq!(parse_import_references(statement), expected, "{statement}");
3027        }
3028        assert_eq!(
3029            parse_import_references("from .reader import (read,)"),
3030            vec![reference(
3031                ImportSyntax::Python,
3032                ".reader",
3033                Some("read"),
3034                "read"
3035            )]
3036        );
3037        assert_eq!(
3038            parse_import_references("from match.case import (type as match, case)"),
3039            vec![
3040                reference(ImportSyntax::Python, "match.case", Some("case"), "case"),
3041                reference(ImportSyntax::Python, "match.case", Some("type"), "match"),
3042            ]
3043        );
3044        for statement in [
3045            "from package.reader import ()",
3046            "from package.reader import (read",
3047            "from package.reader import read)",
3048            "from package.reader import ((read))",
3049            "from package.reader import (read),",
3050            "from package.reader import (read,,close)",
3051            "from package.reader import (,read)",
3052            "from package.reader import (read,,)",
3053            "from package.reader import (read as)",
3054            "from package.reader import (read as load as other)",
3055            "from package.reader import (read.attr)",
3056            "from package.reader import (read as load.attr)",
3057            "from package.reader import (class)",
3058            "from package.reader import (read as None)",
3059            "from package.class import (read)",
3060            "from package.reader import (read())",
3061            "from package.reader import ([read])",
3062            "from package.reader import ({read})",
3063            "from package.reader import (*)",
3064            "from package.reader import (read, # note\n close)",
3065        ] {
3066            assert!(parse_import_references(statement).is_empty(), "{statement}");
3067        }
3068    }
3069
3070    #[test]
3071    fn preserves_unicode_python_import_names_and_aliases() {
3072        let name = "a\u{301}";
3073        let alias = "r\u{301}";
3074        let module = "helper\u{301}.reader";
3075        assert_eq!(
3076            parse_import_references(&format!("from {module} import (run)")),
3077            vec![reference(ImportSyntax::Python, module, Some("run"), "run")]
3078        );
3079        for list in [
3080            format!("{name}, run"),
3081            format!("({name}, run,)"),
3082            format!("(\n{name},\nrun,\n)"),
3083        ] {
3084            assert_eq!(
3085                parse_import_references(&format!("from helper import {list}")),
3086                vec![
3087                    reference(ImportSyntax::Python, "helper", Some(name), name),
3088                    reference(ImportSyntax::Python, "helper", Some("run"), "run"),
3089                ]
3090            );
3091        }
3092        for list in [
3093            format!("run as {alias}, {name}"),
3094            format!("(run as {alias}, {name},)"),
3095        ] {
3096            assert_eq!(
3097                parse_import_references(&format!("from helper import {list}")),
3098                vec![
3099                    reference(ImportSyntax::Python, "helper", Some(name), name),
3100                    reference(ImportSyntax::Python, "helper", Some("run"), alias),
3101                ]
3102            );
3103        }
3104        for invalid in ["\u{301}a", "a\u{2022}", "a\u{b2}", "a\u{200c}", "a\u{200d}"] {
3105            assert!(
3106                parse_import_references(&format!("from helper import ({invalid}, run)")).is_empty()
3107            );
3108            assert!(
3109                parse_import_references(&format!("from helper import (run as {invalid})"))
3110                    .is_empty()
3111            );
3112            assert!(
3113                parse_import_references(&format!("from {invalid}.reader import run")).is_empty()
3114            );
3115        }
3116    }
3117
3118    #[test]
3119    fn resolves_relative_imports_without_platform_path_rules() {
3120        assert_eq!(
3121            resolve_relative_import_path("src/features/main.ts", "../shared/reader.ts"),
3122            Some("src/shared/reader".to_string())
3123        );
3124        assert_eq!(
3125            resolve_relative_import_path("src/main.ts", "../../outside"),
3126            None
3127        );
3128        assert_eq!(resolve_relative_import_path("src/main.ts", "reader"), None);
3129        assert_eq!(
3130            resolve_relative_import_path("src/foo.bar/main.ts", "./reader"),
3131            Some("src/foo.bar/reader".to_string())
3132        );
3133    }
3134
3135    #[test]
3136    fn explicit_ecmascript_suffixes_do_not_merge_module_or_callable_targets()
3137    -> Result<(), Box<dyn Error>> {
3138        let project = project_id(94)?;
3139        let configured = ConfiguredModuleResolution::new(vec![EcmaScriptModuleConfig::new(
3140            "tsconfig.json",
3141            EcmaScriptConfigKind::TypeScript,
3142            Some("src".to_string()),
3143            vec![EcmaScriptPathMapping::new(
3144                "@/*",
3145                vec!["src/*".to_string()],
3146            )?],
3147        )?])?;
3148        let control = projectatlas_core::IndexWorkControl::new(
3149            projectatlas_core::IndexCancellation::new(),
3150            None,
3151        );
3152        let targets = [
3153            ("js", "javascript", "export function run(){}"),
3154            ("jsx", "javascript", "export function run(){}"),
3155            ("ts", "typescript", "export function run(){}"),
3156            ("tsx", "tsx", "export function run(){}"),
3157            ("d.ts", "typescript", "export declare function run(): void;"),
3158            ("mjs", "javascript", "export function run(){}"),
3159            ("cjs", "javascript", "function run(){}"),
3160            ("vue", "vue", "<script>export function run(){}</script>"),
3161            (
3162                "svelte",
3163                "svelte",
3164                "<script module>export function run(){}</script>",
3165            ),
3166            (
3167                "html",
3168                "html",
3169                "<script type='module'>export function run(){}</script>",
3170            ),
3171            (
3172                "htm",
3173                "html",
3174                "<script type='module'>export function run(){}</script>",
3175            ),
3176            ("js.ts", "typescript", "export function run(){}"),
3177            ("json.ts", "typescript", "export function run(){}"),
3178            ("widget", "typescript", "export function run(){}"),
3179        ];
3180        for (specifier, allowed) in [
3181            ("target.svelte", &["svelte"][..]),
3182            ("target.html", &["html"][..]),
3183            ("target.htm", &["htm"][..]),
3184            ("target.vue", &["vue"][..]),
3185            ("target.mjs", &["mjs"][..]),
3186            ("target.cjs", &["cjs"][..]),
3187            ("target.ts", &["ts"][..]),
3188            ("target.tsx", &["tsx"][..]),
3189            ("target.d.ts", &["d.ts"][..]),
3190            ("target.js", &["js", "jsx", "ts", "tsx", "d.ts"][..]),
3191            ("target.jsx", &["jsx", "tsx", "d.ts"][..]),
3192            ("target.json", &[][..]),
3193            ("target.widget", &["widget"][..]),
3194            ("target/index.js", &[][..]),
3195            ("../target", &[][..]),
3196        ] {
3197            for prefix in ["./", "@/", ""] {
3198                let source = format!(
3199                    "import {{run}} from '{prefix}{specifier}'; function caller(){{run();}}"
3200                );
3201                let caller = crate::extract_symbol_graph_with_semantics_controlled(
3202                    "src/caller.ts",
3203                    Some("typescript"),
3204                    &source,
3205                    &control,
3206                )?;
3207                let caller_keys = derive_resolution_keys_with_context(
3208                    project,
3209                    Some("app"),
3210                    caller.graph(),
3211                    ResolutionProjectionContext::with_configured_modules(&configured)
3212                        .with_source_semantics(caller.semantics()),
3213                )?;
3214                let dependencies =
3215                    relation_keys_of_kind(caller.graph(), &caller_keys, RelationKind::Imports);
3216                for (suffix, language, source) in targets {
3217                    let path = format!("src/target.{suffix}");
3218                    let target = crate::extract_symbol_graph_with_semantics_controlled(
3219                        &path,
3220                        Some(language),
3221                        source,
3222                        &control,
3223                    )?;
3224                    let target_keys = derive_resolution_keys_with_context(
3225                        project,
3226                        Some("app"),
3227                        target.graph(),
3228                        ResolutionProjectionContext::default()
3229                            .with_source_semantics(target.semantics()),
3230                    )?;
3231                    let expected = allowed.contains(&suffix);
3232                    require(
3233                        dependencies
3234                            .iter()
3235                            .any(|keys| keys_intersect(keys, target_keys.source_keys()))
3236                            == expected,
3237                        &format!("module: {prefix}{specifier} -> {path}"),
3238                    )?;
3239                    let calls = relation_keys(caller.graph(), &caller_keys, "run");
3240                    let exports = symbol_keys(target.graph(), &target_keys, "run");
3241                    require(
3242                        keys_intersect(calls, exports)
3243                            == (expected && !matches!(suffix, "cjs" | "d.ts")),
3244                        &format!("call: {prefix}{specifier} -> {path}"),
3245                    )?;
3246                }
3247            }
3248        }
3249        Ok(())
3250    }
3251
3252    #[test]
3253    fn ecmascript_module_paths_preserve_entry_aliases_and_literal_file_names()
3254    -> Result<(), Box<dyn Error>> {
3255        let project = project_id(95)?;
3256        for (caller_path, module, target_path, expected) in [
3257            ("src/main.ts", "./tools", "src/tools/index.ts", true),
3258            (
3259                "src/main.ts",
3260                "./tools/index.ts",
3261                "src/tools/index.ts",
3262                true,
3263            ),
3264            ("src/main.ts", "./tools.js", "src/tools/index.ts", false),
3265            (
3266                "src/foo.bar/main.ts",
3267                "./tools",
3268                "src/foo.bar/tools.ts",
3269                true,
3270            ),
3271            ("src/main.ts", "./tools", "packages/other/tools.ts", false),
3272            ("main.ts", "./target  name.js", "target  name.js", true),
3273            ("main.ts", "./target::name.js", "target::name.js", true),
3274        ] {
3275            let caller = extract_symbol_graph(
3276                caller_path,
3277                Some("typescript"),
3278                &format!("import {{run}} from '{module}'; function caller(){{run();}}"),
3279            );
3280            let target =
3281                extract_symbol_graph(target_path, Some("typescript"), "export function run(){}");
3282            let caller_keys = derive_resolution_keys(project, None, &caller)?;
3283            let target_keys = derive_resolution_keys(project, None, &target)?;
3284            require(
3285                relation_keys_of_kind(&caller, &caller_keys, RelationKind::Imports)
3286                    .iter()
3287                    .any(|keys| keys_intersect(keys, target_keys.source_keys()))
3288                    == expected,
3289                &format!("module: {caller_path}: {module} -> {target_path}"),
3290            )?;
3291            require(
3292                keys_intersect(
3293                    relation_keys(&caller, &caller_keys, "run"),
3294                    symbol_keys(&target, &target_keys, "run"),
3295                ) == expected,
3296                &format!("call: {caller_path}: {module} -> {target_path}"),
3297            )?;
3298        }
3299        Ok(())
3300    }
3301
3302    #[test]
3303    fn real_language_graphs_share_alias_qualified_keys() -> Result<(), Box<dyn Error>> {
3304        let project = project_id(1)?;
3305
3306        let rust_target = extract_symbol_graph("src/worker.rs", Some("rust"), "pub fn run() {}\n");
3307        let rust_caller = extract_symbol_graph(
3308            "src/main.rs",
3309            Some("rust"),
3310            "use crate::worker::run as execute;\nfn main() { execute(); }\n",
3311        );
3312        assert_alias_resolution(
3313            project,
3314            Some("atlas"),
3315            &rust_target,
3316            "run",
3317            &rust_caller,
3318            &["execute"],
3319        )?;
3320        let rust_module_target = extract_symbol_graph(
3321            "src/config.rs",
3322            Some("rust"),
3323            "pub fn load_timeout_millis() -> u64 { 250 }\n",
3324        );
3325        let rust_module_caller = extract_symbol_graph(
3326            "src/handler.rs",
3327            Some("rust"),
3328            "use crate::config;\npub fn health_response() { config::load_timeout_millis(); }\n",
3329        );
3330        assert_alias_resolution(
3331            project,
3332            Some("atlas"),
3333            &rust_module_target,
3334            "load_timeout_millis",
3335            &rust_module_caller,
3336            &["config::load_timeout_millis"],
3337        )?;
3338        let rust_callback_caller = extract_symbol_graph(
3339            "src/router.rs",
3340            Some("rust"),
3341            "use crate::handler;\nfn dispatch(path: &str) -> Option<()> { (path == \"/health\").then(handler::health_response) }\n",
3342        );
3343        assert_alias_resolution(
3344            project,
3345            Some("atlas"),
3346            &rust_module_caller,
3347            "health_response",
3348            &rust_callback_caller,
3349            &["handler::health_response"],
3350        )?;
3351        let user_defined_then = extract_symbol_graph(
3352            "src/scheduler.rs",
3353            Some("rust"),
3354            "use crate::handler;\nstruct Scheduler;\nimpl Scheduler { fn then(self, callback: fn()) { let _ = callback; } }\nfn schedule(scheduler: Scheduler) { scheduler.then(handler::health_response); }\n",
3355        );
3356        if user_defined_then.relations.iter().any(|relation| {
3357            relation.kind == RelationKind::Calls
3358                && relation.target_name == "handler::health_response"
3359        }) {
3360            return Err(io::Error::other(
3361                "a user-defined then method fabricated a callback call edge",
3362            )
3363            .into());
3364        }
3365
3366        let typescript_target = extract_symbol_graph(
3367            "src/shared/reader.ts",
3368            Some("typescript"),
3369            "export function read() {}\n",
3370        );
3371        let typescript_caller = extract_symbol_graph(
3372            "src/features/main.ts",
3373            Some("typescript"),
3374            "import { read as load } from '../shared/reader';\nimport * as reader from '../shared/reader';\nexport function main() { load(); reader.read(); }\n",
3375        );
3376        assert_alias_resolution(
3377            project,
3378            Some("web"),
3379            &typescript_target,
3380            "read",
3381            &typescript_caller,
3382            &["load", "reader.read"],
3383        )?;
3384
3385        let dotted_directory_target = extract_symbol_graph(
3386            "src/foo.bar/reader.ts",
3387            Some("typescript"),
3388            "export function read() {}\n",
3389        );
3390        let dotted_directory_caller = extract_symbol_graph(
3391            "src/foo.bar/main.ts",
3392            Some("typescript"),
3393            "import { read } from './reader';\nexport function main() { read(); }\n",
3394        );
3395        assert_alias_resolution(
3396            project,
3397            Some("web"),
3398            &dotted_directory_target,
3399            "read",
3400            &dotted_directory_caller,
3401            &["read"],
3402        )?;
3403
3404        let python_target = extract_symbol_graph(
3405            "src/package/reader.py",
3406            Some("python"),
3407            "def read():\n    pass\n",
3408        );
3409        let python_caller = extract_symbol_graph(
3410            "src/main.py",
3411            Some("python"),
3412            "import package.reader as reader\nreader.read()\n",
3413        );
3414        assert_alias_resolution(
3415            project,
3416            Some("python-app"),
3417            &python_target,
3418            "read",
3419            &python_caller,
3420            &["reader.read"],
3421        )?;
3422        Ok(())
3423    }
3424
3425    #[test]
3426    fn configured_aliases_share_file_and_declaration_keys_across_ecmascript_hosts()
3427    -> Result<(), Box<dyn Error>> {
3428        let project = project_id(21)?;
3429        let configured = ConfiguredModuleResolution::new(vec![
3430            EcmaScriptModuleConfig::new(
3431                "tsconfig.json",
3432                EcmaScriptConfigKind::TypeScript,
3433                Some("src".to_string()),
3434                vec![
3435                    EcmaScriptPathMapping::new("@/*", vec!["src/*".to_string()])?,
3436                    EcmaScriptPathMapping::new("@index/*", vec!["src/*/index.ts".to_string()])?,
3437                ],
3438            )?,
3439            EcmaScriptModuleConfig::new(
3440                "jsconfig.json",
3441                EcmaScriptConfigKind::JavaScript,
3442                Some("src".to_string()),
3443                vec![EcmaScriptPathMapping::new(
3444                    "@/*",
3445                    vec!["src/*".to_string()],
3446                )?],
3447            )?,
3448        ])?;
3449        let context = ResolutionProjectionContext::with_configured_modules(&configured);
3450
3451        for (target_path, language, target_source, caller_path, caller_source) in [
3452            (
3453                "src/controller.ts",
3454                "typescript",
3455                "export function useController(): string { return 'ok'; }\n",
3456                "src/page.ts",
3457                "import { useController } from '@/controller';\nexport const value = useController();\n",
3458            ),
3459            (
3460                "src/controller.ts",
3461                "typescript",
3462                "export function useController(): string { return 'ok'; }\n",
3463                "src/page.tsx",
3464                "import { useController } from '@/controller';\nexport function Page() { useController(); return <div />; }\n",
3465            ),
3466            (
3467                "src/controller.js",
3468                "javascript",
3469                "export function useController() { return 'ok'; }\n",
3470                "src/page.jsx",
3471                "import { useController } from '@/controller';\nexport function Page() { useController(); return <div />; }\n",
3472            ),
3473            (
3474                "src/controller.ts",
3475                "typescript",
3476                "export function useController(): string { return 'ok'; }\n",
3477                "src/Page.vue",
3478                "<script setup lang=\"ts\">\nimport { useController } from '@/controller';\nconst value = useController();\n</script>\n<template><div>{{ value }}</div></template>\n",
3479            ),
3480        ] {
3481            let target = extract_symbol_graph(target_path, Some(language), target_source);
3482            let caller_extension = std::path::Path::new(caller_path)
3483                .extension()
3484                .and_then(std::ffi::OsStr::to_str);
3485            let caller_language = match caller_extension {
3486                Some(extension) if extension.eq_ignore_ascii_case("vue") => "vue",
3487                Some(extension)
3488                    if extension.eq_ignore_ascii_case("js")
3489                        || extension.eq_ignore_ascii_case("jsx") =>
3490                {
3491                    "javascript"
3492                }
3493                Some(extension) if extension.eq_ignore_ascii_case("tsx") => "tsx",
3494                _ => "typescript",
3495            };
3496            let caller = extract_symbol_graph(caller_path, Some(caller_language), caller_source);
3497            let target_projection = derive_resolution_keys(project, Some("shared"), &target)?;
3498            let caller_projection =
3499                derive_resolution_keys_with_context(project, Some("app"), &caller, context)?;
3500            let imports = relation_keys_of_kind(&caller, &caller_projection, RelationKind::Imports);
3501            require(
3502                imports
3503                    .iter()
3504                    .any(|keys| keys_intersect(keys, target_projection.source_keys())),
3505                "configured import did not share the target file key",
3506            )?;
3507            require(
3508                keys_intersect(
3509                    relation_keys(&caller, &caller_projection, "useController"),
3510                    symbol_keys(&target, &target_projection, "useController"),
3511                ),
3512                "configured call did not share the target declaration key",
3513            )?;
3514        }
3515
3516        let index_target = extract_symbol_graph(
3517            "src/tools/index.ts",
3518            Some("typescript"),
3519            "export function useTool() { return 'ok'; }\n",
3520        );
3521        let index_caller = extract_symbol_graph(
3522            "src/index-page.ts",
3523            Some("typescript"),
3524            "import { useTool } from '@index/tools';\nexport const value = useTool();\n",
3525        );
3526        let index_target_projection = derive_resolution_keys(project, Some("app"), &index_target)?;
3527        let index_caller_projection =
3528            derive_resolution_keys_with_context(project, Some("app"), &index_caller, context)?;
3529        require(
3530            relation_keys_of_kind(
3531                &index_caller,
3532                &index_caller_projection,
3533                RelationKind::Imports,
3534            )
3535            .iter()
3536            .any(|keys| keys_intersect(keys, index_target_projection.source_keys())),
3537            "configured index target did not reuse package-entry source keys",
3538        )?;
3539        require(
3540            keys_intersect(
3541                relation_keys(&index_caller, &index_caller_projection, "useTool"),
3542                symbol_keys(&index_target, &index_target_projection, "useTool"),
3543            ),
3544            "configured index target did not reuse declaration keys",
3545        )?;
3546
3547        let extension_caller = extract_symbol_graph(
3548            "src/extension-page.ts",
3549            Some("typescript"),
3550            "import { useController } from '@/controller.ts';\nexport const value = useController();\n",
3551        );
3552        let extension_target = extract_symbol_graph(
3553            "src/controller.ts",
3554            Some("typescript"),
3555            "export function useController(): string { return 'ok'; }\n",
3556        );
3557        let extension_caller_projection =
3558            derive_resolution_keys_with_context(project, Some("app"), &extension_caller, context)?;
3559        let extension_target_projection =
3560            derive_resolution_keys(project, Some("app"), &extension_target)?;
3561        require(
3562            relation_keys_of_kind(
3563                &extension_caller,
3564                &extension_caller_projection,
3565                RelationKind::Imports,
3566            )
3567            .iter()
3568            .any(|keys| keys_intersect(keys, extension_target_projection.source_keys())),
3569            "configured extension target did not reuse extensionless source keys",
3570        )?;
3571
3572        let root_configured = ConfiguredModuleResolution::new(vec![EcmaScriptModuleConfig::new(
3573            "tsconfig.json",
3574            EcmaScriptConfigKind::TypeScript,
3575            Some(String::new()),
3576            Vec::new(),
3577        )?])?;
3578        let root_context = ResolutionProjectionContext::with_configured_modules(&root_configured);
3579        let root_target = extract_symbol_graph(
3580            "controller.ts",
3581            Some("typescript"),
3582            "export function useController() { return 'root'; }\n",
3583        );
3584        let nested_same_name = extract_symbol_graph(
3585            "packages/app/src/controller.ts",
3586            Some("typescript"),
3587            "export function useController() { return 'nested'; }\n",
3588        );
3589        let root_caller = extract_symbol_graph(
3590            "page.ts",
3591            Some("typescript"),
3592            "import { useController } from 'controller';\nexport const value = useController();\n",
3593        );
3594        let root_target_projection = derive_resolution_keys(project, Some("shared"), &root_target)?;
3595        let nested_projection = derive_resolution_keys(project, Some("nested"), &nested_same_name)?;
3596        let root_caller_projection =
3597            derive_resolution_keys_with_context(project, Some("app"), &root_caller, root_context)?;
3598        let configured_imports =
3599            relation_keys_of_kind(&root_caller, &root_caller_projection, RelationKind::Imports);
3600        require(
3601            configured_imports
3602                .iter()
3603                .any(|keys| keys_intersect(keys, root_target_projection.source_keys())),
3604            "root baseUrl import did not resolve its repository-exact target",
3605        )?;
3606        require(
3607            configured_imports
3608                .iter()
3609                .all(|keys| !keys_intersect(keys, nested_projection.source_keys())),
3610            "root baseUrl import also matched a nested basename alias",
3611        )?;
3612        let configured_call = relation_keys(&root_caller, &root_caller_projection, "useController");
3613        require(
3614            keys_intersect(
3615                configured_call,
3616                symbol_keys(&root_target, &root_target_projection, "useController"),
3617            ),
3618            "root baseUrl call did not resolve its repository-exact declaration",
3619        )?;
3620        require(
3621            !keys_intersect(
3622                configured_call,
3623                symbol_keys(&nested_same_name, &nested_projection, "useController"),
3624            ),
3625            "root baseUrl call also matched a nested basename alias",
3626        )?;
3627        Ok(())
3628    }
3629
3630    #[test]
3631    fn configured_import_scopes_are_expanded_once_per_source_graph() -> Result<(), Box<dyn Error>> {
3632        let configured = ConfiguredModuleResolution::new(vec![EcmaScriptModuleConfig::new(
3633            "tsconfig.json",
3634            EcmaScriptConfigKind::TypeScript,
3635            Some("src".to_string()),
3636            vec![EcmaScriptPathMapping::new(
3637                "@/*",
3638                vec!["src/*".to_string()],
3639            )?],
3640        )?])?;
3641        let graph = extract_symbol_graph(
3642            "src/page.ts",
3643            Some("typescript"),
3644            "import { useController } from '@/controller';\n\
3645             export const first = useController();\n\
3646             export const second = useController();\n",
3647        );
3648        let parsed_imports = graph
3649            .relations
3650            .iter()
3651            .map(|relation| {
3652                if relation.kind == RelationKind::Imports {
3653                    crate::semantic::parse_imports(
3654                        SemanticProviderOwner::EcmaScript,
3655                        &relation.target_name,
3656                    )
3657                } else {
3658                    Vec::new()
3659                }
3660            })
3661            .collect::<Vec<_>>();
3662        let configured_source = configured.for_source(&graph.path);
3663        let cache = build_import_scope_cache(
3664            SemanticProviderOwner::EcmaScript,
3665            &graph,
3666            &parsed_imports,
3667            Some(&configured_source),
3668            &mut super::ProjectionBudget::new(None),
3669        )?;
3670        let modules = cache
3671            .get("src/page.ts")
3672            .ok_or_else(|| io::Error::other("source graph omitted its import-scope cache"))?;
3673        if modules.len() != 1
3674            || modules.get("@/controller") != Some(&vec!["module:src/controller".into()])
3675        {
3676            return Err(io::Error::other(
3677                "repeated imported calls did not reuse one configured scope expansion",
3678            )
3679            .into());
3680        }
3681        Ok(())
3682    }
3683
3684    #[test]
3685    fn imports_target_modules_and_qualified_calls_target_members() -> Result<(), Box<dyn Error>> {
3686        let project = project_id(10)?;
3687        let target = extract_symbol_graph(
3688            "src/shared/reader.ts",
3689            Some("typescript"),
3690            "export function read() {}\nexport function close() {}\nexport function fetch() {}\nexport const client = { fetch() {} };\n",
3691        );
3692        let caller = extract_symbol_graph(
3693            "src/features/main.ts",
3694            Some("typescript"),
3695            "import { read, close, client } from '../shared/reader';\nexport function main() { read(); close(); client.fetch(); }\n",
3696        );
3697        let target_projection = derive_resolution_keys(project, Some("web"), &target)?;
3698        let caller_projection = derive_resolution_keys(project, Some("web"), &caller)?;
3699        let imports = relation_keys_of_kind(&caller, &caller_projection, RelationKind::Imports);
3700        require(
3701            imports.len() == 1,
3702            "one import emitted more than one module target",
3703        )?;
3704        require(
3705            imports[0]
3706                .iter()
3707                .all(|key| key.domain() == projectatlas_core::graph::ResolutionKeyDomain::Module),
3708            "named imports emitted declaration resolution targets",
3709        )?;
3710        require(
3711            keys_intersect(imports[0], target_projection.source_keys()),
3712            "named import did not target its source module",
3713        )?;
3714        for symbol in ["read", "close"] {
3715            require(
3716                !keys_intersect(imports[0], symbol_keys(&target, &target_projection, symbol)),
3717                "module import also targeted a named declaration",
3718            )?;
3719        }
3720        assert_alias_resolution(project, Some("web"), &target, "read", &caller, &["read"])?;
3721        assert_alias_resolution(project, Some("web"), &target, "close", &caller, &["close"])?;
3722        let qualified_call = relation_keys(&caller, &caller_projection, "client.fetch");
3723        let fetch_candidates = target_projection
3724            .symbol_keys()
3725            .iter()
3726            .filter(|entry| target.symbols[entry.symbol_index()].name == "fetch")
3727            .map(|entry| {
3728                (
3729                    target.symbols[entry.symbol_index()].parent.as_deref(),
3730                    entry.keys(),
3731                )
3732            })
3733            .collect::<Vec<_>>();
3734        require(
3735            fetch_candidates.len() == 2,
3736            "qualified-call fixture lost one fetch declaration",
3737        )?;
3738        require(
3739            fetch_candidates.iter().any(|(parent, keys)| {
3740                *parent == Some("client") && keys_intersect(qualified_call, keys)
3741            }),
3742            "qualified call did not target the imported parent member",
3743        )?;
3744        require(
3745            fetch_candidates
3746                .iter()
3747                .all(|(parent, keys)| parent.is_some() || !keys_intersect(qualified_call, keys)),
3748            "qualified call also targeted a same-module top-level declaration",
3749        )
3750    }
3751
3752    #[test]
3753    fn actual_complex_import_syntax_abstains_instead_of_fabricating_keys()
3754    -> Result<(), Box<dyn Error>> {
3755        let project = project_id(15)?;
3756        for (path, language, source) in [
3757            (
3758                "src/main.rs",
3759                "rust",
3760                "use crate::worker::{run, broken as };\nfn main() { run(); }\n",
3761            ),
3762            (
3763                "src/main.ts",
3764                "typescript",
3765                "import defaultValue, { read } unexpected from './reader';\ndefaultValue();\n",
3766            ),
3767        ] {
3768            let graph = extract_symbol_graph(path, Some(language), source);
3769            let projection = derive_resolution_keys(project, Some("app"), &graph)?;
3770            let imports = relation_keys_of_kind(&graph, &projection, RelationKind::Imports);
3771            require(
3772                !imports.is_empty(),
3773                "complex-import fixture did not reach provider normalization",
3774            )?;
3775            require(
3776                imports.iter().all(|keys| keys.is_empty()),
3777                "unsupported complex import syntax fabricated resolution keys",
3778            )?;
3779        }
3780        Ok(())
3781    }
3782
3783    #[test]
3784    fn syntax_trivia_and_literals_keep_dependency_and_aliased_call_keys()
3785    -> Result<(), Box<dyn Error>> {
3786        let project = project_id(15)?;
3787        for (language, target_path, target_source, caller_path, caller_source) in [
3788            (
3789                "rust",
3790                "src/worker.rs",
3791                "pub fn run() {}\npub fn stop() {}\n",
3792                "src/main.rs",
3793                "use crate::worker::{run as launch, /* separator */ stop};\nfn caller() { launch(); }\n",
3794            ),
3795            (
3796                "typescript",
3797                "src/worker.ts",
3798                "export function run() {}\nexport function stop() {}\n",
3799                "src/main.ts",
3800                "import { run as launch, /* separator */ stop } from './worker';\nexport function caller() { launch(); }\n",
3801            ),
3802            (
3803                "python",
3804                "package/worker.py",
3805                "def run():\n    pass\ndef stop():\n    pass\n",
3806                "main.py",
3807                "from package.worker import (run as launch, # separator\n stop)\ndef caller():\n    launch()\n",
3808            ),
3809            (
3810                "javascript",
3811                "src/dep  space.js",
3812                "export function run() {}\n",
3813                "src/main.js",
3814                "import { run as launch } from './dep  space.js';\nexport function caller() { launch(); }\n",
3815            ),
3816            (
3817                "javascript",
3818                "src/odd'name.js",
3819                "export function run() {}\n",
3820                "src/main.js",
3821                "import { run as launch } from './odd\\'name.js';\nexport function caller() { launch(); }\n",
3822            ),
3823            (
3824                "javascript",
3825                "src/🚀.js",
3826                "export function run() {}\n",
3827                "src/main.js",
3828                "import { run as launch } from './\\uD83D\\uDE80.js';\nexport function caller() { launch(); }\n",
3829            ),
3830            (
3831                "python",
3832                "package/worker.py",
3833                "def run():\n    pass\n",
3834                "main.py",
3835                "from package.worker import run \\\n    as launch\ndef caller():\n    launch()\n",
3836            ),
3837        ] {
3838            let target = extract_symbol_graph(target_path, Some(language), target_source);
3839            let caller = extract_symbol_graph(caller_path, Some(language), caller_source);
3840            let projection = derive_resolution_keys(project, Some("app"), &caller)?;
3841            require(
3842                relation_keys_of_kind(&caller, &projection, RelationKind::Imports)
3843                    .iter()
3844                    .any(|keys| !keys.is_empty()),
3845                "syntax-preserving import lost dependency keys",
3846            )?;
3847            assert_alias_resolution(project, Some("app"), &target, "run", &caller, &["launch"])?;
3848        }
3849        Ok(())
3850    }
3851
3852    #[test]
3853    fn rust_namespace_aliases_preserve_nested_member_ownership() -> Result<(), Box<dyn Error>> {
3854        let project = project_id(86)?;
3855        let control = projectatlas_core::IndexWorkControl::new(
3856            projectatlas_core::IndexCancellation::new(),
3857            None,
3858        );
3859        let target = crate::extract_symbol_graph_with_semantics_controlled(
3860            "src/fake.rs",
3861            Some("rust"),
3862            "pub fn read(_: &str) {}\npub mod fs { pub fn read(_: &str) {} }",
3863            &control,
3864        )?;
3865        let caller = crate::extract_symbol_graph_with_semantics_controlled(
3866            "src/main.rs",
3867            Some("rust"),
3868            "use crate::fake as std; fn root_call(std: u8) { std::read(\"x\"); } fn nested_call() { std::fs::read(\"x\"); }",
3869            &control,
3870        )?;
3871        let target_keys = derive_resolution_keys_with_context(
3872            project,
3873            Some("app"),
3874            target.graph(),
3875            ResolutionProjectionContext::default().with_source_semantics(target.semantics()),
3876        )?;
3877        let caller_keys = derive_resolution_keys_with_context(
3878            project,
3879            Some("app"),
3880            caller.graph(),
3881            ResolutionProjectionContext::default().with_source_semantics(caller.semantics()),
3882        )?;
3883        for (reference, parent) in [("std::read", None), ("std::fs::read", Some("fs"))] {
3884            let requests = relation_keys(caller.graph(), &caller_keys, reference);
3885            let matches = target_keys
3886                .symbol_keys()
3887                .iter()
3888                .filter(|keys| keys_intersect(requests, keys.keys()))
3889                .map(|keys| &target.graph().symbols[keys.symbol_index()])
3890                .collect::<Vec<_>>();
3891            require(
3892                matches.len() == 1,
3893                "qualified member did not resolve uniquely",
3894            )?;
3895            require(matches[0].name == "read", "qualified member lost leaf name")?;
3896            require(
3897                matches[0].parent.as_deref() == parent,
3898                "qualified member escaped owning namespace",
3899            )?;
3900        }
3901        Ok(())
3902    }
3903
3904    #[test]
3905    fn nested_rust_imports_keep_distinct_dependencies_and_alias_callers()
3906    -> Result<(), Box<dyn Error>> {
3907        let project = project_id(15)?;
3908        let worker = extract_symbol_graph("src/worker.rs", Some("rust"), "pub fn run() {}\n");
3909        let nested =
3910            extract_symbol_graph("src/worker/nested.rs", Some("rust"), "pub fn tail() {}\n");
3911        let caller = extract_symbol_graph(
3912            "src/main.rs",
3913            Some("rust"),
3914            "use crate::worker::{run as launch, nested::{self as tools, tail as last}, *};\nfn caller() { launch(); last(); tools::tail(); }\n",
3915        );
3916        let projection = derive_resolution_keys(project, Some("app"), &caller)?;
3917        let worker_projection = derive_resolution_keys(project, Some("app"), &worker)?;
3918        let nested_projection = derive_resolution_keys(project, Some("app"), &nested)?;
3919        let imports = relation_keys_of_kind(&caller, &projection, RelationKind::Imports);
3920        for expected in [
3921            worker_projection.source_keys(),
3922            nested_projection.source_keys(),
3923        ] {
3924            require(
3925                imports.iter().any(|keys| keys_intersect(keys, expected)),
3926                "nested use lost a distinct module dependency",
3927            )?;
3928        }
3929        assert_alias_resolution(project, Some("app"), &worker, "run", &caller, &["launch"])?;
3930        assert_alias_resolution(
3931            project,
3932            Some("app"),
3933            &nested,
3934            "tail",
3935            &caller,
3936            &["last", "tools::tail"],
3937        )?;
3938        Ok(())
3939    }
3940
3941    #[test]
3942    fn provider_scopes_abstain_instead_of_matching_packages_or_wrong_siblings()
3943    -> Result<(), Box<dyn Error>> {
3944        let project = project_id(11)?;
3945        let correct_typescript = extract_symbol_graph(
3946            "src/a/reader.ts",
3947            Some("typescript"),
3948            "export function read() {}\n",
3949        );
3950        let wrong_typescript = extract_symbol_graph(
3951            "src/b/reader.ts",
3952            Some("typescript"),
3953            "export function read() {}\n",
3954        );
3955        let typescript_caller = extract_symbol_graph(
3956            "src/main.ts",
3957            Some("typescript"),
3958            "import { read } from './a/reader';\nread();\n",
3959        );
3960        assert_import_matches_only_source(
3961            project,
3962            Some("web"),
3963            &typescript_caller,
3964            &correct_typescript,
3965            &wrong_typescript,
3966        )?;
3967
3968        let package_caller = extract_symbol_graph(
3969            "src/package.ts",
3970            Some("typescript"),
3971            "import { read } from 'reader-package';\nread();\n",
3972        );
3973        let package_projection = derive_resolution_keys(project, Some("web"), &package_caller)?;
3974        require(
3975            relation_keys_of_kind(&package_caller, &package_projection, RelationKind::Imports)
3976                .iter()
3977                .all(|keys| keys.is_empty()),
3978            "bare ECMAScript package import entered a repository-local module scope",
3979        )?;
3980        require(
3981            relation_keys(&package_caller, &package_projection, "read").is_empty(),
3982            "call through an unresolved package import fell back to a global declaration",
3983        )?;
3984
3985        for (path, language, source, target) in [
3986            (
3987                "src/unimported.ts",
3988                "typescript",
3989                "export function run() { return client.fetch(); }\n",
3990                "client.fetch",
3991            ),
3992            (
3993                "src/unimported.rs",
3994                "rust",
3995                "pub fn run() { worker::execute(); }\n",
3996                "worker::execute",
3997            ),
3998            (
3999                "src/unimported.py",
4000                "python",
4001                "def run():\n    return reader.read()\n",
4002                "reader.read",
4003            ),
4004        ] {
4005            let graph = extract_symbol_graph(path, Some(language), source);
4006            let projection = derive_resolution_keys(project, Some("app"), &graph)?;
4007            require(
4008                graph.relations.iter().any(|relation| {
4009                    relation.kind == RelationKind::Calls && relation.target_name == target
4010                }),
4011                "unimported qualified-call fixture did not reach semantic normalization",
4012            )?;
4013            require(
4014                relation_keys(&graph, &projection, target).is_empty(),
4015                "unimported qualified call fell back to a project basename scope",
4016            )?;
4017        }
4018
4019        let rust_target =
4020            extract_symbol_graph("src/feature/worker.rs", Some("rust"), "pub fn run() {}\n");
4021        let wrong_rust =
4022            extract_symbol_graph("src/other/worker.rs", Some("rust"), "pub fn run() {}\n");
4023        let rust_caller = extract_symbol_graph(
4024            "src/feature/main.rs",
4025            Some("rust"),
4026            "use super::worker::run;\nfn main() { run(); }\n",
4027        );
4028        let rust_crate_caller = extract_symbol_graph(
4029            "src/main.rs",
4030            Some("rust"),
4031            "use crate::feature::worker::run;\nfn main() { run(); }\n",
4032        );
4033        let rust_self_caller = extract_symbol_graph(
4034            "src/feature/mod.rs",
4035            Some("rust"),
4036            "use self::worker::run;\nfn main() { run(); }\n",
4037        );
4038        for caller in [&rust_caller, &rust_crate_caller, &rust_self_caller] {
4039            assert_import_matches_only_source(
4040                project,
4041                Some("atlas"),
4042                caller,
4043                &rust_target,
4044                &wrong_rust,
4045            )?;
4046        }
4047
4048        let python_target = extract_symbol_graph(
4049            "src/package/reader.py",
4050            Some("python"),
4051            "def read():\n    pass\n",
4052        );
4053        let wrong_python = extract_symbol_graph(
4054            "src/other/reader.py",
4055            Some("python"),
4056            "def read():\n    pass\n",
4057        );
4058        let python_caller = extract_symbol_graph(
4059            "src/package/main.py",
4060            Some("python"),
4061            "from .reader import read\nread()\n",
4062        );
4063        assert_import_matches_only_source(
4064            project,
4065            Some("python-app"),
4066            &python_caller,
4067            &python_target,
4068            &wrong_python,
4069        )?;
4070        let parent_python_target = extract_symbol_graph(
4071            "src/package/reader.py",
4072            Some("python"),
4073            "def read():\n    pass\n",
4074        );
4075        let parent_python_wrong = extract_symbol_graph(
4076            "src/package/sub/reader.py",
4077            Some("python"),
4078            "def read():\n    pass\n",
4079        );
4080        let parent_python_caller = extract_symbol_graph(
4081            "src/package/sub/main.py",
4082            Some("python"),
4083            "from ..reader import read\nread()\n",
4084        );
4085        assert_import_matches_only_source(
4086            project,
4087            Some("python-app"),
4088            &parent_python_caller,
4089            &parent_python_target,
4090            &parent_python_wrong,
4091        )
4092    }
4093
4094    #[test]
4095    fn direct_provider_families_retain_duplicate_ambiguity_keys() -> Result<(), Box<dyn Error>> {
4096        let project = project_id(12)?;
4097        for (path, language, target_source, caller_path, caller_source, target_name) in [
4098            (
4099                "src/shared/reader.ts",
4100                "typescript",
4101                "export function read() {}\nexport function read() {}\n",
4102                "src/main.ts",
4103                "import { read } from './shared/reader';\nread();\n",
4104                "read",
4105            ),
4106            (
4107                "src/package/reader.py",
4108                "python",
4109                "def read():\n    pass\ndef read():\n    pass\n",
4110                "src/main.py",
4111                "from package.reader import read\nread()\n",
4112                "read",
4113            ),
4114        ] {
4115            let target = extract_symbol_graph(path, Some(language), target_source);
4116            let caller = extract_symbol_graph(caller_path, Some(language), caller_source);
4117            let target_projection = derive_resolution_keys(project, Some("app"), &target)?;
4118            let caller_projection = derive_resolution_keys(project, Some("app"), &caller)?;
4119            let duplicate_keys = target_projection
4120                .symbol_keys()
4121                .iter()
4122                .filter(|entry| target.symbols[entry.symbol_index()].name == target_name)
4123                .map(super::SymbolResolutionKeys::keys)
4124                .collect::<Vec<_>>();
4125            require(
4126                duplicate_keys.len() == 2,
4127                "duplicate declarations were not both projected",
4128            )?;
4129            let dependency = relation_keys(&caller, &caller_projection, target_name);
4130            require(
4131                duplicate_keys
4132                    .iter()
4133                    .all(|keys| keys_intersect(dependency, keys)),
4134                "call dependency did not retain every duplicate declaration candidate",
4135            )?;
4136        }
4137        Ok(())
4138    }
4139
4140    #[test]
4141    fn resolution_identity_separates_provider_from_cross_dialect_family()
4142    -> Result<(), Box<dyn Error>> {
4143        let project = project_id(13)?;
4144        let typescript = extract_symbol_graph(
4145            "src/shared/reader.ts",
4146            Some("typescript"),
4147            "export function read() {}\n",
4148        );
4149        let javascript = extract_symbol_graph(
4150            "src/shared/reader.ts",
4151            Some("javascript"),
4152            "export function read() {}\n",
4153        );
4154        let typescript_projection = derive_resolution_keys(project, Some("web"), &typescript)?;
4155        let javascript_projection = derive_resolution_keys(project, Some("web"), &javascript)?;
4156        require(
4157            typescript_projection.source_keys() == javascript_projection.source_keys(),
4158            "ECMAScript-compatible dialects entered different resolution families",
4159        )?;
4160        let provider = projectatlas_core::graph::GraphIdentityText::new("ecma-script")?;
4161        let family = projectatlas_core::graph::GraphIdentityText::new("ecmascript")?;
4162        let package = projectatlas_core::graph::GraphIdentityText::new("web")?;
4163        let identity =
4164            projectatlas_core::graph::GraphIdentityText::new("module:src/shared/reader")?;
4165        let expected = CanonicalResolutionKey::new(
4166            project,
4167            projectatlas_core::graph::ResolutionKeyDomain::Module,
4168            &provider,
4169            &family,
4170            Some(&package),
4171            None,
4172            Some(projectatlas_core::graph::GraphRelationKind::from_legacy(
4173                RelationKind::Imports,
4174            )),
4175            &identity,
4176        );
4177        require(
4178            typescript_projection.source_keys().contains(&expected),
4179            "provider owner and cross-dialect family were not projected independently",
4180        )
4181    }
4182
4183    #[test]
4184    fn embedded_hosts_publish_module_keys_only_for_admitted_component_facts()
4185    -> Result<(), Box<dyn Error>> {
4186        let project = project_id(14)?;
4187        for (path, language, source) in [
4188            (
4189                "page.html",
4190                "html",
4191                "<script>export function run() {}</script>",
4192            ),
4193            ("empty.vue", "vue", "<template><p>empty</p></template>"),
4194            (
4195                "external.vue",
4196                "vue",
4197                "<script src='./external.js'></script>",
4198            ),
4199            ("empty.svelte", "svelte", "<p>empty</p>"),
4200            (
4201                "external.svelte",
4202                "svelte",
4203                "<script src='./external.js'></script>",
4204            ),
4205            (
4206                "external-inline.vue",
4207                "vue",
4208                "<script>import * as fs from 'node:fs';</script>",
4209            ),
4210            (
4211                "external-inline.svelte",
4212                "svelte",
4213                "<script>import * as fs from 'node:fs';</script>",
4214            ),
4215            ("malformed.vue", "vue", "<script"),
4216            ("malformed.svelte", "svelte", "<script"),
4217        ] {
4218            let graph = extract_symbol_graph(path, Some(language), source);
4219            let projection = derive_resolution_keys(project, Some("web"), &graph)?;
4220            require(
4221                projection.source_keys().is_empty(),
4222                "host without an admitted component module surface emitted source keys",
4223            )?;
4224            if path.starts_with("external-inline") {
4225                require(
4226                    relation_keys_of_kind(&graph, &projection, RelationKind::Imports).len() == 1,
4227                    "external-only component lost its outbound import relationship",
4228                )?;
4229            }
4230        }
4231        for (path, language) in [("component.vue", "vue"), ("component.svelte", "svelte")] {
4232            let graph = extract_symbol_graph(
4233                path,
4234                Some(language),
4235                "<script>export function run() {}</script>",
4236            );
4237            require(
4238                !derive_resolution_keys(project, Some("web"), &graph)?
4239                    .source_keys()
4240                    .is_empty(),
4241                "admitted component script facts did not expose their host module",
4242            )?;
4243        }
4244        Ok(())
4245    }
4246
4247    #[test]
4248    fn canonical_projection_is_stable_and_retains_ambiguous_unresolved_and_package_keys()
4249    -> Result<(), Box<dyn Error>> {
4250        let project = project_id(2)?;
4251        let first = extract_symbol_graph("src/worker.rs", Some("rust"), "pub fn run() {}\n");
4252        let moved = extract_symbol_graph(
4253            "src/worker.rs",
4254            Some("rust"),
4255            "\n\n// formatting moved the declaration\npub fn run() {}\n",
4256        );
4257        let first_projection = derive_resolution_keys(project, Some("atlas"), &first)?;
4258        let moved_projection = derive_resolution_keys(project, Some("atlas"), &moved)?;
4259        require(
4260            symbol_keys(&first, &first_projection, "run")
4261                == symbol_keys(&moved, &moved_projection, "run"),
4262            "formatting-only line movement changed canonical symbol identity",
4263        )?;
4264        require(
4265            first_projection.source_keys()
4266                != derive_resolution_keys(project_id(3)?, Some("atlas"), &first)?.source_keys(),
4267            "different projects produced the same canonical source identity",
4268        )?;
4269        require(
4270            symbol_keys(&first, &first_projection, "run")
4271                != symbol_keys(
4272                    &first,
4273                    &derive_resolution_keys(project, Some("other-package"), &first)?,
4274                    "run",
4275                ),
4276            "different packages produced the same canonical declaration identity",
4277        )?;
4278
4279        let duplicate = extract_symbol_graph("src/other.rs", Some("rust"), "pub fn run() {}\n");
4280        let caller = extract_symbol_graph(
4281            "src/main.rs",
4282            Some("rust"),
4283            "fn main() { run(); missing(); }\n",
4284        );
4285        let duplicate_projection = derive_resolution_keys(project, Some("atlas"), &duplicate)?;
4286        let caller_projection = derive_resolution_keys(project, Some("atlas"), &caller)?;
4287        let run_dependencies = relation_keys(&caller, &caller_projection, "run");
4288        require(
4289            keys_intersect(
4290                run_dependencies,
4291                symbol_keys(&first, &first_projection, "run"),
4292            ),
4293            "ambiguous call did not retain the first matching declaration key",
4294        )?;
4295        require(
4296            keys_intersect(
4297                run_dependencies,
4298                symbol_keys(&duplicate, &duplicate_projection, "run"),
4299            ),
4300            "ambiguous call did not retain the duplicate declaration key",
4301        )?;
4302        let missing_dependencies = relation_keys(&caller, &caller_projection, "missing");
4303        require(
4304            !missing_dependencies.is_empty(),
4305            "unresolved call lost its canonical dependency key",
4306        )?;
4307        require(
4308            !keys_intersect(
4309                missing_dependencies,
4310                symbol_keys(&first, &first_projection, "run"),
4311            ),
4312            "unresolved call matched an unrelated declaration key",
4313        )?;
4314
4315        let package = extract_symbol_graph(
4316            "vendor/library/Cargo.toml",
4317            Some("cargo-manifest"),
4318            "[package]\nname = \"library\"\n",
4319        );
4320        let manifest = extract_symbol_graph(
4321            "Cargo.toml",
4322            Some("cargo-manifest"),
4323            "[package]\nname = \"app\"\n[dependencies]\nlibrary = \"1\"\n",
4324        );
4325        let package_projection = derive_resolution_keys(project, None, &package)?;
4326        let manifest_projection = derive_resolution_keys(project, None, &manifest)?;
4327        require(
4328            package_projection.source_keys().is_empty()
4329                && manifest_projection.source_keys().is_empty(),
4330            "Cargo manifests advertised ordinary source-module identities",
4331        )?;
4332        require(
4333            keys_intersect(
4334                relation_keys(&manifest, &manifest_projection, "library"),
4335                symbol_keys(&package, &package_projection, "library"),
4336            ),
4337            "Cargo dependency did not match its package declaration key",
4338        )?;
4339        Ok(())
4340    }
4341
4342    #[test]
4343    fn cargo_provider_abstains_on_malformed_input_and_retains_duplicate_package_candidates()
4344    -> Result<(), Box<dyn Error>> {
4345        let project = project_id(16)?;
4346        let malformed = extract_symbol_graph(
4347            "Cargo.toml",
4348            Some("cargo-manifest"),
4349            "[package\nname = \"broken\"\n",
4350        );
4351        let malformed_projection = derive_resolution_keys(project, None, &malformed)?;
4352        require(
4353            malformed_projection.source_keys().is_empty()
4354                && malformed_projection.symbol_keys().is_empty()
4355                && malformed_projection.relation_keys().is_empty(),
4356            "malformed Cargo input fabricated semantic identities",
4357        )?;
4358
4359        let first = extract_symbol_graph(
4360            "vendor/first/Cargo.toml",
4361            Some("cargo-manifest"),
4362            "[package]\nname = \"shared-package\"\n",
4363        );
4364        let second = extract_symbol_graph(
4365            "vendor/second/Cargo.toml",
4366            Some("cargo-manifest"),
4367            "[package]\nname = \"shared-package\"\n",
4368        );
4369        let caller = extract_symbol_graph(
4370            "Cargo.toml",
4371            Some("cargo-manifest"),
4372            "[package]\nname = \"app\"\n[dependencies]\nshared-package = \"1\"\n",
4373        );
4374        let first_projection = derive_resolution_keys(project, None, &first)?;
4375        let second_projection = derive_resolution_keys(project, None, &second)?;
4376        let caller_projection = derive_resolution_keys(project, None, &caller)?;
4377        let dependency = relation_keys(&caller, &caller_projection, "shared-package");
4378        require(
4379            keys_intersect(
4380                dependency,
4381                symbol_keys(&first, &first_projection, "shared-package"),
4382            ) && keys_intersect(
4383                dependency,
4384                symbol_keys(&second, &second_projection, "shared-package"),
4385            ),
4386            "duplicate Cargo packages did not remain ambiguity candidates",
4387        )
4388    }
4389
4390    #[test]
4391    fn projection_rejects_invalid_identity_and_excessive_key_fanout() -> Result<(), Box<dyn Error>>
4392    {
4393        let graph = extract_symbol_graph("src/lib.rs", Some("rust"), "pub fn run() {}\n");
4394        require(
4395            matches!(
4396                derive_resolution_keys(project_id(4)?, Some("bad\0package"), &graph),
4397                Err(ResolutionProjectionError::Contract(_))
4398            ),
4399            "invalid package identity was accepted",
4400        )?;
4401
4402        let imports = (0..65)
4403            .map(|index| format!("import {{ run as execute }} from './module{index}';"))
4404            .collect::<Vec<_>>()
4405            .join("\n");
4406        let source = format!("{imports}\nexecute();\n");
4407        let graph = extract_symbol_graph("src/main.ts", Some("typescript"), &source);
4408        require(
4409            matches!(
4410                derive_resolution_keys(project_id(4)?, Some("web"), &graph),
4411                Err(ResolutionProjectionError::KeyLimit {
4412                    fact: "relation",
4413                    ..
4414                })
4415            ),
4416            "excessive relation-key fan-out was not rejected",
4417        )?;
4418        Ok(())
4419    }
4420
4421    #[test]
4422    fn contract_failure_retains_valid_resolution_projection_siblings() -> Result<(), Box<dyn Error>>
4423    {
4424        let path = format!("pkg/{0}/{0}/{0}/{0}/page.rs", "d".repeat(100));
4425        let imported = "i".repeat(3_800);
4426        let mut graph = extract_symbol_graph(
4427            &path,
4428            Some("rust"),
4429            "pub fn caller() { helper(); }\npub fn helper() {}\n",
4430        );
4431        graph
4432            .relations
4433            .retain(|relation| relation.kind == RelationKind::Calls);
4434        graph.relations.push(SymbolRelation {
4435            path,
4436            source_name: "<module>".to_string(),
4437            target_name: format!("use self::{{{imported}}};"),
4438            kind: RelationKind::Imports,
4439            line: 1,
4440            context: "partial projection fixture".to_string(),
4441            parser: ParserKind::TreeSitter,
4442        });
4443        graph.relations.push(SymbolRelation {
4444            path: graph.path.clone(),
4445            source_name: "caller".to_string(),
4446            target_name: format!("{imported}.member"),
4447            kind: RelationKind::Calls,
4448            line: 3,
4449            context: "partial projection fixture".to_string(),
4450            parser: ParserKind::TreeSitter,
4451        });
4452
4453        let result = derive_resolution_keys(project_id(23)?, None, &graph);
4454        let Err(ResolutionProjectionError::Contract(failure)) = result else {
4455            return Err(io::Error::other(
4456                "invalid derived resolution key did not retain a typed contract failure",
4457            )
4458            .into());
4459        };
4460        require(
4461            !failure.projection().source_keys().is_empty(),
4462            "contract failure discarded valid source resolution keys",
4463        )?;
4464        require(
4465            !failure.projection().symbol_keys().is_empty(),
4466            "contract failure discarded valid symbol resolution keys",
4467        )?;
4468        require(
4469            failure
4470                .projection()
4471                .relation_keys()
4472                .iter()
4473                .any(|relation| relation.relation_index() == 0 && !relation.keys().is_empty()),
4474            "contract failure discarded a valid sibling relation key",
4475        )?;
4476        require(
4477            failure
4478                .failures()
4479                .iter()
4480                .all(|failure| !format!("{:?}", failure.error()).contains(&imported)),
4481            "contract failure retained rejected raw identity text",
4482        )?;
4483        Ok(())
4484    }
4485
4486    #[test]
4487    fn contract_failure_deduplicates_repeated_facts_after_detail_ceiling()
4488    -> Result<(), Box<dyn Error>> {
4489        let mut graph = SymbolGraph {
4490            path: "src/main.rs".to_string(),
4491            language: Some("rust".to_string()),
4492            parser: ParserKind::TreeSitter,
4493            symbols: Vec::with_capacity(super::MAX_RESOLUTION_PROJECTION_FAILURES + 1),
4494            relations: Vec::new(),
4495        };
4496        for index in 0..super::MAX_RESOLUTION_PROJECTION_FAILURES {
4497            graph.symbols.push(CodeSymbol {
4498                path: graph.path.clone(),
4499                language: graph.language.clone(),
4500                name: format!("invalid\0{index}"),
4501                kind: SymbolKind::Function,
4502                signature: "fn invalid()".to_string(),
4503                exported: true,
4504                documentation: None,
4505                line_start: index + 1,
4506                line_end: index + 1,
4507                source_selector: None,
4508                parent: None,
4509                parser: ParserKind::TreeSitter,
4510                detail: None,
4511            });
4512        }
4513        graph.symbols.push(CodeSymbol {
4514            path: graph.path.clone(),
4515            language: graph.language.clone(),
4516            name: "valid_tail".to_string(),
4517            kind: SymbolKind::Function,
4518            signature: "fn valid_tail()".to_string(),
4519            exported: true,
4520            documentation: None,
4521            line_start: graph.symbols.len() + 1,
4522            line_end: graph.symbols.len() + 1,
4523            source_selector: None,
4524            parent: Some("\0invalid-parent".to_string()),
4525            parser: ParserKind::TreeSitter,
4526            detail: None,
4527        });
4528
4529        let Err(ResolutionProjectionError::Contract(failure)) =
4530            derive_resolution_keys(project_id(24)?, None, &graph)
4531        else {
4532            return Err(
4533                io::Error::other("repeated post-ceiling failures were not reported").into(),
4534            );
4535        };
4536        require(
4537            failure.failures().len() == super::MAX_RESOLUTION_PROJECTION_FAILURES,
4538            "detail ceiling retained repeated fact attempts",
4539        )?;
4540        require(
4541            failure.rejected_count() == super::MAX_RESOLUTION_PROJECTION_FAILURES + 1,
4542            "repeated post-ceiling fact inflated rejected count",
4543        )?;
4544        Ok(())
4545    }
4546
4547    #[test]
4548    fn supported_typescript_parser_retains_control_bearing_import_identities()
4549    -> Result<(), Box<dyn Error>> {
4550        let graph = extract_symbol_graph(
4551            "src/page.ts",
4552            Some("typescript"),
4553            "import * as first from './bad\0one';\nimport * as second from './bad\0two';\nexport function caller() { first(); second(); }\n",
4554        );
4555        require(
4556            graph.parser == ParserKind::TreeSitter,
4557            "supported TypeScript fixture did not use the semantic parser",
4558        )?;
4559        require(
4560            graph.relations.iter().any(|relation| {
4561                relation.kind == RelationKind::Imports && relation.target_name.contains('\0')
4562            }),
4563            "supported TypeScript parser did not retain the control-bearing import",
4564        )?;
4565        Ok(())
4566    }
4567
4568    #[test]
4569    fn unsupported_and_lockfile_languages_do_not_gain_generic_name_resolution()
4570    -> Result<(), Box<dyn Error>> {
4571        for graph in [
4572            extract_symbol_graph(
4573                "src/Service.java",
4574                Some("java"),
4575                "public class Service { void run() {} }\n",
4576            ),
4577            extract_symbol_graph(
4578                "Cargo.lock",
4579                Some("cargo-lock"),
4580                "[[package]]\nname = \"dependency\"\nversion = \"1.0.0\"\n",
4581            ),
4582        ] {
4583            let projection = derive_resolution_keys(project_id(9)?, Some("app"), &graph)?;
4584            require(
4585                projection.source_keys().is_empty()
4586                    && projection.symbol_keys().is_empty()
4587                    && projection.relation_keys().is_empty(),
4588                "unsupported language emitted generic resolution keys",
4589            )?;
4590        }
4591        Ok(())
4592    }
4593
4594    fn require(condition: bool, message: &str) -> Result<(), Box<dyn Error>> {
4595        if condition {
4596            Ok(())
4597        } else {
4598            Err(io::Error::other(message.to_string()).into())
4599        }
4600    }
4601
4602    fn project_id(byte: u8) -> Result<ProjectInstanceId, Box<dyn Error>> {
4603        Ok(ProjectInstanceId::from_bytes([byte; 16])?)
4604    }
4605
4606    fn assert_alias_resolution(
4607        project: ProjectInstanceId,
4608        package: Option<&str>,
4609        target: &SymbolGraph,
4610        symbol_name: &str,
4611        caller: &SymbolGraph,
4612        call_targets: &[&str],
4613    ) -> Result<(), Box<dyn Error>> {
4614        let target_projection = derive_resolution_keys(project, package, target)?;
4615        let caller_projection = derive_resolution_keys(project, package, caller)?;
4616        let exports = symbol_keys(target, &target_projection, symbol_name);
4617        for call_target in call_targets {
4618            if !keys_intersect(
4619                relation_keys(caller, &caller_projection, call_target),
4620                exports,
4621            ) {
4622                return Err(io::Error::other(format!(
4623                    "{call_target} did not share a canonical key with {symbol_name}"
4624                ))
4625                .into());
4626            }
4627        }
4628        Ok(())
4629    }
4630
4631    fn assert_import_matches_only_source(
4632        project: ProjectInstanceId,
4633        package: Option<&str>,
4634        caller: &SymbolGraph,
4635        expected: &SymbolGraph,
4636        wrong: &SymbolGraph,
4637    ) -> Result<(), Box<dyn Error>> {
4638        let caller_projection = derive_resolution_keys(project, package, caller)?;
4639        let expected_projection = derive_resolution_keys(project, package, expected)?;
4640        let wrong_projection = derive_resolution_keys(project, package, wrong)?;
4641        let imports = relation_keys_of_kind(caller, &caller_projection, RelationKind::Imports);
4642        require(
4643            imports.len() == 1,
4644            "caller did not retain one import relationship",
4645        )?;
4646        require(
4647            keys_intersect(imports[0], expected_projection.source_keys()),
4648            "caller import missed its exact source",
4649        )?;
4650        require(
4651            !keys_intersect(imports[0], wrong_projection.source_keys()),
4652            "caller import matched a wrong-sibling basename",
4653        )
4654    }
4655
4656    fn symbol_keys<'a>(
4657        graph: &SymbolGraph,
4658        projection: &'a super::ResolutionKeyProjection,
4659        name: &str,
4660    ) -> &'a [CanonicalResolutionKey] {
4661        projection
4662            .symbol_keys()
4663            .iter()
4664            .find(|entry| graph.symbols[entry.symbol_index()].name == name)
4665            .map_or(&[], super::SymbolResolutionKeys::keys)
4666    }
4667
4668    fn relation_keys<'a>(
4669        graph: &SymbolGraph,
4670        projection: &'a super::ResolutionKeyProjection,
4671        target: &str,
4672    ) -> &'a [CanonicalResolutionKey] {
4673        projection
4674            .relation_keys()
4675            .iter()
4676            .find(|entry| {
4677                let relation = &graph.relations[entry.relation_index()];
4678                matches!(relation.kind, RelationKind::Calls | RelationKind::DependsOn)
4679                    && relation.target_name == target
4680            })
4681            .map_or(&[], super::RelationResolutionKeys::keys)
4682    }
4683
4684    fn relation_keys_of_kind<'a>(
4685        graph: &SymbolGraph,
4686        projection: &'a super::ResolutionKeyProjection,
4687        kind: RelationKind,
4688    ) -> Vec<&'a [CanonicalResolutionKey]> {
4689        projection
4690            .relation_keys()
4691            .iter()
4692            .filter(|entry| graph.relations[entry.relation_index()].kind == kind)
4693            .map(super::RelationResolutionKeys::keys)
4694            .collect()
4695    }
4696
4697    fn keys_intersect(left: &[CanonicalResolutionKey], right: &[CanonicalResolutionKey]) -> bool {
4698        left.iter().any(|key| right.binary_search(key).is_ok())
4699    }
4700}