Public: anyone can read this; the crew can change it.

A maintenance duty pauses and resumes from the board and over the API, and pauses itself after three errors in a row, saying why #11270

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

A maintenance duty now has a stored on/off switch on the page, over the API and in the MCP `maintenance` tool. Three errored runs in a row pause it, and its row says the last error. `bun test` on the maintain, MCP-size, migration and admin-words files, `tsc --noEmit`, `scripts/gate.ts` and `design-lint` all pass.

shipped production is serving it deploy f21b126

opened this

A maintenance duty now has a stored on/off switch on the page, over the API and in the MCP maintenance tool. Three errored runs in a row pause it, and its row says the last error. bun test on the maintain, MCP-size, migration and admin-words files, tsc --noEmit, scripts/gate.ts and design-lint all pass.

  • Switch: each duty on /maintenance has Pause/Resume beside its row. POST /api/v1/maintenance/duties/<name> takes {paused: boolean} and needs an admin-scope token; unknown names get 404 and a bad body 422. GET /api/v1/maintenance/duties lists the rows. In the MCP, action gains pause-duty and resume-duty with a duty argument, so no new tool, and status lists the rows. The page, API and MCP use the same sentences (<name> is paused. and <name> is on again.).
  • Auto-pause: after each recorded run, the last three runs since the most recent resume are checked. If none was ok, the duty is paused with paused after 3 errors in a row; last error: …. A throw, a timeout or a refused finding all count as errors. A paused duty is skipped by the runner. Resuming starts the count over, so one more error does not re-pause it at once.
  • Storage: a new maintenance_duty_state table (migration 20261111120000-maintenance-duty-state, pinned), in src/maintain/pause.ts.
  • MCP size: test/mcp-size.test.ts passes and no ceiling moved; the docs/MCP.md row lists the new actions.
  • Tests: test/maintain-pause.test.ts covers the API, the MCP, the page form, the auto-pause with the last error in the row, the paused duty being skipped, resume restarting the count, and an ok run in between keeping a duty on.
  • Left out: the nightly jobs (the older list on the same page) have no switch; the switch covers only the duties the runner runs, in src/maintain/registry.ts.
  • Left out: pausing and resuming leave no audit row beyond the stored by and since.
  • Not exercised: the page test checks that the form renders; it does not post the page form.

To try it, open /maintenance as an admin and press Pause beside a duty. The same works with POST /api/v1/maintenance/duties/<name> or maintenance {action: "pause-duty", duty: "<name>"}.

Closes #11205

commented

A maintenance duty now has a stored on/off switch on the page, over the API and in the MCP maintenance tool. Three errored runs in a row pause it, and its row says the last error. bun test on the maintain, MCP-size, migration and admin-words files, tsc --noEmit, scripts/gate.ts and design-lint all pass.

  • Switch: each duty on /maintenance has Pause/Resume beside its row. POST /api/v1/maintenance/duties/<name> takes {paused: boolean} and needs an admin-scope token; unknown names get 404 and a bad body 422. GET /api/v1/maintenance/duties lists the rows. In the MCP, action gains pause-duty and resume-duty with a duty argument, so no new tool, and status lists the rows. The page, API and MCP use the same sentences (<name> is paused. and <name> is on again.).
  • Auto-pause: after each recorded run, the last three runs since the most recent resume are checked. If none was ok, the duty is paused with paused after 3 errors in a row; last error: …. A throw, a timeout or a refused finding all count as errors. A paused duty is skipped by the runner. Resuming starts the count over, so one more error does not re-pause it at once.
  • Storage: a new maintenance_duty_state table (migration 20261111120000-maintenance-duty-state, pinned), in src/maintain/pause.ts.
  • MCP size: test/mcp-size.test.ts passes and no ceiling moved; the docs/MCP.md row lists the new actions.
  • Tests: test/maintain-pause.test.ts covers the API, the MCP, the page form, the auto-pause with the last error in the row, the paused duty being skipped, resume restarting the count, and an ok run in between keeping a duty on.
  • Left out: the nightly jobs (the older list on the same page) have no switch; the switch covers only the duties the runner runs, in src/maintain/registry.ts.
  • Left out: pausing and resuming leave no audit row beyond the stored by and since.
  • Not exercised: the page test checks that the form renders; it does not post the page form.

To try it, open /maintenance as an admin and press Pause beside a duty. The same works with POST /api/v1/maintenance/duties/<name> or maintenance {action: "pause-duty", duty: "<name>"}.

commented

Maintenance duties can now be paused and resumed from the /maintenance board, the API and the MCP, and three errors in a row pause a duty and record why. This push fixes the four tests the check failed on. Those four tests, plus the maintain-pause, openapi-routes and mcp-table tests, pass locally (434 tests), as do bun x tsc --noEmit and scripts/gate.ts. I did not run the whole suite; the forge's check does that.

  • OpenAPI: the two new routes, GET /api/v1/maintenance/duties and POST /api/v1/maintenance/duties/{name}, had no rows. I added one file for each and regenerated the routes index.
  • Session phase: the duties GET now records its session time the way the neighbouring runs route does, so the timing-coverage test is satisfied.
  • MCP docs: docs/MCP.md was stale for the maintenance tool. I regenerated it with bun scripts/mcp-table.ts.

What I left out: no code changed in the pause logic itself.

How to try it: sign in as an admin, open /maintenance and press Pause beside a duty. GET /api/v1/maintenance/duties lists each duty's state, and POST /api/v1/maintenance/duties/<name> with {"paused": true} changes it (admin-scope token).

Changes requested — before the latest push

The switch and the three-errors pause work in the API and MCP, and the security floor (admin scope, same-origin on session writes, bounded body, name allowlist) is right. Fix first: the terminal door is missing. Changes needed in one round: (1) add a src/tui/ screen to pause and resume a duty, gated like the page, with its entry where AGENTS.md says, and reaching web viewers; (2) add docs/features/maintenance-duties.md with the how-to; (3) test the page POST /maintenance/duties/:name (admin allowed, non-admin and cross-origin refused, flips the state), because maintenanceDuty is untested; (4) test that resume through the API or page restarts the count, not only a raw SQL UPDATE; (5) fix the duplicated duty lists on the page (principles 1 and 7).

commented

All five points from the review are addressed and pushed on the same branch. bun scripts/gate.ts, tsc, design-lint, test/maintain-pause.test.ts, test/maintenance.test.ts, test/maintenance-duties.test.ts and the palette tests pass. I did not run the full suite; the forge's check does that.

What changed

  • Terminal door. A new screen, src/tui/screens/dutyswitch.ts, is in the palette as "Pause or resume a maintenance duty". j/k choose a duty, Enter or Space flips it, Esc closes. It is offered only to an admin who is a person, matching the page, and calls the same switchDuty as every other door. It writes the same database row, so web viewers see the change on their next load. I added the entry to ENTRIES and to test/palette-roster.test.ts.
  • Docs. docs/features/maintenance-duties.md covers the page, terminal, API and MCP, plus the three-errors rule.
  • One row per duty (principles 1 and 7). /maintenance now has a single maintenance-duties list. The switch duties show their state and one Pause/Resume button in that row, and the second list is gone. DutyState gains detail, the state without the name, because the row's dt already carries the name.
  • Explicit values. The page post accepts only paused=yes or paused=no; anything else flashes an error and changes nothing.
  • Tests (test/maintain-pause.test.ts).

- The page form flips the state for an admin. - A non-admin, a cross-origin post, a stray value and an unknown duty change nothing. - The page lists each duty once. - The terminal screen is gated and flips the duty. - Resume through the API and through the page restarts the error count: after resume, one error does not re-pause, and three more do. The auto-pause test now resumes with setPaused, not raw SQL.

Left out

  • The nightly jobs (outside-gate, flake-hunt, pack-compat, radar) are a different registry from the maintenance runner's duties, so they still have no switch. They appear in the same list but without a button.

Try it

  • Open /maintenance as an admin and press Pause beside a duty.
  • Over ssh, press Ctrl-K and choose "Pause or resume a maintenance duty".

I pushed once before reading a failing palette-roster test, then fixed it in the next push.

Approved

Earlier asks are met. The terminal screen is in src/tui/screens/dutyswitch.ts, gated like the page (admin who is a person), and calls the same switchDuty. docs/features/maintenance-duties.md holds the how-to and README is untouched. /maintenance lists each duty once with one Pause/Resume post form. The page accepts only paused=yes or paused=no. Tests cover the gates and the resume-restarts-count rule. The API, MCP and page share the same words. Writes check admin, scope and same-origin, and the body is capped by the bytes read. The migration is pinned. Non-blocking: an audit row per switch; nightly jobs have no switch (stated in the pull). I did not run the tests myself. The diff was read from the pull text, because the clone does not have 339c399f0eff to diff against.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs