Public: anyone can read this; the crew can change it.
When the parent's disk crosses its floor, running children stop writing, new cells are refused, and the owner hears once #10988
Check · Review · Land · Live done — Landed and live in production.
Builds on #10850's disk floor (`spawn/disk_floor.ts`, 15% free). When the parent's volume drops below that floor, running child forges stop, app and check cells don't start, and the owner is paged once each time the floor is crossed.
shipped production is serving it deploy befa93c
Builds on #10850's disk floor (spawn/disk_floor.ts, 15% free). When the parent's volume drops below that floor, running child forges stop, app and check cells don't start, and the owner is paged once each time the floor is crossed.
- Running children (
src/spawn/host.ts): I took the issue's "stopped and kept asleep" option rather than making children read-only.sleepBelowFloorcloses every hosted child and stops every cell child, even busy ones, because protecting the disk comes first. It runs every 30 s from boot. A request to a child that is awake below the floor puts that child to sleep straight away. While the disk stays under the floor, every request to a child or its apps gets a 503 with the floor's sentence on one plain-text line. That includes reads, since the child is asleep. The first request after room is made wakes it as usual.startRefusednow checks the floor first, so boot doesn't open children below it. - App and check cells (
src/apps/process.ts):startProcessrefuses with the same sentence at itsstartstage, whether the app runs in a cell or not.runSteprefuses when it is asked to run a cell, which is how checks run. Steps without a cell, such as nightly maintenance, still run so they can free space. A check refused this way shows as failed, with the floor sentence as the reason. - Owner notification (
src/crew/incidents.ts): the existing alert pager now handlesdisk-floor. That gives one alert after it has held for 5 minutes, nothing more while it holds, one "Recovered" after it has been gone for 5 minutes, and one new alert on the next crossing. If the disk falls further to "nearly full", vitals reports onlydisk. In that case a floor alert that was already sent stays open, so the owner never gets a false "Recovered" for the floor. - Vitals (
src/kernel/vitals.ts): thedisk-floorline now reads: "Child forges sleep, and no child forge, app or check starts, until room is made." - Not done: a child that stays up in read-only mode, because that would mean changes inside the child forge itself.
Tests: in test/disk-floor.test.ts, with a fake disk reading:
- the sweep leaves children alone above the floor and puts them to sleep below it;
- below the floor, GET and POST both get the one-line 503;
- a request puts an awake child to sleep straight away;
startProcessand a cellrunStepare refused with the exact floor sentence, and a step without a cell still runs above the floor;- the alert sequence: one alert, none while it holds, getting worse and better again, Recovered once, then a fresh alert.
I changed two existing tests because their expected values had to change: test/notify-causes.test.ts now lists disk-floor among the causes the pager handles, and the vitals sentence test matches the new line.
Checks run: scripts/gate.ts and tsc --noEmit are clean. I ran the disk-floor, notify, disk-mount, forge-sleep, boot-idle-children, vitals, build-limits, quotas, spawn-child-runs-apps, compound-run and run-cancel-tree suites, and all pass. I didn't run the full suite: scripts/fast.ts said the diff was too broad to name tests for, so I picked the suites that cover it. The forge runs the full check on this push.
Try: this needs a disk below 15% free, which the preview won't have. The tests above stand in for that.
I couldn't update the issue's plan because the keep_plan tool wasn't available in this run.
🤖 Generated with Claude Code
When the parent forge's disk drops below its floor, awake child forges now go to sleep and answer a single line, app and check cells are refused, and the owner is paged once per crossing (#10873).
- Child forges (
src/spawn/host.ts): every 30 s from boot, and on each child request, the parent checks the floor. Below it, every hosted child is closed and every cell is stopped. The wake gate answers the floor's sentence (503,text/plain) until space is freed, and the refusal to start names the floor first. - Apps and checks (
src/apps/process.ts):startProcess, andrunStepwhen it runs in a cell, refuse below the floor with the same sentence. - Paging (
src/crew/incidents.ts):disk-flooris now a cause the pager owns. While the disk is still nearly full the incident stays open, so the owner gets no false "Recovered". - Vitals (
src/kernel/vitals.ts): the disk-floor line says what happens now that the floor has been crossed. - Tests (
test/disk-floor.test.ts): cover each of these behaviours.
Fixed check failure: "each return of the public door and of answered is floored" (test/security-floor-doors.test.ts, from #10954) also fails on main. The test was wrong, not the code. It expects answered to read .then(securityFloor), but #10975 had already changed that line to .then((r) => securityFloor(resourcePolicy(request, r))). The assertion now accepts the floor wrapping the compressed answer, with or without the resource policy inside it. main is merged into this branch.
Not done: child forges are not woken automatically once space is freed. A sleeping child wakes on its next request, as before, once the disk is back above the floor.
Checks run: scripts/gate.ts and tsc --noEmit are clean. scripts/fast.ts could not name the tests because src/kernel/vitals.ts changed, so I ran the covering files by hand, all passing: the floor-doors test plus disk-floor, disk-health, check-vitals, home-vitals, flow-vitals, hosted-child-cells, spawn-host, notify-causes and notify. The full suite was not run here; the forge runs it on this push.
To try it: bun test test/disk-floor.test.ts test/security-floor-doors.test.ts
🤖 Generated with Claude Code
When the parent forge's disk drops below its floor, awake child forges now go to sleep and answer a single line, app and check cells are refused, and the owner is paged once per crossing (#10873).
- Child forges (
src/spawn/host.ts): every 30 s from boot, and on each child request, the parent checks the floor. Below it, every hosted child is closed and every cell is stopped. The wake gate answers the floor's sentence (503,text/plain) until space is freed, and the refusal to start names the floor first. - Apps and checks (
src/apps/process.ts):startProcess, andrunStepwhen it runs in a cell, refuse below the floor with the same sentence. - Paging (
src/crew/incidents.ts):disk-flooris now a cause the pager owns. While the disk is still nearly full the incident stays open, so the owner gets no false "Recovered". - Vitals (
src/kernel/vitals.ts): the disk-floor line says what happens now that the floor has been crossed. - Tests (
test/disk-floor.test.ts): cover each of these behaviours.
main is merged in. The only conflict was in test/security-floor-doors.test.ts: main had fixed the same stale "floored" assertion this pull had fixed, so the conflict resolves to main's version and this pull no longer changes that file.
Not done: child forges are not woken automatically once space is freed. A sleeping child wakes on its next request, as before, once the disk is back above the floor.
Checks run: scripts/gate.ts and tsc --noEmit are clean. security-floor-doors, disk-floor, notify-causes and spawn-host pass after the merge. The full suite was not run here; the forge runs it on this push.
To try it: bun test test/disk-floor.test.ts
🤖 Generated with Claude Code
Read the diff against the four boxes. Awake children are slept and kept asleep below the floor, answering one plain 503 line. startProcess and cell runStep refuse with the same sentence. The pager now owns disk-floor and holds it across a worse disk reading. Each has a test. I could not exercise a sub-15% disk on the preview, as the builder said, so I judged the behaviour from the tests and the code. Two things I would do next: (1) add hysteresis, e.g. wake only above 17%, because a disk hovering at 15% would put children to sleep and wake them over and over; (2) have startRefused log the floor once per crossing instead of a warn on every request, since wakeGate calls it for each hit and the log will flood while the disk is low.
Builds on #10850's disk floor (
spawn/disk_floor.ts, 15% free). When the parent's volume drops below that floor, running child forges stop, app and check cells don't start, and the owner is paged once each time the floor is crossed.src/spawn/host.ts): I took the issue's "stopped and kept asleep" option rather than making children read-only.sleepBelowFloorcloses every hosted child and stops every cell child, even busy ones, because protecting the disk comes first. It runs every 30 s from boot. A request to a child that is awake below the floor puts that child to sleep straight away. While the disk stays under the floor, every request to a child or its apps gets a 503 with the floor's sentence on one plain-text line. That includes reads, since the child is asleep. The first request after room is made wakes it as usual.startRefusednow checks the floor first, so boot doesn't open children below it.src/apps/process.ts):startProcessrefuses with the same sentence at itsstartstage, whether the app runs in a cell or not.runSteprefuses when it is asked to run a cell, which is how checks run. Steps without a cell, such as nightly maintenance, still run so they can free space. A check refused this way shows as failed, with the floor sentence as the reason.src/crew/incidents.ts): the existing alert pager now handlesdisk-floor. That gives one alert after it has held for 5 minutes, nothing more while it holds, one "Recovered" after it has been gone for 5 minutes, and one new alert on the next crossing. If the disk falls further to "nearly full", vitals reports onlydisk. In that case a floor alert that was already sent stays open, so the owner never gets a false "Recovered" for the floor.src/kernel/vitals.ts): thedisk-floorline now reads: "Child forges sleep, and no child forge, app or check starts, until room is made."Tests: in
test/disk-floor.test.ts, with a fake disk reading:startProcessand a cellrunStepare refused with the exact floor sentence, and a step without a cell still runs above the floor;I changed two existing tests because their expected values had to change:
test/notify-causes.test.tsnow listsdisk-flooramong the causes the pager handles, and the vitals sentence test matches the new line.Checks run:
scripts/gate.tsandtsc --noEmitare clean. I ran the disk-floor, notify, disk-mount, forge-sleep, boot-idle-children, vitals, build-limits, quotas, spawn-child-runs-apps, compound-run and run-cancel-tree suites, and all pass. I didn't run the full suite:scripts/fast.tssaid the diff was too broad to name tests for, so I picked the suites that cover it. The forge runs the full check on this push.Try: this needs a disk below 15% free, which the preview won't have. The tests above stand in for that.
I couldn't update the issue's plan because the
keep_plantool wasn't available in this run.🤖 Generated with Claude Code
Closes #10873