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 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. 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 ## Superseded status notes
### Shared machine services — remaining polish, as listed 2026-08-13 ### 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 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 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 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 setting; the settings API keeps it above the ceiling while the ceiling is
(70 °C) it is the gate turned off (§8a). 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 **upstream** sensor gates, because it reads the coolant actually entering
the tube. 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_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_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_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_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_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. | | `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() after = fc.settings()
ctx.check(all(after.get(k, "") == orig[k] for k in CRIT_KEYS), 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}) "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 restored = False
with ctx.grbl() as grbl: with ctx.grbl() as grbl:
+6 -2
View File
@@ -448,7 +448,8 @@ class CriticalTierTests(unittest.TestCase):
merged.update(form) merged.update(form)
tmax = float(merged.get("cool_temp_max") or 33.0) tmax = float(merged.get("cool_temp_max") or 33.0)
tcrit = float(merged.get("cool_temp_critical_c") or 38.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"}) return (400, {"error": "cool_temp_critical_c must be above cool_temp_max"})
self.fc.state["settings"].update(form) self.fc.state["settings"].update(form)
self._describe() self._describe()
@@ -508,8 +509,11 @@ class CriticalTierTests(unittest.TestCase):
self.assertEqual(run.evidence["critical_after"]["verdict"], "OVERTEMP") self.assertEqual(run.evidence["critical_after"]["verdict"], "OVERTEMP")
self.assertEqual(run.evidence["critical_off"]["gates_off"], ["coolant_critical"]) self.assertEqual(run.evidence["critical_off"]["gates_off"], ["coolant_critical"])
self.assertEqual(run.evidence["restored"]["verdict"], "OK") self.assertEqual(run.evidence["restored"]["verdict"], "OK")
self.assertEqual(run.evidence["ceiling_off_accepted"], 200)
posts = self.settings_posts() 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"}) "cool_temp_critical_c": "7.0"})
self.assertEqual(posts[-1], {"cool_temp_max": "", "cool_temp_resume": "", "cool_temp_critical_c": ""}) self.assertEqual(posts[-1], {"cool_temp_max": "", "cool_temp_resume": "", "cool_temp_critical_c": ""})
self.assertEqual(self.grbl.commands.count("M8"), 3) 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 # only SRCREV and PV here - the image manifest leaves *-pin.inc out of the
# layer content hash because the component entry already identifies the # layer content hash because the component entry already identifies the
# pinned source (forgefirm-image-manifest.bbclass). # pinned source (forgefirm-image-manifest.bbclass).
SRCREV = "76115fd87d0b085cd087a59ae0d58213007c42bf" SRCREV = "368fd0c6bb4ce0eea6e9fad39ec4dfec593f914d"
PV = "0.1.0" PV = "0.1.0"