Skip to content

Resolve treats a purl name as a literal, so a manifest cannot direct a native install #784

Open

Description

@David-Noble-at-work

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.json sets additionalProperties: false and permits
    only name and with. 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/registry currently holds git.go and transport.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 scope a 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 --cask placement 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

  1. David-Noble-at-work commented on Sep 3, 2026

    @David-Noble-at-work
    ContributorAuthor

    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.

    pkg:winget/Vim/Vim?scope=machine
    

    Qualifiers are part of the purl specification, PURL.Qualifiers already models them, and winget ids
    are 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 --scope argument was assembled anywhere, and concluded a
    capability 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.

  2. David-Noble-at-work commented on Sep 3, 2026

    @David-Noble-at-work
    ContributorAuthor

    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

    Load and Validate both accept pkg:winget/Vim/Vim?scope=machine as a Name. The loader treats the
    string as opaque, so the purl survives intact into PackageEntry. That part of the earlier close
    stands.

    Where it stops

    Registry.Resolve (cmd/internal/lorepackage/package.go:176) treats name as a literal:

    1. It looks for <cacheDir>/packages/<name> — a directory named pkg:winget/Vim/Vim?scope=machine
      does not exist, so the lore registry misses.
    2. It falls to resolveNative, which on Windows builds a synthetic Release with
      NativeName: name — the purl string, verbatim.
    3. winget is then asked to install a package literally called pkg:winget/Vim/Vim?scope=machine.

    Nothing in lorepackage parses a pkg: prefix. platform.ParsePURL exists and PURL.Qualifiers
    exists, 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 Resolve should recognise a purl-shaped name and route it directly:

    • Type selects the native source (winget, brew, apt, …) instead of inferring it from the
      target platform.
    • Namespace + Name become the NativeName (Vim.Vim for winget, whose ids are publisher-scoped).
    • Qualifiers carry manager-specific options — scope=machine being the concrete case — and need a
      home on the synthetic Release, since SyntheticPackageInfo has nowhere to put them today.

    That would make pkg:winget/Vim/Vim?scope=machine install Vim.Vim machine-wide, which is what the
    manifest 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.

  3. changed the title [-]winget packages cannot declare machine or user scope[/-] [+]Resolve treats a purl name as a literal, so a manifest cannot direct a native install[/+] on Sep 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Epic:LorePackaginglore packaging and the registryfeatureDeliverable unit within an epic

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions