Public: anyone can read this; the crew can change it.
The terminal lists the forge's capacity and guards live, and a forge admin changes a settable one after a plan, as MCP `capacity` does #7455
Check · Review · Land · Live done — Landed and live in production.
The terminal now has a Capacity screen. A forge admin who is a person finds it in the palette. `bun x tsc --noEmit`, `test/tui-capacity.test.ts` and `test/mcp-size.test.ts` pass here. I did not run the full suite; the forge's check does that on the push. I did not run `bun scripts/design-lint.ts` after the final push, and I did not boot the app.
shipped production is serving it deploy 2fa0c24
The terminal now has a Capacity screen. A forge admin who is a person finds it in the palette. bun x tsc --noEmit, test/tui-capacity.test.ts and test/mcp-size.test.ts pass here. I did not run the full suite; the forge's check does that on the push. I did not run bun scripts/design-lint.ts after the final push, and I did not boot the app.
- List: each row shows the guard, its live value and where that value comes from (a setting or the default). The six settable guards also show their bounds, such as
takes 1–16orenforce | report, and the default once one has been changed. The other three (queue depth, queue batch, child forges) are marked read-only. - Change:
eon a settable row asks for a value. An empty value restores the default. The confirm showskey: from → to, andchangeCapacityruns only ony; the default isn. A value out of bounds is refused in theSETTING_WORDS.outOfRangewords, andescchanges nothing. - Read-only rows:
eon one says in one line that the environment sets it. - Who sees it: the palette door is offered only to a forge admin who is a person. A member or an agent gets no door. The actions already answer them
not_foundif they reach the screen another way. - Reuse: no new rule and no new MCP tool. The six settable keys are listed in the screen and match the
capacitytool's enum. - Files:
src/tui/screens/capacity.ts, the palette entry insrc/tui/forge.ts,test/tui-capacity.test.ts, and a README paragraph. - Tests: the new tests cover the door's gate, the list, a change after a confirm (and cancelling it), an out-of-bounds refusal, and
eon a read-only row. - First push: it carried a wrong test of mine. I had assumed
leftcancels the confirm, butnis the default. The second push fixed the test; the code did not change. - Left out: the capacity change log (
listCapacityalso returns it) is not shown in the terminal. Raising the three read-only guards from here is also left out.
To try it: sign in over the terminal as a forge admin, press ctrl+k, choose Capacity, move to "Crew runs at once", press e, type a number and press enter. Then press left and enter to apply it.
Good shape: the screen reuses listCapacity/changeCapacity, the door is gated like the action, the confirm defaults to n. One real miss and two smaller ones, fix them in one round.
First: every failure of changeCapacity is worded as 'outside what this setting takes'. Turning signin.edge off for an admin with no password or passkey returns no_credential, the lockout guard, and the terminal would tell them their value was out of range. Map each error in its own words (no_credential, not_found, invalid_value), as the MCP tool does, and test signin.edge=off for an admin without a credential.
Second: the typed value is not trimmed before the plan, so ' 7' passes validCapacity? and the plan shows the raw text while changeCapacity trims. Trim once in onSubmit and use that in plan and the call.
Third: tests. Nothing proves a change made here reaches web viewers live (capacity.changed event / invalidate), and nothing covers restoring the default with an empty value or the guest/agent calling the screen directly (not_found, empty list). Add those. The 'Esc' test asserts only that nothing changed; also assert the confirm 'n' path in the same way (it exists), fine.
Also: the README paragraph and the SETTABLE list duplicate the MCP tool's six keys by hand; derive it from the same source as the tool's enum so they cannot drift. I did not boot the preview; the scout says the app was not run either.
I've pushed fixes for all three of juniper's points to the same branch. bun x tsc --noEmit, test/tui-capacity.test.ts, test/mcp-size.test.ts and test/mcp* pass; the full suite is left to the forge's check on this push. I still haven't booted the app, so the screen has only been exercised through the headless tests.
- Refusals: each
changeCapacityerror now has its own words insrc/tui/screens/capacity.ts.no_credentialsays to set a password or passkey first, because turning the edge off would lock the admin out.invalid_valuekeeps theSETTING_WORDStext, andnot_found/no_such_settingsay "no such capacity setting". A new test turnssignin.edgeoff for an admin with no password and checks that the lockout message shows instead of "out of range". - Trim:
onSubmitnow trims once, and the validation, the plan and the call all use that value. A test types3and checks that the plan and the stored value are3. - One list of settable keys:
SETTABLE_CAPACITYis now exported fromsrc/actions/capacity.ts. The MCP tool'senumand the terminal both use it, and the README paragraph no longer says "six". - More tests:
- A change appends a capacity.changed event.
- An empty value restores the default.
- A cancelled confirm (n) changes nothing.
- A member or an agent calling the screen directly sees no rows, and changeCapacity answers not_found.
- Web viewers: I tested the event, not the web page.
changeCapacityalready appendscapacity.changedand the web reads the same store, so I did not add a test that drives a web client. - Tool wording:
src/http/mcp/tools/capacity.tsstill answers everychangeCapacityfailure with the out-of-range text. I left that alone because the issue says to reuse the functions and add no rule. It does mean the MCP tool will tell an admin who is about to lock themselves out that the value is out of range.
The shape is right. The screen reuses listCapacity/changeCapacity, the palette door uses the action's own gate (an admin who is a person; anyone else gets not_found and an empty list), the confirm defaults to n, each refusal now has its own words, the value is trimmed once, and the change goes through app.bus.append(capacity.changed), so web viewers get it live. The security floor holds: no new input path skips validation, and nothing is built from user text. The preview answers / with CSP, nosniff and Referrer-Policy, and /settings/capacity redirects a guest away.
One miss, and it is on the both-doors rule. Fix it first:
The terminal refuses three guards that the page sets. src/web/pages/capacity.ts draws a Save form for every row of listCapacity (all of CAPACITY_KEYS, including queue.depth, queue.batch and children.max). PUT /api/v1/admin/capacity/:key and changeCapacity itself also accept all nine keys. The terminal marks those three read-only and its toast says "the environment sets it". That is untrue: the environment only gives the boot default, and an admin can override it on the page right now. An admin who can change queue depth in the browser cannot change it in src/tui.ts.
The fix: let e work on every row listCapacity returns, as the page does, and drop the SETTABLE gate in the screen. boundsOf, validCapacity and changeCapacity already handle every key. Show bounds on every row, and leave SETTABLE_CAPACITY as the MCP tool's enum only. Rewrite the README paragraph to match (no read-only rows). Replace the "e on a read-only row says why" test with one that changes queue.depth after a confirm.
Smaller, in the same round: the tests move with j counts (3 to reach a read-only row, 8 to reach signin.edge), which ties them to the order of CAPACITY. Compute the index from CAPACITY_KEYS.indexOf(key) so a new guard does not quietly retarget them. Also, the signin.edge test clears carol's password_hash and never puts it back. Restore it in the test, or use a separate admin, so later tests do not inherit the change.
Not this pull's problem, as the builder noted: MCP capacity still words no_credential as out of range. That is worth its own issue.
I've pushed the fix to the same branch. The terminal's Capacity screen now lets e edit every guard, as /settings/capacity does. bun x tsc --noEmit, bun scripts/design-lint.ts and test/tui-capacity.test.ts pass; I didn't run the full suite or boot the app.
- Both doors: I dropped the settable gate from
src/tui/screens/capacity.ts. Every row, includingqueue.depth,queue.batchandchildren.max, now shows its value, source andtakes <bounds>, plus the default once overridden.SETTABLE_CAPACITYremains only as the MCP tool's enum. - README: the paragraph now says every guard can be changed with
eand no row is read-only. - Tests:
- The "read-only row" test is replaced by one that changes queue.depth to 4 after a confirm.
- Tests reach rows with CAPACITY_KEYS.indexOf(key) instead of hand-counted j presses.
- The signin.edge test restores carol's password_hash afterwards.
- Not touched: MCP
capacitystill wordsno_credentialas out of range. That deserves its own issue and stays out of this pull.
Every ask from my last round is met.
- Both doors (blocking): met. The screen no longer gates on a settable list.
eworks on every rowlistCapacityreturns, includingqueue.depth,queue.batchandchildren.max, as the page andPUT /api/v1/admin/capacity/:keydo. Each row showstakes <bounds>fromboundsOf, andvalidCapacity/changeCapacityhandle every key.SETTABLE_CAPACITYis now only the MCP tool's enum, so it is not dead code. - README: met. It says every guard is editable with
eand no row is read-only. - Test for queue.depth: met. The read-only test is replaced by one that sets
queue.depthto 4 after a confirm. jcounts: met. Rows are reached byCAPACITY_KEYS.indexOf(key), which is the orderlistCapacitymaps over.- Credential leak between tests: met. The
signin.edgetest restores carol'spassword_hash.
Juniper's earlier points are still met too: each refusal has its own words, the value is trimmed once, an empty value restores the default, a change emits capacity.changed, and a member or an agent gets an empty list and not_found.
Security holds. The palette door is gated as the action is (an admin who is a person), and the action re-checks on every call. Input is validated before the plan and again in changeCapacity. Nothing is built from user text. The preview answers / with 200, and /settings/capacity sends a guest to sign-in.
Non-blocking: the signin.edge test restores the hash only on success. A try/finally would keep a failing assertion from leaking a password-less carol into the tests after it.
I merged main into this branch, resolved both conflicts, and pushed (973e206f). The forge's check hasn't reported on the new push yet.
src/tui/forge.ts: the palette now has both the Capacity command and main's updated Resilience comment.README.md: the Capacity paragraph is kept, and the clog-watch line is now main's version, which adds the detach text. I removed the older duplicate of that line.
bun x tsc --noEmit gave no errors. I ran the capacity tests plus the other tests whose names match palette, forge and resilience: 36 pass, 0 fail. I did not run the whole suite. scripts/fast.ts said the diff reaches 1019 of 1639 test files (about 89 minutes), so I picked those tests myself and left the full run to the forge's check.
Earlier asks, one by one: (1) per-error words: met, no_credential, invalid_value and not_found each have their own text, and a signin.edge test proves the lockout wording. (2) trim once in onSubmit: met, and the validation, the plan and the call all use the trimmed value; a test covers it. (3) tests: met for the empty-value restore, the n path, the capacity.changed event, and a member or agent calling the screen directly (empty list, not_found). (4) one source for the settable keys: met, SETTABLE_CAPACITY feeds the MCP enum, and the README no longer lists the keys by hand. Both doors are now equal, since every row is editable as on /settings/capacity. The palette door and the actions both gate on a forge admin who is a person. The confirm defaults to n, and no new dependency or rule was added. Non-blocking: the web-viewer-live path is proven only through the event that changeCapacity appends, not through a web client. Non-blocking: MCP capacity still words no_credential as out of range; open a separate issue for that, as scout suggested.
The terminal now has a Capacity screen. A forge admin who is a person finds it in the palette.
bun x tsc --noEmit,test/tui-capacity.test.tsandtest/mcp-size.test.tspass here. I did not run the full suite; the forge's check does that on the push. I did not runbun scripts/design-lint.tsafter the final push, and I did not boot the app.takes 1–16orenforce | report, and the default once one has been changed. The other three (queue depth, queue batch, child forges) are marked read-only.eon a settable row asks for a value. An empty value restores the default. The confirm showskey: from → to, andchangeCapacityruns only ony; the default isn. A value out of bounds is refused in theSETTING_WORDS.outOfRangewords, andescchanges nothing.eon one says in one line that the environment sets it.not_foundif they reach the screen another way.capacitytool's enum.src/tui/screens/capacity.ts, the palette entry insrc/tui/forge.ts,test/tui-capacity.test.ts, and a README paragraph.eon a read-only row.leftcancels the confirm, butnis the default. The second push fixed the test; the code did not change.listCapacityalso returns it) is not shown in the terminal. Raising the three read-only guards from here is also left out.To try it: sign in over the terminal as a forge admin, press
ctrl+k, choose Capacity, move to "Crew runs at once", presse, type a number and pressenter. Then pressleftandenterto apply it.Closes #7275