You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The cockpit is read-only by contract. v0.5.0 makes exactly one surgical exception: editing model-per-role routing from the UI. The write target is the adapter's existing routing knobs — gates.json → budget.orchestrator_model, budget.worker_model, budget.explorer_model, budget.reviewer_model, and budget.reviewer_models.<lens>. Everything else in gates.json (gates, lenses, consensus, merge policy, budgets, protected paths, …) stays read-only — this is NOT a general config editor, and the server must enforce that, not the UI.
Because gates.json is versioned and reviewed like code, edits must land as git commits, not silent file mutations.
Proposal
POST /api/config/models in cockpit-serve.sh, accepting only the five routing keys above. Strict server-side allowlist: any other key → 400, file untouched. Values validated against known model aliases/ids (haiku / sonnet / opus / full claude-* ids); anything else → 400. Malformed body → 400.
On accept: read-modify-write gates.json preserving key order and formatting (patch the parsed tree, don't re-serialize wholesale), then git commit that single file on the current branch with a clearly attributed message, e.g. chore(cockpit): route reviewer:security -> opus plus a via-cockpit trailer. No push in this endpoint — the owner's normal flow (or the loop's) carries it. The armed loop picks the change up on its next tick since it reads the local checkout.
Dirty-tree guard: refuse (409) if gates.json already has uncommitted local modifications — never bundle unrelated changes into the cockpit's commit.
UI: the existing "Model / skill routing" table becomes editable selects prefilled from the state JSON, with a per-role save → POST → re-fetch cycle and inline error surfacing. Agent-frontmatter model: defaults render read-only alongside, labeled as plugin-owned.
A valid POST updates exactly the requested keys in gates.json, commits only that file, and the commit message attributes the cockpit; the routing table reflects the new values after re-fetch.
Non-allowlisted key, unknown model value, or malformed body → 400 with a machine-readable error; file and git state untouched.
Dirty gates.json → 409, nothing committed.
Writes refused when not bound to loopback, and when COCKPIT_READONLY=1.
Tests cover accept / reject / dirty-tree / readonly paths against a temp-dir repo seam (never the real repo config), fixtures-only, no network.
Blocked by #206
Context
The cockpit is read-only by contract. v0.5.0 makes exactly one surgical exception: editing model-per-role routing from the UI. The write target is the adapter's existing routing knobs —
gates.json→budget.orchestrator_model,budget.worker_model,budget.explorer_model,budget.reviewer_model, andbudget.reviewer_models.<lens>. Everything else ingates.json(gates, lenses, consensus, merge policy, budgets, protected paths, …) stays read-only — this is NOT a general config editor, and the server must enforce that, not the UI.Because
gates.jsonis versioned and reviewed like code, edits must land as git commits, not silent file mutations.Proposal
POST /api/config/modelsincockpit-serve.sh, accepting only the five routing keys above. Strict server-side allowlist: any other key → 400, file untouched. Values validated against known model aliases/ids (haiku/sonnet/opus/ fullclaude-*ids); anything else → 400. Malformed body → 400.gates.jsonpreserving key order and formatting (patch the parsed tree, don't re-serialize wholesale), thengit committhat single file on the current branch with a clearly attributed message, e.g.chore(cockpit): route reviewer:security -> opusplus a via-cockpit trailer. No push in this endpoint — the owner's normal flow (or the loop's) carries it. The armed loop picks the change up on its next tick since it reads the local checkout.gates.jsonalready has uncommitted local modifications — never bundle unrelated changes into the cockpit's commit.model:defaults render read-only alongside, labeled as plugin-owned.127.0.0.1(today's default). When Cloudflare exposure lands (v0.4.0: Cockpit: per-project Cloudflare exposure — cockpit-expose skill (named tunnel, Access-gated DNS, loop-scoped unit) #143), writes stay refused behind a tunnel unless origin JWT validation (Cockpit: defense-in-depth — validate Cf-Access-Jwt-Assertion at the origin when tunneled #144) is verified — enforced server-side per request, not a UI toggle. Localhost read-only mode via an env/flag (COCKPIT_READONLY=1) for cautious setups.Acceptance criteria
gates.json, commits only that file, and the commit message attributes the cockpit; the routing table reflects the new values after re-fetch.gates.json→ 409, nothing committed.COCKPIT_READONLY=1.Notes