# Plugin dependency scope **Built 2026-08-21.** A plugin declares WHO has to be able to run each host binary, and the platform installs and checks accordingly. ## The bug `os-deps.ts` probes a dependency by asking the owner's login shell where the binary is. That is the right question for most plugins and the wrong one for some, and nothing distinguished them — a per-user answer was reported as a fact about the machine. It matters because `confined` plugins do their work as **the member's own Linux user**, in their own home. A binary in the owner's `~/.local/bin` is not on that user's PATH, and reaching it means traversing the owner's home directory. On alpha it happened to work. `/home/pastilhas` is mode **751** — world-traversable, not readable — so an absolute path resolved for members. Mode 750, which is the more usual default, would have broken every member's binary with nothing reporting anything: the install would still say satisfied, because the install only ever asked the owner. Working by accident is the part worth fixing. Nobody chose 751. ## The field ```ts osDependencies: [{ binary: 'iris', reason: '…', packages: {…}, scope: 'shared' }] ``` `owner` is the default and stays right for most things — anything an `execution` plugin uses runs as the owner in the owner's home by definition. `shared` says every account with a Linux user must be able to run it, which changes both the install target and the check. An unrecognised value is a manifest error rather than a silent fallback to `owner`, because the fallback is the permissive one. ## What changes when it is `shared` **Where it is looked for.** System directories only — `/usr/local/bin`, `/usr/bin`, `/bin`, and the sbin trio. Deliberately not the owner's login shell: that shell finds the copy in the owner's home and reports the dependency satisfied, which is the exact false answer this exists to stop. **What counts as reachable.** Two things, and checking only the first is how this hid: - the file is world-executable (`0o001` — group is not enough, members are not in the owner's group) - **every directory above it is traversable**, walked to the root A binary at 755 inside a home at 700 is unreachable, and `stat` on the file alone says it is fine. **Where a release binary is installed.** `/usr/local/bin`, via `sudo -n install -m 0755`. The download is still staged in the owner's own space — fetching straight into a root-owned directory would mean running the download as root, which is a bigger ask than moving the result. `-n` so a plugin install never blocks on an invisible password prompt. **What blocks an install.** A shared dependency that is present and working but unreachable gets its own state, `unreachable`, and its own refusal — checked before everything else, because no package manager fixes it. The binary is installed; what is wrong is where it lives. Reinstalling the package would report success and change nothing. ## What this deliberately does not do **It does not gate execution on a grant.** A binary in `/usr/local/bin` is runnable by any account with a shell, whether or not they hold the plugin's permission. The kernel does not know about `role_permissions` and never will. That is the same boundary `ffmpeg` and everything else in `/usr/bin` already has, and it is the right one: the plugin's **data** is gated by the permission system, its **executable** is not. For iris it is benign — a member who runs it captures their own `$HOME` profile and their own sessions, which is the isolation the wrapper already depends on. If a binary must not merely be _reachable_ but _unusable_ without a grant, `scope` is not the mechanism. That needs the plugin's own surface behind the permission gate, and the binary not on anyone's PATH. ## Iris The motivating case, and still the only `shared` one. `PLUGIN.md` had already recorded it as open item 3 — "the binary lands in the owner's `~/.local/bin`, and a confined member's agent cannot read it there" — but that had not been connected to the probe being owner-scoped, so the install reported success either way. `iris` and `iris-browser` are built by the sidecar rather than fetched, so declaring `scope: 'shared'` makes the probe honest about who can run them; moving the sidecar's build output into `/usr/local/bin` is the remaining half, and it is a change to the sidecar rather than to this. ## Status | | | | ----- | -------------------------------------------------------------------------- | | Built | `scope` on a dependency, validated, defaulting to `owner` | | Built | shared probing on system paths, with a traversal walk to the root | | Built | shared release binaries installed to `/usr/local/bin` via `sudo -n` | | Built | an unreachable shared dependency refuses the install, naming the directory | | Open | iris's sidecar still builds into `~/.local/bin` — see above |