Follow-up to #70, and the thing that unblocks an extracted xbox library plugin.
The measurement
The xbox scanner resolves each package's PackageFamilyName by enumerating %ProgramData%\Microsoft\Windows\AppRepository\Packages. Probed on .173 with a temp scheduled task (design doc S1, now answered):
principal
result
NT AUTHORITY\LocalService — the plugin runner
UnauthorizedAccessException
LocalSystem — the host service
count=348
The 348-vs-denied contrast is what makes that a permission verdict rather than an empty directory. Everything else the Windows plugins need is readable de-privileged: C:\XboxGames, Epic's %ProgramData% manifests, and the HKLM Steam root.
So an extracted xbox plugin cannot build an AUMID, and aumid — which takes a complete <PFN>!<AppId> — is unpublishable by one.
The fix, which is neither fallback the design doc offered
The doc proposed either entries without aumid (they would list but not launch), or a one-shot elevated PFN resolve at install time. This is a third option the probe made obvious:
The plugin sends <Identity>!<AppId> — both read straight out of MicrosoftGame.config, which is readable de-privileged — and the host resolves the publisher hash at launch time, where it already owns command construction.
D1 holds: the plugin supplies a validated value, the host builds the command.
Resolving late means a package update that changes the publisher hash cannot leave a stale unlaunchable tile behind — which the install-time cache would have.
xbox_pfn therefore moves from the scan path to the launch path rather than being deleted, and its doc comment records the privilege asymmetry so the next reader doesn't have to re-derive it.
The charset guard is now load-bearing
Factored out as aumid_part / valid_aumid and shared with the aumid kind. On aumid it was belt-and-braces — every value was host-derived. On xbox the Identity arrives over the wire from a plugin and is interpolated into a shell:AppsFolder\… argument, so it is validated at reconcile too: a malformed value is a 400 the plugin author can act on, not a tile that fails on click.
Gates
Gate
Where
Result
cargo test -p punktfunk-host
.21
438 passed / 0 failed
cargo test -p punktfunk-host --bin punktfunk-host library::
.173
55 passed / 0 failed
Both +1 for the new guard test, and .173 is where these arms actually compile — Linux CI never sees them.
The plugin that uses it
unom/punktfunk-plugin-xbox, branch feat/xbox-library-source (7 tests, tsc + biome clean). That repo was empty, so it has no main branch yet and no PR can target it.
Still owed there: parity --compare on a Windows box with GDK titles installed, under the runner.
Follow-up to #70, and the thing that unblocks an extracted xbox library plugin.
## The measurement
The xbox scanner resolves each package's PackageFamilyName by enumerating `%ProgramData%\Microsoft\Windows\AppRepository\Packages`. Probed on `.173` with a temp scheduled task (design doc **S1**, now answered):
| principal | result |
|---|---|
| `NT AUTHORITY\LocalService` — **the plugin runner** | `UnauthorizedAccessException` |
| `LocalSystem` — **the host service** | `count=348` |
The 348-vs-denied contrast is what makes that a permission verdict rather than an empty directory. Everything *else* the Windows plugins need is readable de-privileged: `C:\XboxGames`, Epic's `%ProgramData%` manifests, and the HKLM Steam root.
So an extracted xbox plugin cannot build an AUMID, and `aumid` — which takes a complete `<PFN>!<AppId>` — is unpublishable by one.
## The fix, which is neither fallback the design doc offered
The doc proposed either entries without `aumid` (they would list but not launch), or a one-shot elevated PFN resolve at install time. This is a third option the probe made obvious:
**The plugin sends `<Identity>!<AppId>`** — both read straight out of `MicrosoftGame.config`, which *is* readable de-privileged — **and the host resolves the publisher hash at launch time**, where it already owns command construction.
- D1 holds: the plugin supplies a validated value, the host builds the command.
- Same shape as the `playnite` kind from #70.
- Resolving *late* means a package update that changes the publisher hash cannot leave a stale unlaunchable tile behind — which the install-time cache would have.
`xbox_pfn` therefore moves from the scan path to the launch path rather than being deleted, and its doc comment records the privilege asymmetry so the next reader doesn't have to re-derive it.
## The charset guard is now load-bearing
Factored out as `aumid_part` / `valid_aumid` and shared with the `aumid` kind. On `aumid` it was belt-and-braces — every value was host-derived. On `xbox` the Identity **arrives over the wire from a plugin** and is interpolated into a `shell:AppsFolder\…` argument, so it is validated at reconcile too: a malformed value is a 400 the plugin author can act on, not a tile that fails on click.
## Gates
| Gate | Where | Result |
|---|---|---|
| `cargo test -p punktfunk-host` | `.21` | **438 passed / 0 failed** |
| `cargo test -p punktfunk-host --bin punktfunk-host library::` | `.173` | **55 passed / 0 failed** |
Both +1 for the new guard test, and `.173` is where these arms actually compile — Linux CI never sees them.
## The plugin that uses it
`unom/punktfunk-plugin-xbox`, branch `feat/xbox-library-source` (7 tests, tsc + biome clean). That repo was empty, so it has no `main` branch yet and no PR can target it.
Still owed there: `parity --compare` on a Windows box with GDK titles installed, under the runner.
The xbox scanner resolves each package's PackageFamilyName by enumerating
%ProgramData%\Microsoft\Windows\AppRepository\Packages. Probed on .173 (design doc S1),
that directory is:
NT AUTHORITY\LocalService UnauthorizedAccessException <- the plugin runner
LocalSystem count=348 <- the host service
So an extracted xbox plugin cannot build an AUMID, and `aumid` — which takes a complete
`<PFN>!<AppId>` — is unpublishable by one.
The design doc offered two fallbacks: entries without `aumid` (they would list but not
launch), or a one-shot elevated PFN resolve at install time. This is neither. The plugin
sends `<Identity>!<AppId>`, both read straight out of MicrosoftGame.config — which IS
readable de-privileged — and the host resolves the publisher hash at LAUNCH time, where it
already owns command construction.
That keeps D1 intact (the plugin supplies a validated value, the host builds the command),
it is the same shape as the `playnite` kind, and resolving late means a package update that
changes the publisher hash cannot leave a stale unlaunchable tile behind — which the
install-time cache would have.
`xbox_pfn` therefore moves from the scan path to the launch path rather than being deleted,
and its doc records the privilege asymmetry so the next reader doesn't re-derive it.
The charset guard is factored out (`aumid_part` / `valid_aumid`) and shared with the `aumid`
kind. On `aumid` it was belt-and-braces — every value was host-derived. On `xbox` it is
load-bearing: the Identity arrives over the wire from a plugin, and it is interpolated into
a `shell:AppsFolder\…` argument. Validated inbound at reconcile too, so a malformed value is
a 400 the plugin author can act on rather than a tile that fails on click.
Gates: punktfunk-host 438 passed / 0 failed on .21, and 55/0 for the library tests on .173
(where these arms actually compile). Both counts +1 for the new guard test.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Follow-up to #70, and the thing that unblocks an extracted xbox library plugin.
The measurement
The xbox scanner resolves each package's PackageFamilyName by enumerating
%ProgramData%\Microsoft\Windows\AppRepository\Packages. Probed on.173with a temp scheduled task (design doc S1, now answered):NT AUTHORITY\LocalService— the plugin runnerUnauthorizedAccessExceptionLocalSystem— the host servicecount=348The 348-vs-denied contrast is what makes that a permission verdict rather than an empty directory. Everything else the Windows plugins need is readable de-privileged:
C:\XboxGames, Epic's%ProgramData%manifests, and the HKLM Steam root.So an extracted xbox plugin cannot build an AUMID, and
aumid— which takes a complete<PFN>!<AppId>— is unpublishable by one.The fix, which is neither fallback the design doc offered
The doc proposed either entries without
aumid(they would list but not launch), or a one-shot elevated PFN resolve at install time. This is a third option the probe made obvious:The plugin sends
<Identity>!<AppId>— both read straight out ofMicrosoftGame.config, which is readable de-privileged — and the host resolves the publisher hash at launch time, where it already owns command construction.playnitekind from #70.xbox_pfntherefore moves from the scan path to the launch path rather than being deleted, and its doc comment records the privilege asymmetry so the next reader doesn't have to re-derive it.The charset guard is now load-bearing
Factored out as
aumid_part/valid_aumidand shared with theaumidkind. Onaumidit was belt-and-braces — every value was host-derived. Onxboxthe Identity arrives over the wire from a plugin and is interpolated into ashell:AppsFolder\…argument, so it is validated at reconcile too: a malformed value is a 400 the plugin author can act on, not a tile that fails on click.Gates
cargo test -p punktfunk-host.21cargo test -p punktfunk-host --bin punktfunk-host library::.173Both +1 for the new guard test, and
.173is where these arms actually compile — Linux CI never sees them.The plugin that uses it
unom/punktfunk-plugin-xbox, branchfeat/xbox-library-source(7 tests, tsc + biome clean). That repo was empty, so it has nomainbranch yet and no PR can target it.Still owed there:
parity --compareon a Windows box with GDK titles installed, under the runner.xboxlaunch kind a de-privileged plugin can publish