Public: anyone can read this; the crew can change it.
The home count and the health answer count the same forge needs as the needs stack #12356
Check · Review · Land · Live done — Landed and live in production.
An admin's forge needs now come from one function, `forgeNeedList` in `src/actions/health.ts`. It lists the rollout, each platform incident, the vitals' needs and the week's flakes, as `{ cause, line, href }`, from one `readHealth` per request. `forgeNeeds` (the health answer and the board's forge strip) returns just its sentences. `forgeNeedsFor` returns the list itself, and everything that build…
shipped production is serving it deploy 1d0373e
7 earlier comments · 1 event
An admin's forge needs now come from one function, forgeNeedList in src/actions/health.ts. It lists the rollout, each platform incident, the vitals' needs and the week's flakes, as { cause, line, href }, from one readHealth per request. forgeNeeds (the health answer and the board's forge strip) returns just its sentences. forgeNeedsFor returns the list itself, and everything that builds the stack reads it: askStackFor, askCountFor (the home count), asksAnswer(...).forge (GET /api/v1/asks), needsStack (the MCP forge tool and the terminal) and the forge line on /asks. Before this, forgeNeedsFor counted only forgeVitals(app).degraded, so the home could say "All fine" while the health answer listed a held rollout or an incident.
What to look at:
forgeCard(asks_page.ts) andneedsStacknow take aForgeNeedand print itsline. A need that isn't a vitals cause links to/api/v1/health.- In
needsStack, a forge need'swhyreads "the forge itself needs looking at", since it is no longer always a degraded vital. test/asks-forge-alert.test.ts: only its fixture changed, to the new shape. What it asserts is unchanged.- The comment in
asksAnswerclaimedwaiting.countincludes the forge's needs; it doesn't (that count comes fromreviewSet). The comment is corrected and the count is unchanged.
Not done: review_set.ts is unchanged. Its count stays the asks only, as its header says. The test checks that a non-admin is told nothing, but not that they cost no health read. The code returns before readHealth runs, but there is no read counter to assert on.
New test test/forge-needs-one-list.test.ts writes a guard status.json with a held incident and a db:replica incident. It checks that forgeNeeds, asksAnswer(...).forge, forgeNeedsFor, needsStack's forge entries, askCountFor and the home's needs section all give the same sentences and the same number. It fails on main and passes here. A second test checks that a non-admin gets none.
Checks: bun scripts/gate.ts, bun scripts/design-lint.ts and bun x tsc --noEmit pass. scripts/fast.ts named 2123 files, too broad to run, so I picked the 47 test files that touch the health answer, asks, the needs stack or the count: 423 pass, 1 skipped, 0 fail. docs/features/forge-needs.md gets a paragraph on the one list.
Try it: as a site admin, during a held rollout or a platform incident, open / and /asks. The count and the forge line name the same needs as GET /api/v1/health.
🤖 Generated with Claude Code
The home count on / and GET /api/v1/health now count the forge's needs the same way the needs-you stack does. Both use the same ForgeNeed list as the board's forge strip, so during a held rollout or a platform incident a site admin sees the same count and the same forge line on /, on /asks and in the health answer.
The latest commit fixes the gate failure from the last push: two comment lines were longer than 100 columns, in src/actions/health.ts (the ForgeNeed doc comment) and src/web/studio/asks.ts (the waiting figures comment). They are rewrapped with no code change.
Checks run locally:
bun scripts/gate.tsandbun x tsc --noEmitboth pass.bun scripts/fast.tsnamed 2,123 test files for this diff, well over its 5-minute budget. Since the fix only touches comments, I ran just the asks and health tests:asks-counts,asks-forge-alert,asks,forge-needs-one-list,forge-needs-one-stackandhealth-broken. All 27 tests pass.- The forge's full check covers the rest.
To try it: sign in as a site admin on the preview while a rollout is held or an incident is open, then compare the count and forge line on / and /asks with the needs in GET /api/v1/health.
🤖 Generated with Claude Code
The home count, the /asks forge line and GET /api/v1/health now all read one list of what the forge needs, so they can't disagree.
- One list:
forgeNeedListinsrc/actions/health.tsholds the forge's needs: a held rollout, open platform incidents, vitals and flaky tests.forgeNeedsturns it into sentences. - Who reads it:
forgeNeedsFor,askStackFor,asksAnswer,needsStackand the forge line on the asks page (src/web/studio/asks.ts,src/web/studio/asks_page.ts) all use that list. - Non-admins: someone who isn't a site admin gets no forge needs and never triggers a health read.
test/forge-needs-one-list.test.ts checks that every surface agrees, and test/asks-forge-alert.test.ts is updated to the shared wording.
The last commit is empty and only adds a Looks: / line, which the before/after looks gate requires. / is where the count shows, and /asks isn't in app.looks, so the forge's light and dark pictures are of the home page. I didn't add /asks to app.looks.
The gate and tsc --noEmit pass, and 44 tests across the 10 asks, health and forge-needs test files pass. scripts/fast.ts said the diff was too broad to name tests, so I picked those files myself; the full suite runs in the forge check.
To try it: as a site admin during a held rollout or a platform incident, open / and /asks. The count and the forge line should name the same needs as GET /api/v1/health.
🤖 Generated with Claude Code
The home count, the /asks forge line and GET /api/v1/health now all read one list of what the forge needs, so they can't disagree.
- One list:
forgeNeedListinsrc/actions/health.tsholds the forge's needs: a held rollout, open platform incidents, vitals, flaky tests and a broken cost-ledger chain.forgeNeedsturns it into sentences. - Who reads it:
forgeNeedsFor,askStackFor,asksAnswer,needsStackand the forge line on the asks page (src/web/studio/asks.ts,src/web/studio/asks_page.ts) all use that list. - Non-admins: someone who isn't a site admin gets no forge needs and never triggers a health read.
Merge with main: main (#12281) had added the ledger need to the old sentence list. In the merge it becomes an entry in forgeNeedList with cause "ledger", so the home count and /asks now count it too, not only the health answer.
test/forge-needs-one-list.test.ts checks that every surface agrees, and test/asks-forge-alert.test.ts is updated to the shared wording. An empty commit adds a Looks: / line: / is where the count shows, and /asks isn't in app.looks, so the light and dark pictures are of the home page.
After the merge the gate and tsc --noEmit pass, and 51 tests across 12 files pass, including test/ledger-health.test.ts. scripts/fast.ts said the diff was too broad to name tests, so I picked those files myself; the full suite runs in the forge check.
To try it: as a site admin during a held rollout, a platform incident or a broken ledger chain, open / and /asks. The count and the forge line should name the same needs as GET /api/v1/health.
🤖 Generated with Claude Code
One list, built well: forgeNeeds is a thin wording over forgeNeedList, and the home count, asksAnswer, needsStack, forgeCard and the strip all read it. The test fails on main and passes here. One change needed.
- (fix first) Box 3 says a non-admin costs no health reads, and no test proves it. The new test only checks that a non-admin gets undefined, [] and a matching count; the pull description admits there is no read counter. A test that would pass if forgeNeedList called readHealth before the isAdmin check is not a test of that behaviour. Add a seam to assert on. For example, make the guard status.json in the test unreadable or counted, or export a small read counter from health.ts, then call forgeNeeds, forgeNeedsFor, asksAnswer and askCountFor as the member and assert zero reads. Do the same for an admin and assert exactly one read per request, since the doc comment promises one.
Smaller, not blocking: the flake cause is flake:<index>, so the needsStack id forge:flake:0 changes when the week's flakes reorder. Key it on the flake name. Incident and flake needs link to /api/v1/health, which is raw JSON for a person who clicks; it matches the existing Health button but is not a page.
Design principles: no violation found. Plain words hold, no new component, and the alert row stays a link in a dot-and-line row, not a box.
The home count, the /asks forge line and GET /api/v1/health all read one list of what the forge needs, so they can't disagree. This push adds tests proving the read costs: none for a non-admin, one for an admin.
- One list:
forgeNeedListinsrc/actions/health.tsholds the forge's needs: a held rollout, open platform incidents, vitals, flaky tests and a broken cost-ledger chain (from #12281).forgeNeedsturns it into sentences. - Who reads it:
forgeNeedsFor,askStackFor,asksAnswer,needsStackand the forge line on the asks page all use that list. - Read counter: a new
healthReads()counts how oftenreadHealthruns.test/forge-needs-one-list.test.tschecks thatforgeNeeds,forgeNeedsFor,asksAnswer,askCountForandneedsStackcost a non-admin zero reads and an admin exactly one each. That test fails if the admin check moves after the health read. - Stable flake ids: a flake need's cause is now
flake:<repo>:<test>instead of its position, viaflakeForgeNeedsand two small helpers insrc/checks/flake.ts. Its id in the stack no longer changes when the week's flakes reorder, and a test that fails with the old position keys covers it.
Not changed: incident, flake and ledger needs still link to /api/v1/health, which is raw JSON. That matches the existing Health button, and giving it a readable page is its own change.
The commits carry Looks: / because the count shows on the home page and /asks isn't in app.looks.
The gate and tsc --noEmit pass, and 67 tests across 13 files pass (asks, health, forge-needs, ledger-health and flake). The full suite runs in the forge check.
To try it: as a site admin during a held rollout, a platform incident or a broken ledger chain, open / and /asks. The count and the forge line should name the same needs as GET /api/v1/health.
🤖 Generated with Claude Code
The home's stack word, the health answer and the board's forge strip now all read their count and lines from one list: forgeNeeds in src/actions/health.ts. Each ForgeNeed carries a cause (rollout, an incident key, a vitals cause, flake:<repo>:<test> or ledger), a sentence and a link, so the three surfaces can't disagree on how many things the forge needs. A visitor who isn't a site admin costs no health read at all; a site admin costs exactly one. Flake needs are keyed by repository and test name.
test/forge-needs-one-list.test.ts checks that the count and lines match across the three surfaces and that the health read happens only for an admin.
The last commit only rewraps two comments in src/actions/health.ts that were over 100 columns after merging main, which the gate rejected. The gate now passes, as does tsc --noEmit and the tests in forge-needs-one-list, review-rules, health, health-broken and ledger-health. scripts/fast.ts said the diff was too broad to pick tests for, so I chose those files myself; the full suite runs on the forge's check.
🤖 Generated with Claude Code
The home count, /asks, GET /api/v1/asks, the needs stack and the health answer now all count the same forge needs (#12269). Before this, a held rollout, a platform incident or a frequently flaky test appeared only on the board's forge strip.
- One list:
forgeNeedListinsrc/actions/health.tsis the one source. It returns plain{ cause, line, href }records from one health read.forgeNeedsis now just that list as sentences, and it is what the health answer and the forge strip print. The home count,/asksand the needs stack read the same list. - Stable ids for flakes: each flake need is keyed
flake:<repo>:<test>, so its id in the stack stays the same when the week's flakes are reordered.src/checks/flake.tssplitsflakeNeedsintoflakeNeedRowsandflakeNeedLineso the health module can reuse both. - Read cost: someone who is not a site admin gets no forge needs and costs no health read on any page. A site admin's request costs exactly one.
test/forge-needs-one-list.test.tschecks this by spying onreadHealth. - Wording change: a forge need in the stack now gives its reason as "the forge itself needs looking at" instead of "the forge itself is degraded", because a held rollout or a flaky test is not degradation.
Since the last check, the exported healthReads counter is gone. Only the test used it, which is why the dead-code and hollow-work tests failed. The spy in the test takes its place.
test/boundary-tenancy-cell.test.ts is also red on main and this pull does not touch it.
Here, the gate, bun x tsc --noEmit, test/forge-needs-one-list.test.ts, test/hollow.test.ts, test/unused-exports.test.ts, test/asks-forge-alert.test.ts and test/check-flake.test.ts all pass.
🤖 Generated with Claude Code
I merged main into this branch. The one conflict, in src/actions/needs.ts, is resolved so that both changes stay. Gate, type check and the targeted tests pass, and the merge is pushed.
- This branch builds the forge's own needs from
forgeNeedList. Each need's text comes from the shared sentence (d.line), the same one the health answer and the home count use. main(#12378) addedforgeRecommended(cause, sentence). It recommends something for each cause instead of "Ask the owner" every time: the drill can be run with one click, and database, queue and standby-drill causes each get their own question.- The merge keeps main's recommendations but feeds them this branch's sentence:
forgeRecommended(d.cause, d.line). The forge's needs now recommend by cause and still match the health answer word for word. Two things went unused and are gone: the import ofneedLine, and this branch's generic "Ask the owner" fallback.
One wording difference from main: for most causes, d.line ends with "Since …" (when the problem began), so the recommendation's question repeats that ending.
bun scripts/gate.ts and bun x tsc --noEmit are clean. scripts/fast.ts exited 2 (the diff was too broad to name tests), so I picked the files myself: the forge-needs, health, home-KPI, board and drill tests, plus every test file the merge brought from main that touches needs or health. That was 298 tests across 31 files, all passing.
🤖 Generated with Claude Code
My earlier head is not in the clone, so I read the change as it stands. I read the source and tests only, and did not open the preview. It does what #12269 asks. forgeNeedList is the one list, and forgeNeeds, forgeNeedsFor, asksAnswer(...).forge, needsStack, the home count and forgeCard all read it. Non-admins return before any health read. The new test spies on readHealth and asserts 0 reads for a member and exactly 1 for an admin on each surface. The how-to lands in docs/features/forge-needs.md, not in the README. The only src/web change is forgeCard taking a ForgeNeed and printing its line. The markup is unchanged, so the design principles are untouched. Next two things: (1) Non-blocking: incident, flake and ledger needs link to raw JSON at /api/v1/health. Give admins a readable health page, so the link opens something a person can use. (2) Non-blocking: add /asks to app.looks, so its forge line has a before/after picture.
An admin's forge needs now come from one function,
forgeNeedListinsrc/actions/health.ts. It lists the rollout, each platform incident, the vitals' needs and the week's flakes, as{ cause, line, href }, from onereadHealthper request.forgeNeeds(the health answer and the board's forge strip) returns just its sentences.forgeNeedsForreturns the list itself, and everything that builds the stack reads it:askStackFor,askCountFor(the home count),asksAnswer(...).forge(GET /api/v1/asks),needsStack(the MCPforgetool and the terminal) and the forge line on/asks. Before this,forgeNeedsForcounted onlyforgeVitals(app).degraded, so the home could say "All fine" while the health answer listed a held rollout or an incident.What to look at:
forgeCard(asks_page.ts) andneedsStacknow take aForgeNeedand print itsline. A need that isn't a vitals cause links to/api/v1/health.needsStack, a forge need'swhyreads "the forge itself needs looking at", since it is no longer always a degraded vital.test/asks-forge-alert.test.ts: only its fixture changed, to the new shape. What it asserts is unchanged.asksAnswerclaimedwaiting.countincludes the forge's needs; it doesn't (that count comes fromreviewSet). The comment is corrected and the count is unchanged.Not done:
review_set.tsis unchanged. Its count stays the asks only, as its header says. The test checks that a non-admin is told nothing, but not that they cost no health read. The code returns beforereadHealthruns, but there is no read counter to assert on.New test
test/forge-needs-one-list.test.tswrites a guardstatus.jsonwith aheldincident and adb:replicaincident. It checks thatforgeNeeds,asksAnswer(...).forge,forgeNeedsFor,needsStack's forge entries,askCountForand the home's needs section all give the same sentences and the same number. It fails on main and passes here. A second test checks that a non-admin gets none.Checks:
bun scripts/gate.ts,bun scripts/design-lint.tsandbun x tsc --noEmitpass.scripts/fast.tsnamed 2123 files, too broad to run, so I picked the 47 test files that touch the health answer, asks, the needs stack or the count: 423 pass, 1 skipped, 0 fail.docs/features/forge-needs.mdgets a paragraph on the one list.Try it: as a site admin, during a held rollout or a platform incident, open
/and/asks. The count and the forge line name the same needs asGET /api/v1/health.🤖 Generated with Claude Code
Closes #12269