|
} |
|
} |
|
|
|
fn resolve_dependency_from_root( |
|
&mut self, |
|
alias: &str, |
|
dep: &Dependency, |
|
base_root: &Path, |
|
parent_options: &ResolutionOptions, |
|
stack_ids: &mut Vec<String>, |
|
stack_labels: &mut Vec<String>, |
|
) -> Result<String> { |
|
let package_name = dependency_package_name(alias, dep); |
|
let (mut resolved, manifest) = match dep { |
|
Dependency::Simple(version) => { |
|
self.resolve_from_registry_with_manifest(&package_name, version, None, registry::RegistryResolutionPolicy::default())? |
|
} |
|
Dependency::Detailed(detailed) => { |
|
if detailed.resolver.is_some() { |
|
let normalized = self.resolve_external_dependency(alias, &package_name, detailed, base_root, parent_options)?; |
|
let resolved = if let Some(git) = &normalized.git { |
|
self.resolve_from_git_with_manifest(&package_name, git, &normalized)? |
|
} else { |
|
self.resolve_from_registry_with_manifest( |
|
&package_name, |
|
&normalized.version, |
|
normalized.namespace.as_deref(), |
|
registry::RegistryResolutionPolicy::default(), |
|
)? |
|
}; |
|
let exact = version::parse_version_req(&normalized.version)?; |
|
if !version::satisfies(&resolved.0.version, &exact) { |
|
return Err(CompileError::without_span(format!( |
|
"external resolver for '{}' declared version inconsistent with materialized package '{}'", |
|
alias, resolved.0.version |
|
))); |
|
} |
|
resolved |
|
} else if let Some(path) = &detailed.path { |
|
self.resolve_from_path_at(&package_name, path, base_root)? |
|
} else if let Some(git) = &detailed.git { |
|
self.resolve_from_git_with_manifest(&package_name, git, detailed)? |
|
} else { |
|
let ns = detailed.namespace.as_deref(); |
|
self.resolve_from_registry_with_manifest( |
|
&package_name, |
|
&detailed.version, |
|
ns, |
|
registry::RegistryResolutionPolicy { |
|
allow_unverified: detailed.allow_unverified, |
|
allow_quarantined: detailed.allow_quarantined, |
|
}, |
|
)? |
|
} |
|
} |
|
}; |
|
|
|
self.validate_manifest_package_contract(&manifest)?; |
|
if manifest.package.name != package_name { |
|
return Err(CompileError::without_span(format!( |
|
"dependency alias '{}' expects package '{}' but '{}' declares package name '{}'", |
|
alias, |
|
package_name, |
|
resolved.path.display(), |
|
manifest.package.name |
|
))); |
|
} |
|
let child_options = dependency_resolution_options(dep, parent_options); |
|
let node_id = package_node_id(&resolved, &child_options); |
|
if let Some(requirement) = self.version_requirement_of(dep) { |
|
let requirement = version::parse_version_req(&requirement)?; |
|
if !version::satisfies(&resolved.version, &requirement) { |
|
return Err(CompileError::without_span(format!( |
|
"dependency alias '{}' resolved package '{}' to '{}', which does not satisfy its requirement", |
|
alias, package_name, resolved.version |
|
))); |
|
} |
|
} |
|
if let Some(existing) = self.resolved.get(&node_id) { |
|
if existing.manifest_digest != resolved.manifest_digest || existing.source_hash != resolved.source_hash { |
|
return Err(CompileError::without_span(format!( |
|
"dependency node '{}' resolved with conflicting manifest or source identity", |
|
node_id |
|
))); |
|
} |
|
return Ok(node_id); |
|
} |
|
if let Some(position) = stack_ids.iter().position(|item| item == &node_id) { |
|
let mut cycle = stack_labels[position..].to_vec(); |
|
cycle.push(alias.to_string()); |
|
return Err(CompileError::without_span(format!("Circular dependency detected: {}", cycle.join(" -> ")))); |
|
} |
|
|
|
stack_ids.push(node_id.clone()); |
|
stack_labels.push(alias.to_string()); |
|
let child_dependencies = self.selected_dependencies(&manifest, &child_options, false)?; |
|
let mut child_edges = BTreeMap::new(); |
|
for (child_alias, child_dep) in child_dependencies { |
|
let child_id = |
|
self.resolve_dependency_from_root(&child_alias, &child_dep, &resolved.path, &child_options, stack_ids, stack_labels)?; |
|
child_edges.insert(child_alias, child_id); |
|
} |
|
stack_ids.pop(); |
|
stack_labels.pop(); |
|
|
|
resolved.node_id = node_id.clone(); |
|
resolved.dependencies = child_edges; |
|
self.resolved.insert(node_id.clone(), resolved); |
|
Ok(node_id) |
|
} |
|
|
Summary
CellScript's documented dependency policy and implemented resolver currently disagree.
The package provenance documentation says that one version of each package exists in a graph and that conflicting requirements fail closed. The implementation instead keys nodes by package name, version, source, environment label, and feature set. It can therefore retain multiple instances of the same declared package without a graph-wide unification decision.
The compiler later flattens all resolved source roots into one module resolver. Duplicate module identities then fail only at source loading, far downstream from dependency resolution.
Code and documentation evidence
CellScript/docs/CELLSCRIPT_PACKAGE_PROVENANCE_AND_DEPLOYMENT_IDENTITY.md
Lines 1685 to 1708 in 8ae6dc4
CellScript/src/package/mod.rs
Lines 553 to 570 in 8ae6dc4
CellScript/src/package/mod.rs
Lines 1180 to 1290 in 8ae6dc4
CellScript/src/package/mod.rs
Lines 1997 to 2000 in 8ae6dc4
CellScript/src/resolve/mod.rs
Lines 61 to 72 in 8ae6dc4
This means a graph can be structurally lock-valid while its package-instance semantics remain undefined.
Why this is a correctness boundary
The resolver must decide, before source loading:
Without that decision, failures depend on incidental module names. Two versions with disjoint module names may compile, while two versions with the expected same module name fail later. Neither behavior matches the documented single-version policy.
Reference models
Cargo permits multiple semver-incompatible versions but enforces one compatible version per package/source activation domain and performs graph-wide backtracking:
https://github.com/rust-lang/cargo/blob/75d17360928f57ff2a7d2f2da1c753f5fe1926d1/src/resolver/mod.rs#L1-L37
Move package-alt explicitly allows multiple versions in a PackageID-keyed rooted DAG and carries dependency names/renames as graph edges:
https://github.com/MystenLabs/sui/blob/5a9f37431c473fa2f6d49abecbcc6a6d7190f533/external-crates/move/crates/move-package-alt/src/graph/mod.rs#L40-L53
These are different valid models. CellScript currently implements parts of both without the namespace and linkage rules needed by either.
Recommended first contract
For the next stable package schema, prefer a conservative fail-closed model:
A later multi-version design is possible only after source-level module identity becomes package-instance-qualified and public interfaces, monomorphization, metadata, source maps, and Registry coordinates preserve that qualification.
Required diagnostics
A conflict report should include:
Acceptance criteria
Non-goals