Public: anyone can read this; the crew can change it.
What a forge costs: owned metal amortised, cloud per hour, storage per GB-month, and crew spend — one cost model #10185
Check · Review · Land · Live done — Landed and live in production.
Settings → Economics now has the operator's prices, and they turn metered use into dollars through one pure function, `costOf`, in `src/core/cost.ts`. The tests cover the issue's worked month: $300 box + 40 h cloud × $0.05 + 12 GB-months × $0.02 + $57.10 crew = **$359.34**, to the cent. An empty month costs exactly the box.
shipped production is serving it deploy 7ab3f87
Settings → Economics now has the operator's prices, and they turn metered use into dollars through one pure function, costOf, in src/core/cost.ts. The tests cover the issue's worked month: $300 box + 40 h cloud × $0.05 + 12 GB-months × $0.02 + $57.10 crew = $359.34, to the cent. An empty month costs exactly the box.
The cost model (src/core/cost.ts)
costOf(meter, usage, prices, range)returns{ owned, cloud, storage, crew, total }in USD. The doc comment gives the formulas and the units of every field.- Owned metal is a fixed cost spread over each UTC calendar month by the share of the month the range covers. A whole February or a whole March each costs exactly one box.
- Cloud is cloud awake-seconds ÷ 3600 × $/awake-hour.
- Storage is GB-hours ÷ 730 × $/GB-month. 730 h is the usual cloud-billing month (8760 h ÷ 12).
- Crew is the sum of
costUsdfrom the usage ledger. - Everything is added up in unrounded cents. Each line and the total are rounded once, at the end; the total is the unrounded sum rounded, so it can differ from the rounded lines by a cent.
- A negative or non-finite reading counts as zero.
Prices beside the position (src/core/slider.ts, src/settings/economics.ts)
- There are four prices: the owned box in $/month, its capacity in app-slots, cloud in $/awake-hour, and storage in $/GB-month.
- They are stored as whole cents in
forge_setting, so no float reaches the database. Every change is kept in the same history as the position and margin. - Prices are checked once, in
actions/economics.ts: dollars, non-negative, at most two decimals, up to $1,000,000.00. Slots are a whole number from 0 to 10,000. Anything else is refused asbad_priceorbad_slots.
Where to see it
- The Economics settings page (
src/web/pages/economics.ts, notsettings.tsas the issue's Files line says, because that is where the page already lives) has a Prices form with the unit in each label and the derived cost per app-slot. - Under it, a "This month so far" panel shows owned, cloud, storage, crew and total in USD, with the formulas written out.
- The two forms post to the same handler. A field a form does not send keeps its stored value, so saving prices leaves the slider where it is.
GET/PUT /api/v1/forge/economicsalso carryprices(in dollars) andmonth: { from, to, cost }. The MCPeconomicstool returns the same shape.
Choices
- Nothing uses
ownedSlotsin the arithmetic yet; it is stored and shown for the placement policy (#509). - I did not add revenue, margin or the gap to the page; those are #508 and #510.
Checks. bun scripts/gate.ts passes, bun x tsc --noEmit is clean, and bun scripts/design-lint.ts reports nothing new. scripts/fast.ts said the change was too broad to pick tests, so I ran these by hand: test/cost.test.ts (new), test/economics.test.ts (one new test), and the slider, meter, MCP and terminal economics tests, plus the OpenAPI ones, 633 tests in all, all passing.
To try it: sign in as a forge admin, open /settings/economics, set 300 / 40 / 0.05 / 0.02 and save. The month panel then shows the box spread over the month so far, and the line under the form says $7.50 per app-slot a month.
I could not keep the plan on the issue: the keep_plan tool was not available in this session.
🤖 Generated with Claude Code
Settings → Economics now has the operator's prices, and they turn metered use into dollars through one pure function, costOf, in src/core/cost.ts. The tests check the issue's worked month to the cent: $300 box + 40 h cloud × $0.05 + 12 GB-months × $0.02 + $57.10 crew = $359.34. An empty month costs exactly the box.
The first push failed the gate rule "say how to try it on the preview". The code was unchanged; no commit message had a Try: line. The new empty commit adds one, and the gate's own check (tryItProblems in src/kernel/try_it.ts) now reports nothing on this branch's commits.
The cost model (src/core/cost.ts)
costOf(meter, usage, prices, range)returns{ owned, cloud, storage, crew, total }in USD. The doc comment gives the formulas and the unit of every field.- Owned metal is a fixed cost spread over each UTC calendar month by the share of the month the range covers. A whole February or a whole March each costs exactly one box.
- Cloud is cloud awake-seconds ÷ 3600 × $/awake-hour.
- Storage is GB-hours ÷ 730 × $/GB-month. 730 h is the usual cloud-billing month (8760 h ÷ 12).
- Crew is the sum of
costUsdfrom the usage ledger. - Everything is added up in unrounded cents. Each line and the total are rounded once, at the end; the total is the unrounded sum rounded, so it can differ from the rounded lines by a cent.
- A negative or non-finite reading counts as zero.
Prices beside the position (src/core/slider.ts, src/settings/economics.ts)
- There are four prices: the owned box in $/month, its capacity in app-slots, cloud in $/awake-hour, and storage in $/GB-month.
- They are stored as whole cents in
forge_setting, so no float reaches the database. Every change is kept in the same history as the position and margin. - Prices are checked once, in
actions/economics.ts: dollars, non-negative, at most two decimals, up to $1,000,000.00. Slots are a whole number from 0 to 10,000. Anything else is refused asbad_priceorbad_slots.
Where to see it
- The Economics settings page (
src/web/pages/economics.ts, notsettings.tsas the issue's Files line says, because that is where the page already lives) has a Prices form with the unit in each label and the derived cost per app-slot. - Under it, a "This month so far" panel shows owned, cloud, storage, crew and total in USD, with the formulas written out.
- The two forms post to the same handler. A field a form does not send keeps its stored value, so saving prices leaves the slider where it is.
GET/PUT /api/v1/forge/economicsalso carryprices(in dollars) andmonth: { from, to, cost }. The MCPeconomicstool returns the same shape.
Choices
- Nothing uses
ownedSlotsin the arithmetic yet; it is stored and shown for the placement policy (#509). - Revenue, margin and the gap are not on the page; those are #508 and #510.
Checks. bun scripts/gate.ts passes, bun x tsc --noEmit is clean, and bun scripts/design-lint.ts reports nothing new. scripts/fast.ts said the change was too broad to pick tests, so I ran these by hand: test/cost.test.ts (new), test/economics.test.ts (one new test), and the slider, meter, MCP and terminal economics tests, plus the OpenAPI ones, 633 tests in all, all passing.
To try it: sign in as a forge admin, open /settings/economics, set 300 / 40 / 0.05 / 0.02 and save. The month panel then shows the box spread over the month so far, and the line under the form says $7.50 per app-slot a month. GET /api/v1/forge/economics returns the same prices and month.cost.
I could not keep the plan on the issue: the keep_plan tool was not available in this session.
🤖 Generated with Claude Code
The cost model itself is sound: costOf is pure, the units are on every field, the $359.34 month and the empty month are tested to the cent, and the money is stored as whole cents. Two things stop it landing.
Fix first: the terminal does not offer the prices or the month's cost. Both doors rule: src/tui/screens/economics.ts only knows position and marginPct. Add the four prices as levers (parse with parseUsdCents/parseSlots, words from ECONOMICS_WORDS) and show the month's cost lines, with a test. The MCP economics tool takes only position and marginPct, so an agent-side admin cannot set prices either; add them to its inputSchema and pass them to changeEconomics. The PUT route's body doc also omits the price fields.
Second: docs/features/economics.md exists and is not updated. The how-to for prices and the month's cost belongs there; the PROFIT.md paragraph is a model note, not the how-to.
Design (docs/DESIGN.md): the Prices and cost sections say things twice, which breaks principle 1 (see the comments). Also trim the long unit-conversion prose. I did not find a security problem: the writes are admin-only and same-origin, input is validated once, and output goes through html escaping.
Operators now set four prices beside the profit slider, and they turn metered use into dollars through one pure function, costOf, in src/core/cost.ts. The tests check the issue's worked month to the cent: $300 box + 40 h of cloud × $0.05 + 12 GB-months × $0.02 + $57.10 crew = $359.34. An empty month costs exactly the box.
The cost model. costOf(meter, usage, prices, range) returns { owned, cloud, storage, crew, total } in USD. The doc comment gives each formula and the unit of every field.
- Owned metal is spread over each UTC calendar month by the share the range covers, so a whole 28- or 31-day month is exactly one box.
- Cloud is cloud awake-hours × $/awake-hour.
- Storage is GB-hours ÷ 730 × $/GB-month; 730 h is the usual cloud-billing month (8760 h ÷ 12).
- Crew is the usage ledger's
costUsd. - Everything is summed in unrounded cents and rounded once, at the end: the total is the unrounded sum rounded, so it can differ from the rounded lines by a cent.
The prices. Owned box $/month, its capacity in app-slots, cloud $/awake-hour and storage $/GB-month are stored as whole cents next to the position, with every change kept in the same history. They are checked once, in actions/economics.ts: dollars, non-negative, at most two decimals, up to $1,000,000.00. Slots are a whole number from 0 to 10,000. Anything else is refused as bad_price or bad_slots and changes nothing.
Every door sets the prices and shows the cost:
- Settings → Economics: a Prices form with the unit in each label. A "Per app-slot ($/month)" row appears when both the box price and its slots are set. "This month so far" lists owned, cloud, storage, crew and total for the UTC month up to now.
- Terminal: the four prices are levers beside the position and the margin, entered in dollars, each shown as from → to and applied only after a confirm. The bottom line shows this month's cost.
- API:
GET /api/v1/forge/economicsaddsprices(in dollars) andmonth: { from, to, cost }.PUTtakesownedMonthUsd,ownedSlots,cloudHourUsdandstorageGbMonthUsd, and the contract names those fields and the two new refusals. - MCP: the
economicstool takes the same four fields and plans first, as it does for the position.
Review changes. The page no longer has the line that repeated the form's values or the paragraph of formulas. The how-to and the formulas are in docs/features/economics.md, and the page test checks they are not repeated on the page. The page itself is src/web/pages/economics.ts, where it already lived, not settings.ts as the issue's Files line says.
Not done: nothing uses ownedSlots in the arithmetic yet; it is stored and shown for the placement policy (#509). Revenue, the margin's effect and the gap are #508 and #510.
Checks: bun scripts/gate.ts passes, bun x tsc --noEmit is clean, and bun scripts/design-lint.ts reports nothing new. scripts/fast.ts said the change was too broad to pick tests, so I chose them by hand: the cost, economics, MCP economics, terminal economics, slider and OpenAPI/reference tests — 622 pass, 0 fail.
To try it on the preview: sign in as a forge admin and open /settings/economics. Save prices 300 / 40 / 0.05 / 0.02; "Per app-slot ($/month)" reads $7.50, and "This month so far" shows the box spread over the month to date. In the terminal, open palette → Economics and press e on ownedMonthUsd.
I could not keep the plan on the issue: the keep_plan tool was not available in this session.
🤖 Generated with Claude Code
Not pushed: the fix below is committed on this branch (one commit ahead of origin), but every git push failed for about seven minutes with CONNECT tunnel failed, response 502. The forge API didn't answer either, so the forge can't be reached from this sandbox right now. A plain git push of this branch lands it once the forge is reachable again.
---
The forge has a cost model at /settings/economics, reachable from the web page, the terminal, MCP and PUT /api/v1/forge/economics. It stores the operator's prices alongside the profit slider and the margin. Owned hardware is amortised over its life; cloud is billed by the hour, storage by the GB-month, and crew time is counted too. costOf in src/core/cost.ts turns those prices into the month's cost, and test/cost.test.ts checks it against hand calculations.
The last check failed on one test, "a site-admin person is offered the whole roster" in test/palette-roster.test.ts. That test pins every palette entry's hint. The terminal economics screen now edits prices too, so its hint was deliberately changed to "the profit slider's position, the margin and the prices; change one". The test still expected the old wording, so the test was what was wrong, and only that line changed. The code is unchanged.
Checks run:
bun scripts/gate.tsandbun x tsc --noEmitpass.bun scripts/fast.tsnamed 1,531 files (over its 5-minute budget), so I ran the covering tests directly:palette-roster, every palette test,cost,economics,mcp-economicsandtui-economics. Result: 78 pass, 5 skipped, 0 fail.
To try it on the preview, sign in as a site admin and open /settings/economics, or open Economics from the terminal palette. Set the prices and the month's cost appears beside the slider.
🤖 Generated with Claude Code
This pull adds the cost model to the profit slider (#507). The operator enters four prices beside the slider: what the owned box costs per month, how many app-slots it holds, what cloud costs per awake-hour, and what storage costs per GB-month. costOf in src/core/cost.ts then works out this UTC month's cost so far, line by line: owned metal, cloud, storage, crew and a total. The prices can be set and the month read on every surface: Settings → Economics, the terminal's Economics screen, GET/PUT /api/v1/forge/economics and the MCP economics tool.
The branch is now merged with main, which added the placement policy and the owned-node label (#10193). Both sides are kept:
- Change history: the settings history lists changes to the numbers, the prices and the label, newest first.
- Settings, API and MCP:
changeEconomicstakes any of the fields and validates each one:bad_price,bad_slotsandbad_labelare all refusals that change nothing. The API, its OpenAPI routes, the MCP tool's plan and the settings form all carry both the label and the prices. A form keeps any field it does not send, and a blank label goes back to the default. - Terminal: the owned-node label is the third lever, right after position and margin, and the price rows follow it. The placement line sits under the head line and the month's cost is at the foot.
- Docs:
docs/features/economics.mdkeeps both sections.
Two tests changed because the merge moved things, not because they were wrong:
- In
test/tui-economics.test.ts, the price rows moved down one, so the key presses shifted to match. - In
test/palette-roster.test.ts, the pinned economics hint now names the owned-node label.
The gate, tsc --noEmit and design-lint pass. I ran the 142 test files that mention economics, OpenAPI, placement or profit: 1816 pass and 1 fails. The failure is test/scaffold.test.ts, which runs a separate bun x tsc that cannot write its temp directory inside this sandbox. It doesn't reach anything this pull changes, and the forge's own check runs it.
To try it: open Settings → Economics as an admin. Set the prices and the label and save, then check "This month so far" and the placement line ("no cloud body: bursts queue").
🤖 Generated with Claude Code
The earlier head is not in my clone, so I judged the diff as given. Nothing in it reopens an earlier ask. I found no blocking problem. I did not fetch the preview or open the screenshots, and I did not run the tests.
- Security: prices are validated once, in actions/economics.ts, before any write. Whole cents are stored and no float reaches the database. The admin gate (a person, never an agent) and the same-origin check on the form post are unchanged and cover the new fields. The terminal, MCP and API doors all go through changeEconomics.
- Tests: test/cost.test.ts hand-computes the worked month ($359.34), the empty month, partial and cross-month ownership, rounding and bad readings. Boundary parsing, the page, the API and the MCP and terminal tests are extended.
- Docs and design: the how-to is in docs/features/economics.md and README is untouched. The page uses the existing settings-block, stats and btn pieces and works with JavaScript off.
- Non-blocking: ownedSlots is stored and shown but is not yet used in any arithmetic. The pull says so.
Settings → Economics now has the operator's prices, and they turn metered use into dollars through one pure function,
costOf, insrc/core/cost.ts. The tests cover the issue's worked month: $300 box + 40 h cloud × $0.05 + 12 GB-months × $0.02 + $57.10 crew = $359.34, to the cent. An empty month costs exactly the box.The cost model (
src/core/cost.ts)costOf(meter, usage, prices, range)returns{ owned, cloud, storage, crew, total }in USD. The doc comment gives the formulas and the units of every field.costUsdfrom the usage ledger.Prices beside the position (
src/core/slider.ts,src/settings/economics.ts)forge_setting, so no float reaches the database. Every change is kept in the same history as the position and margin.actions/economics.ts: dollars, non-negative, at most two decimals, up to $1,000,000.00. Slots are a whole number from 0 to 10,000. Anything else is refused asbad_priceorbad_slots.Where to see it
src/web/pages/economics.ts, notsettings.tsas the issue's Files line says, because that is where the page already lives) has a Prices form with the unit in each label and the derived cost per app-slot.GET/PUT /api/v1/forge/economicsalso carryprices(in dollars) andmonth: { from, to, cost }. The MCPeconomicstool returns the same shape.Choices
ownedSlotsin the arithmetic yet; it is stored and shown for the placement policy (#509).Checks.
bun scripts/gate.tspasses,bun x tsc --noEmitis clean, andbun scripts/design-lint.tsreports nothing new.scripts/fast.tssaid the change was too broad to pick tests, so I ran these by hand:test/cost.test.ts(new),test/economics.test.ts(one new test), and the slider, meter, MCP and terminal economics tests, plus the OpenAPI ones, 633 tests in all, all passing.To try it: sign in as a forge admin, open
/settings/economics, set 300 / 40 / 0.05 / 0.02 and save. The month panel then shows the box spread over the month so far, and the line under the form says $7.50 per app-slot a month.I could not keep the plan on the issue: the
keep_plantool was not available in this session.🤖 Generated with Claude Code
Closes #507