music's library browser depends on another plugin's permission, not just its own

MusicBrowser lists folders with GET /file-browser/ls rather than through the
music sidecar. /file-browser belongs to `files`, which is `confined` — so a
member granted `music` and not `files` gets a working player, working
favourites and an empty library, and `files` is not a grant that can simply be
handed over, since authorize.ts drops a confined grant for an account with no
Linux user.

First cross-plugin PERMISSION dependency in the system, and a different animal
from offscale's. That one is ConsoleView → TerminalView: code, resolved at
build time, worst case a plugin that will not compile. This resolves at request
time, per account, and fails as a screen that renders perfectly and shows
nothing.

Three shapes written down, none chosen. Moving the listing into the sidecar is
probably right — it is the same rule offscale follows, that the sidecar absorbs
everything — but it is tomorrow's call.

Recorded now rather than trusted to memory: it was found by reading, after the
extraction was already verified and pushed, so nothing was going to surface it
again on its own.
This commit is contained in:
2026-08-15 03:09:50 +00:00
parent e930586878
commit 8bfcd40bd2
+31
View File
@@ -205,6 +205,37 @@ exist before.
## Still open
### The library browser reads the filesystem, not this plugin — and that is a PERMISSION dependency
Found 2026-08-15, after the extraction landed, by reading the code rather than by anything failing.
`MusicBrowser.tsx` lists folders with `GET /file-browser/ls`, not through the music sidecar
(`MusicBrowser.tsx:63,81`). `/file-browser` belongs to the **`files`** capability, and `files` is
**`confined`** — so:
- a member granted `music` but not `files` gets a working player, working favourites, and an **empty
library**, because every listing 403s;
- and `files` is not a grant that can simply be handed over. `authorize.ts` drops a confined grant for an
account with no `osUser`, so it means nothing without a per-user Linux account.
This is the first **cross-plugin permission dependency** in the system, and it is a different animal from
the one offscale has. Offscale's `ConsoleView``TerminalView` is a CODE dependency: it resolves at build
time, and the worst case is a plugin that will not compile. This one resolves at request time, per
account, and its failure mode is a screen that renders perfectly and shows nothing.
Three possible shapes, none chosen:
1. **The sidecar lists.** Music already walks the library for its index — `GET /music/ls` would put the
listing behind the `music` permission where it belongs, and the plugin stops needing `files` at all.
Most self-contained, and the most work.
2. **The manifest declares a permission dependency**, and the platform refuses the grant or warns. Honest,
but it makes one plugin's grant conditional on another capability, which is new machinery.
3. **Leave it and document it** — a member needs `files` too. Cheapest, and it quietly ties a music grant
to a Linux account, which is a much bigger commitment than the owner is agreeing to on that page.
(1) is probably right, and it is the same shape as offscale's rule that the sidecar absorbs everything.
Not tonight's call.
- **`hasPersonalWrites` reads `c.personal` only**, so the permissions API reports `false` for a plugin
that declares the same thing through `readOnlyWrites`. Nothing renders the field, so it is dead on the
wire — noted rather than fixed.