The ceiling at its off end stands with the default critical line

forgectrl pin 368fd0c: the critical-line cross-check binds only while the
ceiling is a gate. cooling.critical-tier pins that a POST setting the
ceiling to its off end is accepted with the default line (the unit fake
mirrors the exemption); CAMPAIGN-LOG records the gate-off failure on dev
image 20260822165832 that found it, and the critical-tier pass.
This commit is contained in:
ScottW514
2026-08-22 13:43:36 -04:00
parent 9fae47cf14
commit 6458f6aec0
5 changed files with 35 additions and 6 deletions
+16
View File
@@ -3402,6 +3402,22 @@ names itself only on its rising edge and the critical fault had overwritten
it. The engine now re-publishes the standing hold's reason when a critical
fault clears (no new log line), and `cooling.critical-tier` checks it.
## 2026-08-22: the board temperatures on an image, and a cross-check that bound too far
Dev image `20260822165832` (forgectrl 76115fd pinned by forgefirm 9fae47c):
`cooling.critical-tier` PASS; `cooling.gate-off` FAIL in its off leg: the
POST that sets the ceiling to its off end (60 C) came back `400
cool_temp_critical_c must be above cool_temp_max`, because the step 3
cross-check compared the default critical line (38 C) against the ceiling
wherever the ceiling stood. Under the settings rule every gate is off by
value on its own, so a ceiling at its off end is no ceiling and the
critical line stands alone as the fail tier: the cross-check now binds
only while the ceiling is a gate (its own table row's off end decides),
`cooling.critical-tier` pins that the off-end POST is accepted with the
default line, and the unit fake mirrors it. The test restored the
settings on its failure path as designed (the trip leg had passed: a 6 C
ceiling read `OVERTEMP` in 1 s).
## Superseded status notes
### Shared machine services — remaining polish, as listed 2026-08-13
+4 -3
View File
@@ -287,8 +287,9 @@ The engine uses the factory's coolant windows:
ends with the session; the ceiling's pause keeps holding while the loop is
hot, and the next session judges the line afresh. A cloud job's header
carries no critical line for the coolant, so this one is always the local
setting; the settings API keeps it above the ceiling, and at its top
(70 °C) it is the gate turned off (§8a).
setting; the settings API keeps it above the ceiling while the ceiling is
a gate (a ceiling at its off end leaves the line standing alone), and at
its top (70 °C) it is the gate turned off (§8a).
The **upstream** sensor gates, because it reads the coolant actually entering
the tube.
@@ -380,7 +381,7 @@ start of every run, so a change takes effect on your next job.
| `cool_confirm_max_s` | 480 s | 60 to 3600 s | | How long a suspicion may stay unresolved before it escalates to a fault. |
| `cool_temp_max` | 33 °C | 5 to 60 °C | 25 to 38 °C | Run ceiling: above it, hold. `60` turns the gate off (§8a). |
| `cool_temp_resume` | 31 °C | 5 to 59 °C | 20 to 36 °C | Resume gate: below it, continue. Always kept below the ceiling. |
| `cool_temp_critical_c` | 38 °C | 6 to 70 °C | 36 to 45 °C | Critical line: a fault with no resume in the job (§5). Always kept above the ceiling; `70` turns the gate off. |
| `cool_temp_critical_c` | 38 °C | 6 to 70 °C | 36 to 45 °C | Critical line: a fault with no resume in the job (§5). Kept above the ceiling while the ceiling is a gate (a ceiling at 60 leaves the line standing alone); `70` turns the gate off. |
| `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` | 6400 rpm | 0 to 20000 | 5800 to 7000 | Exhaust fan floor at run duty (§3a). `0` turns the gate off. |
+8
View File
@@ -422,6 +422,14 @@ def critical_tier(ctx):
after = fc.settings()
ctx.check(all(after.get(k, "") == orig[k] for k in CRIT_KEYS),
"the refused POST changed a setting: %s", {k: after.get(k) for k in CRIT_KEYS})
# A ceiling at its off end is no ceiling: the default line stands
# alone, and the POST that turns the ceiling off is accepted.
st, body = fc.post("/settings", params={"cool_temp_max": str(ceil.get("hi"))})
ev["ceiling_off_accepted"] = st
if st == 200:
fc.post("/settings", params=orig)
ctx.check(st == 200, "the ceiling at its off end %s was refused against the critical line: %s %s",
ceil.get("hi"), st, body)
restored = False
with ctx.grbl() as grbl:
+6 -2
View File
@@ -448,7 +448,8 @@ class CriticalTierTests(unittest.TestCase):
merged.update(form)
tmax = float(merged.get("cool_temp_max") or 33.0)
tcrit = float(merged.get("cool_temp_critical_c") or 38.0)
if tcrit <= tmax:
ceiling_off = tmax >= self.ROWS["cool_temp_max"][3]
if not ceiling_off and tcrit <= tmax:
return (400, {"error": "cool_temp_critical_c must be above cool_temp_max"})
self.fc.state["settings"].update(form)
self._describe()
@@ -508,8 +509,11 @@ class CriticalTierTests(unittest.TestCase):
self.assertEqual(run.evidence["critical_after"]["verdict"], "OVERTEMP")
self.assertEqual(run.evidence["critical_off"]["gates_off"], ["coolant_critical"])
self.assertEqual(run.evidence["restored"]["verdict"], "OK")
self.assertEqual(run.evidence["ceiling_off_accepted"], 200)
posts = self.settings_posts()
self.assertEqual(posts[1], {"cool_temp_max": "6.0", "cool_temp_resume": "5.0",
# refused cross-check, the ceiling at its off end (accepted), its undo, then the low leg
self.assertEqual(posts[1], {"cool_temp_max": "60.0"})
self.assertEqual(posts[3], {"cool_temp_max": "6.0", "cool_temp_resume": "5.0",
"cool_temp_critical_c": "7.0"})
self.assertEqual(posts[-1], {"cool_temp_max": "", "cool_temp_resume": "", "cool_temp_critical_c": ""})
self.assertEqual(self.grbl.commands.count("M8"), 3)
@@ -2,5 +2,5 @@
# only SRCREV and PV here - the image manifest leaves *-pin.inc out of the
# layer content hash because the component entry already identifies the
# pinned source (forgefirm-image-manifest.bbclass).
SRCREV = "76115fd87d0b085cd087a59ae0d58213007c42bf"
SRCREV = "368fd0c6bb4ce0eea6e9fad39ec4dfec593f914d"
PV = "0.1.0"