Repository navigation
pkg.Resource rejects the canonical purl form its doc promises: "pkg:winget/Vim/Vim?scope=machine" fails as unknown package manager "pkg" #813
Description
Feature: #459 — Provider value types and plan-time conversion (a string becoming a pkg.Resource).
Found: 2026-09-04 on DANOBLE-WD11-3 (Windows 11, arm64), the first writ deploy after #805 fixed the team
Scheduling (USER, 2026-09-04): super high priority — addressed as soon as thread 2 (resource management) completes, ahead of the rest of thread 3.
layer's manifests. Blocks the Windows deploy of the personal layer.
Observed
writ deploy noblefactor --dry-run, writ v0.1.0-dev.20260903040058-1-g966bdf91 (windows/arm64), team clone at
9c84a90, personal clone synced:
Error: manifest C:\Users\david-noble\.cache\devlore\snapshots\personal-27e53171af5e\Home\common.Windows\packages-manifest.yaml:
building "pkg:winget/Vim/Vim?scope=machine": phase "install": addNativeSoftwarePackages: plan.Provider.invocation:
pkg.Install: op.ActionPlanner.Plan: pkg.install: param "packages": slice index 0:
construct *pkg.Resource from string: pkg.Resource: unknown package manager "pkg"
The personal manifest (Home/common.Windows/packages-manifest.yaml):
packages:
- name: pkg:winget/Vim/Vim?scope=machineMechanism
cmd/lore/lore/builder.goaddNativeSoftwarePackagespasses every manifest package string topkg.install
unchanged, as thepackagesslice.pkg/op/provider/pkg/resource.gobuildCandidateparses exactly two forms:manager:name[@version]and a bare
name[@version]. It cuts at the first:, hands the prefix toplatform.ResolvePurlType, and refuses when the
prefix is not a manager. For a purl the prefix is the scheme,pkg, so every canonical purl is refused.- The same file's doc comment promises a third form —
value: "a string package name with an optionalmanager:
prefix, or a canonical URI" (resource.go:101). The code never implemented it.buildCandidateemits the
canonical form (purl.String()→pkg:apt/jq) as the resource URI, so the constructor cannot read what it writes. pkg/platform/purl.gohasParsePURLfor the full grammarpkg:{type}/{namespace}/{name}@{version}?{qualifiers},
and documentsNamespaceas "owner/group prefix (winget publisher, npm scope, …)". The purl form is the designed
spelling for a winget package: publisher and name are two segments, andscope=machinehas no other spelling.schema/packages-manifest.jsondescribespackages[].nameas "Package name" with examplesgh,jq,ripgrep.
The schema is behind the design on the same point.
So three surfaces disagree: the schema says bare names, the constructor accepts bare or manager:-prefixed names,
the platform and the manifests use purls.
Ruling (USER, 2026-09-04) — pending
Two ways to close the disagreement; the recommendation is the first:
- pkg accepts the canonical purl form through
platform.ParsePURL: type resolved viaResolvePurlType,
namespace and name joined as the manager expects (winget:Publisher.Name), version from@, qualifiers kept on
the resource for the driver. The schema documents the purl form beside the bare andmanager:forms. - Manifests are rewritten to
manager:name, and machine scope gets a new spelling in the manifest schema.
Acceptance
-
pkg.NewResource/pkg.DiscoverResourceacceptpkg:winget/Vim/Vim?scope=machine,pkg:brew/[email protected], and
the existingbrew:jq/jqforms; a malformed purl is refused with the parser's message - the constructor round-trips its own URI:
DiscoverResource(env, r.URI())yields the same catalog entry -
schema/packages-manifest.jsondocuments every accepted form ofname - a unit test per form in
pkg/op/provider/pkg, and adevlore-testfixture that plans a purl-named package -
writ deploy noblefactor --dry-runon DANOBLE-WD11-3 passes the personal layer's Windows manifest
Workaround (USER, 2026-09-04) — in force until the fix lands
writ plans every layer from a snapshot worktree of the clone's HEAD (<layer>-<short hash> under the cache
directory), so a working-tree edit never reaches the planner and --allow-dirty only silences the dirty check. The
workaround is therefore a commit on the Personal repository, made against this issue:
Home/common.Windows/packages-manifest.yamlis set aside (renamed topackages-manifest.yaml.safe) or its entry
is respelled in the form pkg accepts today,winget:Vim.Vim, which drops thescope=machinequalifier.- The VM's personal clone is synced afterwards, and
writ deploy noblefactorruns without--allow-dirty.
The fix's plan carries the reversal as an acceptance box: the manifest returns to
pkg:winget/Vim/Vim?scope=machine on Personal's main, and the Windows deploy passes with it.
Also learned on the same test, tracked elsewhere
-
packages-manifest.yaml files collide like deployed files: the most specific one shadows the rest, so a layer's general packages are never planned #814:
packages-manifest.yamlfiles collide like deployed files; on this machine the personal Windows manifest
shadowed both team manifests, which is why this purl was the only package the planner saw. -
writ has no command to refresh a repository it cloned; a layer's manifests stay at the clone's checkout until the user runs git by hand #812: writ has no command to refresh a clone it made; the team clone stayed at 966bdf9 until pulled by hand.
-
The deploy refused the team layer as dirty over one untracked, empty
threads.mdat the clone root
("layers have uncommitted changes"); whether an untracked file outsideHome/should count is a thread-3
question, not yet filed. -
The machine's home holds ~40 dangling Tuckr-era symlinks into
Workspace\Personal\Home\all…, which break the
PowerShell profile on every login; migration debris the deploy will meet at its targets. -
The binary on the machine is the 966bdf9 build; any pkg fix needs a rebuild (
make build PLATFORM=windows/arm64,
make build PLATFORM=<other> fails its version check against a stale host binary #790 caveat) and a copy before the deploy can be retried.
Activity
David-Noble-at-work commentedon Sep 8, 2026
Met again 2026-09-08 on DANOBLE-WD11-3, the first deploy there since noblefactor-ops#182 and personal#174, on build
919b952:Planning stops before a single link is made, so the whole personal layer is undeployable on Windows while its manifest carries the documented purl form. The detour from the last Windows test was applied again — a local commit on the writ-owned personal clone renaming the manifest to
.safe— so the layer move could be verified there. The clone sits onworkaround/813-manifest-formuntil this lands;git checkout mainthere undoes it.pkg reads the canonical purl it writes, and carries the namespace to …
pkg reads the canonical purl it writes, and carries the namespace to …