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

A spawn that would pass the host's child quota is refused with one sentence, and the New forge form says so before it is filled in #5843

Merged opened this

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

Merged

A spawn that would pass a declared child quota is now refused with a 409 `refused` and one sentence naming the limit. The New forge page shows that sentence above the form when even a small forge would not fit. The new quota tests and `test/new-forge.test.ts` pass unchanged (17 tests), and `tsc --noEmit` is clean. I did not run the full check or `design-lint` (the change adds no new component clas…

shipped production is serving it deploy 23f74da

opened this

A spawn that would pass a declared child quota is now refused with a 409 refused and one sentence naming the limit. The New forge page shows that sentence above the form when even a small forge would not fit. The new quota tests and test/new-forge.test.ts pass unchanged (17 tests), and tsc --noEmit is clean. I did not run the full check or design-lint (the change adds no new component class).

  • Quota: FORGE_CHILD_CPUS, FORGE_CHILD_MEMORY_MIB and FORGE_CHILD_DISK_MIB declare what all children may hold together. A variable that is unset, zero or not a whole number declares nothing, so test hosts and today's forges behave as before. The logic is in the new src/spawn/quota.ts. It reads the env on every check, sums the sizes in the ledger (a ledger with no size counts as small), and names the first limit the new forge would pass.
  • Where it applies: the check runs in make() in src/actions/spawn.ts, only for a new child. Retrying or re-answering a child that already exists is never refused for quota.
  • New forge page: the sentence is a #quota-full status line above the form. The form still renders, so f-name-err and f-modelKey-err still appear beside their fields. This is separate from the existing "cannot hold another child" screen, which still replaces the form when the child count is full.
  • Other changes: I added "quota" to the placement refusal reasons in src/spawn/placement.ts, and a "Child quota" section in README.md.
  • Tests: test/child-quota.test.ts covers four cases:

- Unset or invalid: nothing is refused and the page has no sentence. - Set and full: the second spawn gets a 409 with the sentence, the page shows it, and a field error still renders. - Per-size fit: a limit with room for small but not large refuses only large.

I did not add an AGENTS.md or CLAUDE.md note. Disk is counted from declared sizes, not measured usage.

Try it: start the forge with FORGE_CHILD_CPUS=1, spawn one child, then open /forges/new.

Closes #2981

commented

A spawn that would pass a declared child quota is now refused with a 409 refused and one sentence naming the limit. The New forge page shows that sentence above the form when even a small forge would not fit. The new quota tests and test/new-forge.test.ts pass unchanged (17 tests), and tsc --noEmit is clean. I did not run the full check or design-lint (the change adds no new component class).

  • Quota: FORGE_CHILD_CPUS, FORGE_CHILD_MEMORY_MIB and FORGE_CHILD_DISK_MIB declare what all children may hold together. A variable that is unset, zero or not a whole number declares nothing, so test hosts and today's forges behave as before. The logic is in the new src/spawn/quota.ts. It reads the env on every check, sums the sizes in the ledger (a ledger with no size counts as small), and names the first limit the new forge would pass.
  • Where it applies: the check runs in make() in src/actions/spawn.ts, only for a new child. Retrying or re-answering a child that already exists is never refused for quota.
  • New forge page: the sentence is a #quota-full status line above the form. The form still renders, so f-name-err and f-modelKey-err still appear beside their fields. This is separate from the existing "cannot hold another child" screen, which still replaces the form when the child count is full.
  • Other changes: I added "quota" to the placement refusal reasons in src/spawn/placement.ts, and a "Child quota" section in README.md.
  • Tests: test/child-quota.test.ts covers four cases:

- Unset or invalid: nothing is refused and the page has no sentence. - Set and full: the second spawn gets a 409 with the sentence, the page shows it, and a field error still renders. - Per-size fit: a limit with room for small but not large refuses only large.

I did not add an AGENTS.md or CLAUDE.md note. Disk is counted from declared sizes, not measured usage.

Try it: start the forge with FORGE_CHILD_CPUS=1, spawn one child, then open /forges/new.

Changes requested — before the latest push

The refusal itself is sound: it is enforced in make() for every door, only for new children, and the tests cover unset, invalid, full and per-size. One miss against the list: the issue says the New forge form says so before it is filled in, and only the web page does. src/tui/screens/newforge.ts shows the quota sentence only after a failed submit, so a terminal user fills in the whole form first. Fix first: have the terminal new-forge screen call quotaFull(app.config.dataDir) when it opens and show the sentence in its status line, and add a tty test for it. Also: the New menu (layout.ts forgeDoor, via noRoom) still offers New forge as enabled when the quota is full, so consider feeding quotaFull into the same disabled reason. And the page uses class error with role=status for a standing notice; role=alert is not needed, but check docs/DESIGN.md for the named notice component instead of the error class.

commented

The terminal New forge screen and the New menu now say the quota sentence up front. Type check, design-lint and the focused tests pass (33 tests across the quota, web form and terminal screens); I did not run the full check.

  • Terminal: newForgeScreen calls quotaFull(dataDir) when it opens. If even a small forge would not fit, it draws "This forge cannot hold another child: …" on the first row and the fields below it. A tty test in test/tui-newforge-size.test.ts checks that the sentence and the Name field are both on screen before anything is typed.
  • New menu: layout.ts now uses noRoom(...) ?? quotaFull(...) as the disabled reason, so New forge is greyed out with the quota sentence when the quota is full. test/child-quota.test.ts checks that the home page carries the sentence.
  • Web notice: docs/DESIGN.md names no notice component for a standing sentence. I dropped the hand-rolled class="error mb" and used a plain <p role="status" id="quota-full">, the same markup as the existing "cannot hold another child" line on that page. If you want a named component, say which one.
Approved

Approved. The quota is opt-in (unset means no limit), the check sits only on the new-child path in make(), the 409 gives one sentence, and the page, New menu and terminal all say it. Tests cover unset, invalid, full and per-size cases. The notice is plain markup that matches the existing "cannot hold another child" line, and DESIGN.md names no notice component. The preview has no FORGE_CHILD_* set, so I could not see the sentence there; the tests cover it. Next, in order: (1) layout.ts calls quotaFull on every page render for every person, and with a limit set that is a synchronous readdir plus a JSON parse of each ledger file. Cache the held sum briefly or invalidate it when the ledger changes, to keep common requests under 50 ms. (2) The check and the ledger write are not atomic, so two concurrent spawns can both pass the last slot. Re-check after placing, or serialise the check and the write.

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs