0091 — A paired device may start, pause, resume and unwind a project

Status: proposed · Date: 2026-10-23 · Cites: ADR-0067, ADR-0068; sub-doctrines 10.c, 12.c, 12.j · Related: ADR-0054, ADR-0085

Context

ADR-0068 gave a paired device the scopes view, answer, spend, run, bump and workflows, and the device policy of the mobile and desktop clients refused four things outright: starting a project, starting its worker, pausing work, and unwinding a project. The result was a first run that dead-ended: a person with only the phone or the desktop app saw "No projects yet" and could do nothing about it, because the only way to start one was a terminal or the editor on the host.

The run scope already existed ("start, stop or wind down work") and abandon and worker were already run-scoped for a command sent to the workflows (ADR-0085). What was missing was a route, and the decision that a device may use it. The operator has decided that it may.

Decision

Four routes, each needing run, and no new scope:

Route Does
POST /api/v1/projects starts a project from {name, intake} and gives it a worker
POST /api/v1/projects/{id}/work/start starts, or starts again, that project's worker
POST /api/v1/projects/{id}/work/pause stops that project's own workers and no one else's
POST /api/v1/projects/{id}/abandon unwinds it, or with dry_run says what it would cancel

Delegation. The hub owns no write of its own (ADR-0067). Each route runs the command the CLI already offers (vibey new --json, retry --keep-workers --start-worker, recover --project --stop-workers, abandon --json) on the host, so it cannot drift from them and cannot route around a check they make: the one-live-project-per-checkout hold, the Sabbath window, the runtime config's own refusals.

A device never names a path. The request carries a name and a sentence of intake; both are validated by the service (a name is at most 64 characters of letters, digits, dot, dash and underscore, starting with a letter or digit, so it can be neither a path nor a flag), an unknown key is a 422, and the folder is <projects_root>/<name>, made by the host. [hub] projects_root is unset by default: with none declared, a device can pause, resume and unwind but cannot start anything, and is told why. A name whose folder exists is refused, never adopted.

Reversibility is kept visible. Pause stops only that project's workers; a worker that serves every project is left running and named, and the leases are not reset while it lives (a job would run twice). Unwind is the one irreversible act: the clients show a dry run, ask for on-device confirmation (the biometric prompt spending already uses), and require a reason that is recorded on the ledger under the device's name.

What does not change. NEVER_FROM_THE_HUB is untouched: paid use, caps, the DSN, migrations, the canon and state sync stay on the host. A device that was not paired with run still cannot do any of this; the operator grants the scope when pairing and may withdraw it.

Consequences

A phone or the desktop app is now enough to start, steer and end a project, which is the first-run the audience needs. The blast radius of a stolen device with run grows from "can wind down work that exists" to "can also create folders under projects_root and start workers there", bounded by the folder, the name rules, the caps the host declared, and the revocability of the device (ADR-0068). An operator who does not want this simply never pairs a device with run, or leaves projects_root unset.