Public: anyone can read this; the crew can change it.
Egress enforcement cannot be turned to report by environment alone #10784
Check · Review · Land · Live done — Landed and live in production.
The forge's environment can no longer switch cell egress to report: `cells.egress` is now enforce unless an admin sets report on /settings/capacity, and every change to it needs a reason, which is kept next to who made it.
shipped production is serving it deploy 6b2678b
The forge's environment can no longer switch cell egress to report: cells.egress is now enforce unless an admin sets report on /settings/capacity, and every change to it needs a reason, which is kept next to who made it.
- Environment ignored (
src/core/capacity.ts):cells.egressno longer has an environment variable.explicitDefaultskips a setting with no variable, so its default is alwaysenforce. WhenFORGE_CELLS_EGRESSasks for anything other than enforce,egressEnvIgnored()writes one info-level line at boot (src/main.ts). Nothing in red, nothing on any page. - No env fallback left in cells (
src/apps/contain.ts): the oldegressMode()that read the variable is gone. A cell reports only when its caller passesegressMode: "report"from the admin setting.egressNotenow requires the mode as an argument. - Who and why: a new migration (pinned) adds a
reasoncolumn toforge_setting_change.setCapacityandchangeCapacitytake a reason. Acells.egresschange without one is refused withreason_required. The reason is cleaned of control characters and cut to 500 characters, and it also goes into thecapacity.changedevent.
- Web: the cells.egress row has a required "why" field, and the Changes list shows the reason beside who.
- API: PUT /api/v1/admin/capacity/cells.egress takes reason and answers 422 without it.
- MCP: the capacity tool takes reason.
- Terminal: it has nowhere to type a reason, so it refuses the change and says where to make it.
- Docs:
docs/APPS.mdand two code comments now point at the admin setting;docs/MCP.mdwas regenerated from the tool definitions. - Tests: the new
test/egress-default.test.tscovers:
- the default is enforce; - an environment asking for report is ignored, and said in one line; - the page shows nothing about it; - an admin's setting is refused without a reason, then honoured and kept with who and why through both the action and the API.
Existing tests that set cells.egress now pass a reason; the old rule allowed none, so those tests weren't wrong. The report-mode cell test in test/contain.test.ts now passes egressMode: "report" instead of setting the variable.
Not done:
- No reason for other settings: other capacity settings still accept a change without a reason.
- Old records: changes recorded before this have an empty reason.
- Upgrade: a deployment that relied on
FORGE_CELLS_EGRESS=reportgoes back to enforce after the upgrade. An admin who still wants report sets it, with a reason. - Plan not kept on the issue: the
keep_plantool wasn't available in this run.
Checks run here: bun scripts/gate.ts, bun x tsc --noEmit and bun scripts/design-lint.ts all pass. scripts/fast.ts named 2,123 test files (about three hours), so I ran the 179 files that cover capacity, egress, the terminal screens, the web pages, MCP and the migrations: 1,543 pass, 0 fail. The unix-socket and cell suites, including the report-mode test in test/contain.test.ts, are skipped in this sandbox; the forge's check runs them.
To try it: start the forge with FORGE_CELLS_EGRESS=report and you'll see the one "is ignored" line in the boot log. Open /settings/capacity: Egress from cells is enforce. Set it to report with a reason, and the change shows under Changes with your name and the reason.
🤖 Generated with Claude Code
The environment is correctly ignored, the default is enforce, and the admin path keeps who and why. Three things must change before this lands.
- First: a reason is demanded even when moving to enforce or putting the default back (
changeCapacitychecksneedsReason(key)for every change). Tightening is the emergency lever and must never be blocked. The page's "Put back" reset posts no reason, so it now fails with reason_required; the tests only pass because they were all edited to pass a reason. Require a reason only when the new value isreport, and add a test that the reset and a move to enforce work without one. - Both doors: the terminal can no longer change
cells.egressat all and tells the person to go to the page. A screen insrc/tui/must take the reason (a prompt step) and reach the samechangeCapacity. Add a test for it. - Tests: nothing covers the web POST without a reason, the MCP
reasonargument or the terminal's message. Each behaviour added needs a test that fails without it.
Smaller: the help text and the row meta say the same thing three times (principles 1 and 8); say it once, plainly. The reason.slice(0, REASON_MAX) in setCapacity repeats the cut in changeCapacity; keep one.
Egress from cells (cells.egress) is enforced unless an admin sets it to report. Setting it to report now requires a reason, and that reason is kept with who made the change. The FORGE_CELLS_EGRESS environment variable is ignored; at boot the forge logs one line saying so. Turning egress back to enforce, and resetting to the default with Put back, never need a reason.
Review changes in this push:
- Reason only for report (
src/actions/capacity.ts):changeCapacityasks for a reason only when the new value isreport. Enforce and the reset to default go through without one, including the page's Put back. Test edits that had given a reason to enforce and reset calls are reverted, so those tests now prove neither needs one. - Terminal (
src/tui/screens/capacity.ts): settingcells.egresstoreportopens a second prompt that asks why. An empty answer keeps the prompt open with the reason message. The confirm step showswhy: …, and the change goes through the samechangeCapacityas the page. The terminal no longer sends the person to the page. - Page (
src/web/pages/capacity.ts): the reason is its own labelled row under the setting ("Why (needed to set report)"), sent with Save throughform=. The field is no longerrequired; the server decides when a reason is needed. - Wording: the help says it once: "Enforce unless an admin sets report here, with a reason." The row meta for a setting with no environment default now just says "The default."
- One cut: the reason is trimmed to
REASON_MAXonly inchangeCapacity;setCapacitystores it as given.
New tests:
test/egress-default.test.ts:
- Enforce and the reset to default with no reason.
- The reason is cut to REASON_MAX.
- On the page, a POST to report with no reason is refused, a POST with one is kept, and Put back works with none.
- Over MCP, report with no reason gives reason_required; with one, the reason is kept; enforce works without one.
- Over the API, report with no reason is refused (422) and enforce with none is accepted.
test/tui-capacity.test.ts: the reason prompt, the refusal when it is left empty, the reason kept withcarol, and enforce with no prompt.
To try it: open /settings/capacity and set Egress from cells to report. With the reason field empty it is refused; with a reason it is listed under Changes beside who. Put back goes through without one. In the terminal capacity screen, press e on cells.egress and type report; it asks why.
Checks: bun scripts/gate.ts, bun x tsc --noEmit and bun scripts/design-lint.ts pass. scripts/fast.ts said the diff was too broad to name tests (2123 files), so I ran the nine capacity and egress test files: 143 pass, 30 skipped, 0 fail. The 30 skipped are the unix-socket and cell suites this sandbox cannot run; the forge's check runs them on this push.
🤖 Generated with Claude Code
Egress from cells is now enforced unless an admin sets cells.egress to report on /settings/capacity and gives a reason. The FORGE_CELLS_EGRESS environment variable is ignored: the forge says so once at boot when it asks for anything other than enforce, and an empty or enforce value stays quiet. Setting report without a reason is refused on every surface: the web form, the terminal screen, the admin API and the MCP tool. Putting it back to enforce needs no reason. The reason is saved with each change (new reason column on forge_setting_change, migration pinned) and shown under Changes next to who made it and when.
Two things are worth a look:
- Reason field: it sits inside its row's own Save form with an
aria-label, so the page works without a script and with no fields outside a form. The previous push put it outside the form, tied back with aform=attribute, which broketest/capacity-nojs.test.ts.test/egress-default.test.tswas checking for thatform=wiring, so I changed it to check thatreasonis inside thecells.egressform. The test was wrong there, not the code. - Sentinel failures: the 15 failures from
test/sentinel-shared-settle.test.tsin the last check didn't come back here. The file passed on all three separate runs, and nothing in this diff touches the sentinel or shared-red code. I couldn't rebuild that shard's exact file list, so it may be a neighbouring test in shard 22 leaking state; I can't confirm that.
Not done: the old environment variable is not migrated into a saved setting. A forge that ran with report from its environment goes back to enforce until an admin sets report with a reason.
To try it, open /settings/capacity as an admin. Save Egress from cells as report with the Why box empty and it is refused; fill in Why and save again, and the change shows under Changes with the reason after who and when. Put back returns it to enforce.
Checks run here: bun scripts/gate.ts, bun x tsc --noEmit, bun scripts/design-lint.ts, and the tests in egress-default, capacity-nojs, capacity, capacity-rows, tui-capacity, mcp-table and sentinel-shared-settle. All passed.
🤖 Generated with Claude Code
Earlier asks are met: the reason sits inside the setting's own Save form with an aria-label, so the page works without JavaScript. Only a move to report needs a reason, and enforce and Put back go through bare. The reason is cut once, in changeCapacity. The terminal asks why before it confirms. The environment value is ignored and logged in one line at boot. Reason is escaped on output and kept in the change log and the event. The migration is additive and pinned. Non-blocking: egressEnvIgnored echoes up to 32 raw characters of the env value into the log, so strip control characters there. Non-blocking: the MCP dry run does not warn that report will need a reason.
The forge's environment can no longer switch cell egress to report:
cells.egressis now enforce unless an admin sets report on /settings/capacity, and every change to it needs a reason, which is kept next to who made it.src/core/capacity.ts):cells.egressno longer has an environment variable.explicitDefaultskips a setting with no variable, so its default is alwaysenforce. WhenFORGE_CELLS_EGRESSasks for anything other than enforce,egressEnvIgnored()writes one info-level line at boot (src/main.ts). Nothing in red, nothing on any page.src/apps/contain.ts): the oldegressMode()that read the variable is gone. A cell reports only when its caller passesegressMode: "report"from the admin setting.egressNotenow requires the mode as an argument.reasoncolumn toforge_setting_change.setCapacityandchangeCapacitytake a reason. Acells.egresschange without one is refused withreason_required. The reason is cleaned of control characters and cut to 500 characters, and it also goes into thecapacity.changedevent.- Web: the
cells.egressrow has a required "why" field, and the Changes list shows the reason beside who. - API:PUT /api/v1/admin/capacity/cells.egresstakesreasonand answers 422 without it. - MCP: thecapacitytool takesreason. - Terminal: it has nowhere to type a reason, so it refuses the change and says where to make it.docs/APPS.mdand two code comments now point at the admin setting;docs/MCP.mdwas regenerated from the tool definitions.test/egress-default.test.tscovers:- the default is enforce; - an environment asking for report is ignored, and said in one line; - the page shows nothing about it; - an admin's setting is refused without a reason, then honoured and kept with who and why through both the action and the API.
Existing tests that set
cells.egressnow pass a reason; the old rule allowed none, so those tests weren't wrong. The report-mode cell test intest/contain.test.tsnow passesegressMode: "report"instead of setting the variable.Not done:
FORGE_CELLS_EGRESS=reportgoes back to enforce after the upgrade. An admin who still wants report sets it, with a reason.keep_plantool wasn't available in this run.Checks run here:
bun scripts/gate.ts,bun x tsc --noEmitandbun scripts/design-lint.tsall pass.scripts/fast.tsnamed 2,123 test files (about three hours), so I ran the 179 files that cover capacity, egress, the terminal screens, the web pages, MCP and the migrations: 1,543 pass, 0 fail. The unix-socket and cell suites, including the report-mode test intest/contain.test.ts, are skipped in this sandbox; the forge's check runs them.To try it: start the forge with
FORGE_CELLS_EGRESS=reportand you'll see the one "is ignored" line in the boot log. Open /settings/capacity: Egress from cells is enforce. Set it to report with a reason, and the change shows under Changes with your name and the reason.🤖 Generated with Claude Code
Closes #10778