forgetest: witness the airflow behind the beam; harnesses carry the armed flag

The cooling verdict now carries the engine's own armed flag, so the
stand-in engines in both null-sink harnesses publish it. The lifecycle
harness gains two cases: an engine that never takes the armed window
must produce a refused arm and no emission, and one that takes it a
couple of seconds late must produce a wait and then a normal arm. The
late case is the one that proves the controller keeps reading the
verdict while it is blocked in the arm; without that every job would
fail there.

The emission witness gains the bench form of the same rule: no sample
may show the laser firing while the cooling engine reports a phase that
runs the fans at their idle duty. That is what a burn with no airflow
looks like from the outside, and nothing in the catalog looked for it.
This commit is contained in:
ScottW514
2026-09-03 12:26:59 -04:00
parent aa0d86635c
commit db9acf9910
3 changed files with 1039 additions and 943 deletions
File diff suppressed because it is too large Load Diff
+5 -2
View File
@@ -375,10 +375,13 @@ def wait_state(sock, log, prefix, timeout=5.0):
def publish_verdicts(path, stop):
"""Publish a fresh, clean cooling verdict every 0.5 s (the arm flow
refuses without one; freshness window is 2 s). Same-host monotonic
clock, atomic rename so the reader never sees a torn file."""
clock, atomic rename so the reader never sees a torn file. "armed"
is the engine's acknowledgment that it has taken the controller's
armed window; the arm waits for it, so a stand-in engine that means
to let jobs run must assert it."""
while not stop.is_set():
body = ('{"ts_mono":%.3f,"fire_ok":true,"hold":false,'
'"resume_ok":true,"reason":""}'
'"resume_ok":true,"armed":true,"reason":""}'
% time.clock_gettime(time.CLOCK_MONOTONIC))
tmp = path + ".tmp"
with open(tmp, "w") as f: