Apply [bootstrap.packages] at provisioning time via a root package applier #276

Merged
lucas merged 1 commit from agent/issue-274-bootstrap-packages into main 2026-11-13 15:19:42 +01:00
Collaborator

Closes: #274

A workspace's mise config could declare [bootstrap.packages] and nothing ever installed them — mise install applies only [tools] and exits 0, so the workspace reported ok while the project's headers and .pc files were absent.

What it does

  • packages/mise-apply (new workspace package) is the single place that builds the privileged mise command lines: inspect with bootstrap packages status --json / bootstrap files status --json, apply files then packages, then verify with bootstrap packages status --missing. Nothing declared → nothing runs. A failing files apply stops before packages. Applies are serialised process-wide (the dpkg lock is machine-wide). Only those two parts are applied through their own subcommands — never mise bootstrap --only …, which would also run the config's hooks and its bootstrap task.
  • apps/packages (new app) is the root-side applier: POST /apply with a workspace id on a unix socket (OMP_PACKAGES_SOCKET, default /run/omp-packages/packages.sock). The id is validated against ^[A-Za-z0-9_-]{1,64}$ before any path is derived; the scratch root is built here, never taken from the wire. It is the one process the entrypoint does not drop privileges for.
  • apps/server/src/mise/packages-client.ts dials that socket and falls back to applying in-process when it is absent (bare metal, dev). A daemon that answered is never retried through the fallback.
  • Provisioning applies packages before mise install and reports the outcome in a new MiseStatus.packages, absent when the config declares nothing. A package failure never fails or blocks the tools install.
  • The agent's static mise guidance was rewritten (its old opening — "anything apt installed would be lost" — is now wrong), and the per-turn status line gains a packages sentence, including "Do not guess at the API of a library whose headers are missing" on failure.
  • Workspaces box shows packages: <phase> · <keys> plus the error; docs in docs/configuration.md and docs/deployment.md; OMP_PACKAGES_SOCKET in the env schema; @omp-deck/mise-apply bundled for the published package (prepack.mjs BUNDLED).

Verification

  • bun run typecheck clean, bun test 1710 pass / 0 fail (new tests: packages/mise-apply, apps/packages, apps/server/src/mise/system-context.test.ts).
  • Ran the real module against the real pinned mise (v2026.11.7) in this container:
    • config with only [tools] → {"declared":false,"ok":true,"applied":[]}, no apply command run;
    • config declaring apt:libwireplumber-0.5-dev plus a [bootstrap.files] entry → {"declared":true,"ok":false,"applied":["apt:libwireplumber-0.5-dev"],"error":"mise ERROR sudo not found. Re-run this mise command as root…"} — i.e. the unprivileged fallback path behaves as designed and the package keys parse out of the real JSON ({"<manager>":{"packages":[{"package":…}]}}).

The plan's two "confirm first" assumptions are settled: the status JSON shape is as above, and trusted_config_paths (set via MISE_TRUSTED_CONFIG_PATHS, plus MISE_YES=1) is a global-only setting with no owner-uid check in mise's trust model, so root reading a config owned by the deck's UID needs no copy step.

Not verified here, because it needs a built image and a root container: the end-to-end container run (a real apt install reaching packages: ok, and re-application after --force-recreate).

Deliberate property

The applier has no policy gate — it applies whatever the workspace's mise config declares, and an agent can write that config. A workspace config can therefore run code as root in the container (apt maintainer scripts, [bootstrap.files] templates calling exec()), which makes the broker's UID separation advisory for an agent working in such a workspace. This is stated in docs/configuration.md, docs/deployment.md, the entrypoint header and the CHANGELOG.

Closes: #274 A workspace's mise config could declare `[bootstrap.packages]` and nothing ever installed them — `mise install` applies only `[tools]` and exits 0, so the workspace reported `ok` while the project's headers and `.pc` files were absent. ## What it does - **`packages/mise-apply`** (new workspace package) is the single place that builds the privileged mise command lines: inspect with `bootstrap packages status --json` / `bootstrap files status --json`, apply `files` then `packages`, then verify with `bootstrap packages status --missing`. Nothing declared → nothing runs. A failing `files apply` stops before packages. Applies are serialised process-wide (the dpkg lock is machine-wide). Only those two parts are applied through their own subcommands — never `mise bootstrap --only …`, which would also run the config's hooks and its `bootstrap` task. - **`apps/packages`** (new app) is the root-side applier: `POST /apply` with a workspace id on a unix socket (`OMP_PACKAGES_SOCKET`, default `/run/omp-packages/packages.sock`). The id is validated against `^[A-Za-z0-9_-]{1,64}$` before any path is derived; the scratch root is built here, never taken from the wire. It is the one process the entrypoint does not drop privileges for. - **`apps/server/src/mise/packages-client.ts`** dials that socket and falls back to applying in-process when it is absent (bare metal, dev). A daemon that answered is never retried through the fallback. - Provisioning applies packages **before** `mise install` and reports the outcome in a new `MiseStatus.packages`, absent when the config declares nothing. A package failure never fails or blocks the tools install. - The agent's static mise guidance was rewritten (its old opening — "anything apt installed would be lost" — is now wrong), and the per-turn status line gains a packages sentence, including *"Do not guess at the API of a library whose headers are missing"* on failure. - Workspaces box shows `packages: <phase> · <keys>` plus the error; docs in `docs/configuration.md` and `docs/deployment.md`; `OMP_PACKAGES_SOCKET` in the env schema; `@omp-deck/mise-apply` bundled for the published package (`prepack.mjs` `BUNDLED`). ## Verification - `bun run typecheck` clean, `bun test` 1710 pass / 0 fail (new tests: `packages/mise-apply`, `apps/packages`, `apps/server/src/mise/system-context.test.ts`). - Ran the real module against the real pinned mise (v2026.11.7) in this container: - config with only `[tools]` → `{"declared":false,"ok":true,"applied":[]}`, no apply command run; - config declaring `apt:libwireplumber-0.5-dev` plus a `[bootstrap.files]` entry → `{"declared":true,"ok":false,"applied":["apt:libwireplumber-0.5-dev"],"error":"mise ERROR sudo not found. Re-run this mise command as root…"}` — i.e. the unprivileged fallback path behaves as designed and the package keys parse out of the real JSON (`{"<manager>":{"packages":[{"package":…}]}}`). The plan's two "confirm first" assumptions are settled: the status JSON shape is as above, and `trusted_config_paths` (set via `MISE_TRUSTED_CONFIG_PATHS`, plus `MISE_YES=1`) is a global-only setting with no owner-uid check in mise's trust model, so root reading a config owned by the deck's UID needs no copy step. Not verified here, because it needs a built image and a root container: the end-to-end container run (a real apt install reaching `packages: ok`, and re-application after `--force-recreate`). ## Deliberate property The applier has **no policy gate** — it applies whatever the workspace's mise config declares, and an agent can write that config. A workspace config can therefore run code as root in the container (apt maintainer scripts, `[bootstrap.files]` templates calling `exec()`), which makes the broker's UID separation advisory for an agent working in such a workspace. This is stated in `docs/configuration.md`, `docs/deployment.md`, the entrypoint header and the CHANGELOG.
A project's mise config could declare system packages under
`[bootstrap.packages]` and nothing ever installed them: `mise install`
only applies `[tools]` and exits 0, so the workspace reported `ok` while
the project's headers, `.pc` files and docs were absent, and a session
had no way to verify a library's API.

The deck now applies that config's `[bootstrap.files]` and
`[bootstrap.packages]` before installing its tools. In the container a
root-side applier does it, because mise's apt manager needs root and a
session has neither root nor sudo; elsewhere the deck runs the same mise
commands as its own user. Only those two parts are ever applied, each
through its own subcommand, so the config's hooks and its `bootstrap`
task — arbitrary root command execution — never run.

The outcome is reported apart from the tools install, in the Workspaces
box and in the agent's per-turn status line, so a workspace whose
libraries are missing is no longer green; a package failure neither
fails nor blocks the tools install.

This is a deliberate trade: the applier has no policy gate, and an agent
can write the config it applies, so a workspace config can run code as
root in the container. The broker's UID separation therefore becomes
advisory for an agent working in such a workspace.

Co-Authored-By: Claude Opus 5 via omp-deck
lucas approved these changes 2026-11-13 15:19:37 +01:00
lucas merged commit 362ececc7d into main 2026-11-13 15:19:42 +01:00
lucas deleted branch agent/issue-274-bootstrap-packages 2026-11-13 15:19:42 +01:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
lucas/omp-deck!276
No description provided.