Problem
Resolver#package_id builds a PackageId by excavating the constraint hash by key convention:
source = decl.constraint["repo"] || decl.constraint["url"]
Identity should be a field, not an excavation. A source-based package's coordinates (git URL, owner/repo slug) are part of which package this is — the same fact PackageId#source carries — yet on the declaration side they hide inside the constraint hash, whose job is to describe which versions qualify. Any new source-shaped key (a mirror URL, a monorepo path) silently extends the || chain.
Proposal
Give Declaration an explicit optional source field, stamped where the constraint is normalized (DSL verbs for project rows; repositories for transitive declarations). Resolver#package_id then reads decl.source and the key-convention dig dies. The constraint hash keeps only version-shaped keys (version, tag, buildid, …).
Notes
Problem
Resolver#package_idbuilds aPackageIdby excavating the constraint hash by key convention:Identity should be a field, not an excavation. A source-based package's coordinates (git URL, owner/repo slug) are part of which package this is — the same fact
PackageId#sourcecarries — yet on the declaration side they hide inside the constraint hash, whose job is to describe which versions qualify. Any new source-shaped key (a mirror URL, a monorepo path) silently extends the||chain.Proposal
Give
Declarationan explicit optionalsourcefield, stamped where the constraint is normalized (DSL verbs for project rows; repositories for transitive declarations).Resolver#package_idthen readsdecl.sourceand the key-convention dig dies. The constraint hash keeps only version-shaped keys (version,tag,buildid, …).Notes
Declarationas the shared atom (project rows and PackageVersion declared-deps alike) — the field now has exactly one home.