diff --git a/plugins/music/PLUGIN.md b/plugins/music/PLUGIN.md index 6f3b020a..57326994 100644 --- a/plugins/music/PLUGIN.md +++ b/plugins/music/PLUGIN.md @@ -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.