0095: Team policies set each runtime line and finer switches
On this page
Status: accepted. Amends 0093.
Context
0093 let a team set one lowest version per runtime. A team whose projects run on different lines (one on PHP 8.3, one on 8.5) couldn't require a patched 8.3 without banning it, and "8.5.12 or newer" refused 8.5 itself, since Short Leash installs lines, not patches. Only PHP was checked at all: installs came named as the app shows them ("Node") and the policy's ids are lowercase. Nothing checked a site as it ran. Features were switched off as whole Pro features, so a team couldn't turn meeting notes off and keep dictation, and coding assistants (leash mcp), pushing and running code had no switch.
Older apps read rules.runtimes as one optional version per runtime, and a policy they can't parse is dropped whole (and silently, at start), so the shape of that key can't change.
Decision
- Lines. The policy gains
rules.runtimeLines.{id}.{line} = {allowed, min}, the shape services already use. A line is the minor (PHP, Python, Go, Ruby) or major (Node) Short Leash installs. A line with no rule followsruntimes. - Older apps. The website keeps sending
runtimes.{id}, derived: the lowest allowed line as"<line>.0"(which older apps' comparison never misreads as refusing that line),nullwhen every line is allowed,"999.0.0"when none is. Rules that can't be said that way (a line off between two allowed ones, a patch floor) raiseneeds, so the Policies page counts the computers that won't follow them. Nothing gates older apps. - Where lines are checked. Installing and updating a line check only that it's allowed, so a line below its patch floor can still be brought up to date. Running checks the line and the release installed for it: each PHP request, each app site start, and pinning a site to a version. A stricter policy stops app sites on lines it holds back, and the Runtimes screen says why on the row.
- Choices Short Leash makes. Where Short Leash picks a version from a range, it picks inside what the policy allows:
composer.json's newest installed fit,go.mod's lowest fit, and a service added without a version (the first allowed in the catalog, recommended first). A default on a held line moves, when a policy arrives or leashd starts, to the newest allowed line, an installed one first; with no line allowed it stays, since everything on it is refused anyway. A version asked for exactly (a pin,leash.toml,.nvmrc,.python-version,.ruby-version, a version a file or import names) is never swapped: it's refused with the reason, which the site shows on its card, and no picker offers it. Applying a project, or Use leash.toml's, keeps the site's own version over the file's held one; an import leaves a held PHP unticked. - Left out, not shown off. What the policy turns off whole (its switches, Pair, Webhooks, Calls, Local AI, Mail, sharing, a runtime line, a service version) isn't in the app: its pages, Settings sections, buttons, Go To entries and picker options are left out, so a team's limits don't read as a wall of switched-off features.
PolicyView.offnames them,setcarries only the limits on what's left on, and a runtime row saysteamOffwhen its line is off (not only an older patch). Settings › License lists all of it. What someone has to act on still shows: a site on a held version says why and offers the others, and terminals already open stay. Short Leash's refusals are unchanged; hiding isn't enforcement. - What's reported. The inventory leashd sends gains each runtime line its sites use, for every runtime a policy covers, with the release installed, so the website's report of who a rule would stop compares like with like.
- Switches.
features.disabledtakesmeetings,dictation,chat,assist,localModel(the model endpoint anddelegate_to_local),mcp,agents(handing work to the person's own Claude Code or Codex, 0094, which sends it to that agent's company),git(pushing, from Changes or a workspace merge) andrunCode(scratchpads,run_code,run_process,run_artisan, new terminals).aistill turns all five AI ones off, and the website addsaiwhen all five are off so older apps follow. Each is checked where its feature starts;leash mcprefuses every tool whilemcpis off, and the tools a switch covers while it's off. A stricter policy stops notes being taken and the endpoint; terminals already open stay, since stopping a shell loses its contents.
Consequences
- Node, Python, Go and Ruby floors are enforced for the first time, and on running sites as well as installs: sites that started before may stop after the update. The CHANGELOG says so.
- The switches govern Short Leash's own features. Someone with a shell on the computer reaches the same data through the
leashCLI, a terminal outside Short Leash or the PATH shims; like 0093, this is cooperative enforcement, and the Policies page doesn't call it a security boundary. - Each PHP request now reads the policy and the version's
VERSIONfile before its pool, beside the config write it already did. - A moved default stays moved after the policy relaxes: it's a setting the person sees in Runtimes, not one Short Leash quietly puts back.
- The derived
runtimesfloor is a lossy copy for older apps; once enough computers run this version, the website can stop raisingneedsfor it.