cooling.fan-gate-trips: a fan under its floor is a fault; pin the airflow gates

The acceptance catalog gains the test behind forgectrl 5a31d66: an
exhaust floor at the legal maximum must trip AIRFLOW after the grace and
three ticks (hold, fire blocked, no resume, the exhaust gate TRIPPED and
named in the reason); a purge current floor at the ADC rail must trip
the same way; an exhaust floor of zero must read off in gates_off and
trip nothing; restored, the next session must run OK with every fan at
or above its floor. Three host cases against a scripted engine that
models the grace and the readings. The covers map names src/airflow.*.

COOLING.md gains 3a (the airflow gates) and the five settings with their
ranges and bands; SERVICES.md the gate rule, the rows, the AIRFLOW
verdict and fan_gates; SAFETY.md names the floors among the verdict's
inputs; BRINGUP item 19 records the gates as in with provisional floors
and the catalog at 44. Pins: forgectrl 5a31d66, forgefirm-app 81027ff
(0.1.15+git); fetch-verified.
This commit is contained in:
ScottW514
2026-08-21 19:00:06 -04:00
parent a8a02596cd
commit 0bddc9eeab
7 changed files with 329 additions and 20 deletions
+16 -6
View File
@@ -48,7 +48,7 @@ hardware-validated.**
modes (cancel-and-return on a lid or interlock open, button pause/resume),
bench-validated 2026-08-17.
- **Releases are gated by the acceptance tool** (`forgetest`, dev image only):
a 43-test catalog, domain-scoped inheritance, an always-required safety core,
a 44-test catalog, domain-scoped inheritance, an always-required safety core,
and a release gate that reads the exported artifact. The full campaign on
dev image `20260821181036` (the first built on the `<recipe>-pin.inc`
layout) satisfied 42 of 42 and its export authorizes a release;
@@ -579,13 +579,14 @@ under the domain model from the day's earlier dev images) and the export reads "
YES" for that image's manifest. That authorizes a release; it is not one
until `releases/v<version>/acceptance.json` is committed.
- **Catalog: 43 tests** in `forgetest/forgetest/suite/`, every one a port of a
- **Catalog: 44 tests** in `forgetest/forgetest/suite/`, every one a port of a
proven bench drill or a bench-verified check — the always-required core
(`image.health`, `kernel.latch-locked-idle`, `kernel.k1-k2`,
`kernel.fire-line`), `forgectrl.*`, `logs.*`, `update.*`, `motion.*`
(pacing, jog round-trip, liveness probe, cancel/abort, dead-man, the lid,
interlock and button parity tests), `cooling.*` (flow verification, fans
quiet after motion, a gate setting tripping and off by value), `camera.snapshot`,
quiet after motion, a gate setting tripping and off by value, a fan under
its floor), `camera.snapshot`,
`laser.*` (emission witness, arm-wait lid, disarm-in-hold, armed kill,
pause/resume/lid-cancel) and `cloud.*`. Tests that share a setup are merged;
the `auto` tests stay separate for failure isolation.
@@ -1331,11 +1332,20 @@ Open items only. Anything closed is in `CAMPAIGN-LOG.md`.
`/cool/state`; the engine resolves each as the stricter of local and
header, never loosening and never overruling an off gate, logs the
effective set and publishes it in `/cool/status`; the coolant ceiling
is the live consumer, the floors wait for the fan gates
is the live consumer and the fan floors follow
(`cloud.pause-resume` checks both log lines on a real print; bench PASS
2026-08-21 on dev image `20260821220926`, the service's hunt windows
ignored as looser and the print's 33 C window matched). The fan
gates, the coolant critical tier and the watch-only board temperatures
ignored as looser and the print's 33 C window matched). **The airflow
gates are in** (`forgectrl/src/airflow.c`): every fan held to a floor
while the run profile is applied (exhaust, intakes and air assist by
tachometer, purge by current), a spin-up grace, three ticks under the
floor, a fault for the rest of the session (`AIRFLOW`, no resume), a
header window only ever raising a floor; `cooling.fan-gate-trips` in
the catalog. The shipped floors are provisional, 55 percent of one
run-duty snapshot (exhaust 3700, intake 1800, air assist 6000 rpm,
purge current 300, grace 15 s) until `fan_floor_measure.py` has been
run on the bench and the numbers in `forgectrl/src/gates.c` set from
it. The coolant critical tier and the watch-only board temperatures
follow.
Nothing here can put energy where it was not commanded: the hardware chain
+45 -2
View File
@@ -135,6 +135,37 @@ per-layer Air Assist), OR'd with the armed window. In **cloud mode** the job's
own header carries the duties and the client passes them through, so a print
gets the fan profile the service designed for it and a lens hunt stays quiet.
### 3a. Airflow gates: a fan that is not moving the air
Commanding a fan and getting airflow are two different things, and the
machine can tell them apart: the exhaust, the two intakes and the air assist
carry tachometers, and the purge-air fan in the head reports its current.
While the run profile is applied, the engine holds every one of them to a
floor.
- **The floors** are settings (§8): `cool_tach_exhaust_min_rpm`,
`cool_tach_intake_min_rpm` (either intake), `cool_tach_air_assist_min_rpm`
and `cool_purge_min_current`, each a fraction of what the fan reaches at
run duty on the bench machine. A cloud job's header can raise a tach floor
for that job, never lower it (§2).
- **A spin-up grace** (`cool_fan_grace_s`) runs from the moment the run
profile is written; nothing counts inside it, because the big exhaust fan
takes seconds to reach speed.
- **Three seconds under the floor trip the gate**, and a single reading at
or above it in between clears the count, so a tach reading that wanders
does not end a job.
- **A trip is a fault, not a pause.** The verdict goes `AIRFLOW`, fire is
blocked, the job holds, and there is no resume for the rest of that run
session: a fan that has stopped moving air is not a condition to cut
through. The fans stay at run duty (a stalled extraction fan needs every
other fan around it running), and the reason names the fan, the reading
and the floor. The next job starts the gates fresh.
- **A floor of zero is that gate off** (§8a). It still measures: the first
reading in a job that would have tripped the shipped default is logged.
`/cool/status` carries each fan's reading, floor and state (`grace`, `ok`,
`under`, `TRIPPED`, `off`, or `idle` outside a run) as `fan_gates`.
---
## 4. Coolant flow verification
@@ -324,6 +355,11 @@ start of every run, so a change takes effect on your next job.
| `cool_temp_resume` | 31 °C | 5 to 59 °C | 20 to 36 °C | Resume gate: below it, continue. Always kept below the ceiling. |
| `cool_cooldown_s` | 15 s | 0 to 1800 s | | Smoke-clear phase at run duty after a job. |
| `cool_cooldown_max_s` | 300 s | 0 to 1800 s | | Cap on the thermal cooldown phase. |
| `cool_tach_exhaust_min_rpm` | 3700 rpm | 0 to 20000 | 2500 to 5000 | Exhaust fan floor at run duty (§3a). `0` turns the gate off. |
| `cool_tach_intake_min_rpm` | 1800 rpm | 0 to 20000 | 1200 to 2500 | Intake fan floor, either intake (§3a). `0` turns the gate off. |
| `cool_tach_air_assist_min_rpm` | 6000 rpm | 0 to 30000 | 4000 to 8000 | Air-assist fan floor (§3a). `0` turns the gate off. |
| `cool_purge_min_current` | 300 raw | 0 to 1023 | 150 to 500 | Purge-air fan current floor (the fan has no tachometer; about 1 off, about 630 on). `0` turns the gate off. |
| `cool_fan_grace_s` | 15 s | 0 to 120 s | 5 to 30 s | Spin-up window after the run profile is written, during which no floor counts. |
Two settings are deliberately not on the panel:
@@ -335,8 +371,8 @@ Two settings are deliberately not on the panel:
### 8a. Turning a gate off
The gates are settings, and the far end of a gate setting's range is the off
switch: a coolant ceiling of 60 °C never trips, and a check window of 0 s runs
no flow verification at all. There is no other switch, and no list of names to
switch: a coolant ceiling of 60 °C never trips, a check window of 0 s runs
no flow verification at all, and a fan floor of 0 never trips. There is no other switch, and no list of names to
get wrong. The ranges are wide on purpose: the shipped defaults and the
recommended bands come from one bench machine, and a machine whose loop or
sensors read differently changes the number rather than waiting for new
@@ -371,6 +407,10 @@ Stated plainly so nobody counts on them:
cannot be detected (the output has no readback), so this will become a user
setting plus a simple hysteresis around the factory's setpoints.
- **A fire watch that acts** (§7).
- **Fan floors measured on more than one machine.** The shipped floors are
a fraction of one bench machine's run-duty speeds; a machine whose fans
read differently sets its own (§8), and a floor of zero turns that gate
off while it does.
---
@@ -385,6 +425,9 @@ Stated plainly so nobody counts on them:
| Suspicion unresolved past the budget | Escalates to `FAULT`. |
| Three cleared suspicions in one job | Aggregated "check your coolant" warning. |
| Upstream coolant above 33 °C | `OVERTEMP`: hold + forced cooling; auto-resume under 31 °C. |
| A fan under its floor inside the spin-up grace | Nothing yet: the gate reads `grace`. |
| A fan under its floor for three seconds after the grace | `AIRFLOW`: fire blocked, hold, no resume this job; fans held at run duty; the next job starts the gates fresh. |
| Purge-air current absent at run duty | `AIRFLOW`, the same way. |
| A gate setting at its off end (ceiling 60 °C, check window 0 s) | No verdict from that gate; a run-start log line, `gates_off` in `/status`, and a standing panel banner. |
| Job ends | 15 s smoke clear at run duty, then reduced airflow until the loop is under the resume gate. |
| Controller stops reporting | Fire blocked at once, stand-down through cooldown. |
+3 -2
View File
@@ -204,8 +204,9 @@ would not allow.
and the next emission needs a fresh press. The relock waits for the kernel to
finish the queue tail so a controlled stop can never leave FIRE driven.
- **Coolant fire gates.** The armed window requires a fresh `fire_ok` verdict
from the cooling engine (flow verification, over-temperature, lid-IR
emission witness); a stale or failed verdict relocks in-process. The
from the cooling engine (flow verification, over-temperature, the airflow
floors on every fan, lid-IR emission witness); a stale or failed verdict
relocks in-process. The
thermal gates are settings with a wide range whose far end turns the gate
off by value (`COOLING.md` §8a), loudly; the fresh-report rule, the
emission witness, the dead-man and the latch are not settings and stay in