Repository navigation
Resolve treats a purl name as a literal, so a manifest cannot direct a native install #784
Description
The gap
Nothing in the codebase can express whether a winget package installs machine-wide or per-user, and
nothing assembles a --scope argument. A grep for --scope across internal/registry and
pkg/platform returns nothing.
On Windows the distinction is not cosmetic. User scope installs under the invoking profile and is
invisible to other users and to services; machine scope installs under Program Files and is
available to everything on the box. For an environment manager whose job is to make a machine
reproducible, which of the two happened is part of what was declared.
Concrete case
Vim was installed by hand as winget install Vim.Vim --scope machine — native ARM64, into
C:\Program Files\Vim. Declaring it in a packages manifest cannot reproduce that: the entry says
vim and lore chooses. If lore later defaults to user scope, a redeploy silently produces a
different machine than the one that was set up.
Where it cannot be said today
- The manifest.
schema/packages-manifest.jsonsetsadditionalProperties: falseand permits
onlynameandwith. This is correct and should not change —docs/guides/writ/packages-manifest.md
is explicit that "You don't specify how to install packages in the manifest — that knowledge lives
in the registry." Scope is a how. - The registry.
internal/registrycurrently holdsgit.goandtransport.go. There is no vim
entry, and no mechanism a registry entry could use to carry scope even if there were.
Where it could be said
pkg/platform.PURL already has the field:
Qualifiers map[string]string // key=value pairs (OS, arch, repository, distro)The purl specification is pkg:{type}/{namespace}/{name}@{version}?{qualifiers}, and winget ids are
already modelled with the publisher as namespace. So machine scope has a canonical spelling that
needs no new syntax:
pkg:winget/Vim/Vim?scope=machine
That keeps the decision in the registry where the guide puts it, keeps the manifest a declaration of
what rather than how, and reuses a field that exists rather than adding one.
Questions for the ruling
- Is
scopea purl qualifier, or a first-class field on a registry entry's Windows install method? - What is the default when unspecified — winget's own default, or machine, on the grounds that an
environment manager is provisioning a machine? - Does the same concept generalise? Homebrew has
--caskplacement and MacPorts has none; if scope
is winget-only, a qualifier is the better home than a shared field.
Plan and design documents
| Kind | Document |
|---|---|
| Plan | None yet. Written on the branch when the work opens, per the development process. |
| Design | Platform and Package Management § Routing by purl — purls already route to providers; the resolver just does not consult them |
| Design | Platform and Package Management § Purl reference matrix — the winget row |
| Design | Registry Knowledge Base Architecture — where Resolve sits |
| Design | Packages Manifest — the contract that puts how in the registry, not the manifest |
Activity
David-Noble-at-work commentedon Sep 3, 2026
Withdrawn. The gap I described does not exist: scope is expressible today, because the purl form
already carries it and the manifest takes a name as a string.
Qualifiers are part of the purl specification,
PURL.Qualifiersalready models them, and winget idsare already publisher-scoped as the namespace. Nothing needs adding to the manifest schema — which
correctly permits only a name and its features — and no new field is needed on a registry entry.
I filed this having checked only that no
--scopeargument was assembled anywhere, and concluded acapability was missing when what was missing was my reading of the purl I had just been looking at.
Recorded in David-Noble-at-work/personal#161, which now names vim by purl.
David-Noble-at-work commentedon Sep 3, 2026
Reopened, with the gap relocated. The withdrawal was one step too early: the manifest can carry a
purl, but lore cannot act on one.
What was verified
LoadandValidateboth acceptpkg:winget/Vim/Vim?scope=machineas aName. The loader treats thestring as opaque, so the purl survives intact into
PackageEntry. That part of the earlier closestands.
Where it stops
Registry.Resolve(cmd/internal/lorepackage/package.go:176) treatsnameas a literal:<cacheDir>/packages/<name>— a directory namedpkg:winget/Vim/Vim?scope=machinedoes not exist, so the lore registry misses.
resolveNative, which on Windows builds a synthetic Release withNativeName: name— the purl string, verbatim.pkg:winget/Vim/Vim?scope=machine.Nothing in
lorepackageparses apkg:prefix.platform.ParsePURLexists andPURL.Qualifiersexists, but neither is consulted on the resolution path. The only purl construction in the package is
search.go:161, which builds one from a search hit's manager and name — the opposite direction.The ruling this needs
Whether
Resolveshould recognise a purl-shaped name and route it directly:winget,brew,apt, …) instead of inferring it from thetarget platform.
NativeName(Vim.Vimfor winget, whose ids are publisher-scoped).scope=machinebeing the concrete case — and need ahome on the synthetic Release, since
SyntheticPackageInfohas nowhere to put them today.That would make
pkg:winget/Vim/Vim?scope=machineinstallVim.Vimmachine-wide, which is what themanifest in David-Noble-at-work/personal#161 declares. Until then that entry loads, validates, and
would fail at deploy — the exact shape of drift #785 was about, one layer down.
Unchanged from the original report: the manifest schema should not grow a scope field. The purl
already says it; the resolver just has to listen.
[-]winget packages cannot declare machine or user scope[/-][+]Resolve treats a purl name as a literal, so a manifest cannot direct a native install[/+]