diff --git a/docs/BRINGUP.md b/docs/BRINGUP.md index acbf2c3..3307167 100644 --- a/docs/BRINGUP.md +++ b/docs/BRINGUP.md @@ -984,8 +984,13 @@ Open items only. Anything closed is in `CAMPAIGN-LOG.md`. live job longer than the ring (built and covered, never yet run from the service), the memory guards against a real ceiling rather than a reasoned one, packaged-path boot with `controller_mode = cloud`, the three unobserved - actions, the lid-flash LED, and taking the pause constants from the pulse - header (`CCbp`/`CCbt`) once a capture confirms them. Not inducible from the bench: the + actions, the lid-flash LED, and driving the park and the two lifecycle + periods off the header (`CFrh`, `CCwp`, `CCrp`) once a capture confirms + what they mean; the machine warms up and rests on the factory's measured + timings meanwhile, and logs those keys on every job. (`CCbp`/`CCbt`, read + earlier as the pause constants, are refuted: the factory's tag table marks + them report-only, so they cannot appear in a pulse header at all.) Not + inducible from the bench: the cancel-with-a-rejected-`settings`-action case, a malformed frame (needs a MITM), a body past the memory guard (the service has no such job to send), and a wedged feed (a healthy machine will not stall on request). The pulse header's unenforced safety envelope is item 19: it is diff --git a/forgetest/forgetest/suite/cloud.py b/forgetest/forgetest/suite/cloud.py index df0329a..af55228 100644 --- a/forgetest/forgetest/suite/cloud.py +++ b/forgetest/forgetest/suite/cloud.py @@ -728,7 +728,10 @@ def hunt_lid_open(ctx): description="Pressing the button during a cloud print pauses it the factory way - controlled " "stop, backtrack with the laser off, print:paused - and the next press resumes " "with the laser-off lead, print:resumed; the job then completes and parks. The " - "latch stays unlocked and the armed window open through the pause.") + "latch stays unlocked and the armed window open through the pause. The same " + "print is where the job's lifecycle shows: a print warms up before its first " + "fire and rests after its park, both non-zero, while the connect-time hunt in " + "the same session does neither.") def pause_resume(ctx): ev = ctx.evidence offset = enter_cloud(ctx) @@ -759,8 +762,38 @@ def pause_resume(ctx): ctx.check(not relocked, "the pause relocked or cancelled the job (%s)", relocked[:2]) ctx.confirm("Did the head stop and back up a few millimeters (laser off) on the first press, " "resume on the second, and did the job finish and the app show it complete?") + + # The job's lifecycle, from the same print: a warm-up before the first + # fire and a rest after the park are equipment protection the service + # assumes has happened, and a machine configured to skip them says so in + # the log rather than quietly not doing them. + lines = log_lines_since(GFCLOUD_LOG, offset) + holds = {"warm up": None, "cool down": None} + for ln in lines: + for phase in holds: + if holds[phase] is None and ("%s: holding" % phase) in ln: + holds[phase] = ln.strip()[:160] + if holds[phase] is None and ("%s: skipped" % phase) in ln: + holds[phase] = ln.strip()[:160] + ev["lifecycle"] = holds + ctx.log("warm-up: %s", holds["warm up"]) + ctx.log("rest: %s", holds["cool down"]) + for phase in ("warm up", "cool down"): + ctx.check(holds[phase] is not None, "the print logged no %s at all", phase) + ctx.check(holds[phase] and "holding" in holds[phase], + "the print skipped its %s (%s): the config still carries a zero", + phase, holds[phase]) + # A hunt is not a print: the connect-time hunt ran in this same session + # and must not have held for either period. + hunt_end = next((i for i, ln in enumerate(lines) if "waiting for button" in ln), len(lines)) + early_holds = [ln.strip()[:120] for ln in lines[:hunt_end] + if "warm up: holding" in ln or "cool down: holding" in ln] + ev["holds_before_the_print"] = early_holds + ctx.check(not early_holds, "a hunt or motion held for a print's periods: %s", early_holds[:2]) + settle_cloud(ctx, offset) - ctx.log("PASS: button pause/resume mid-print, job completed and parked") + ctx.log("PASS: button pause/resume mid-print, warm-up and rest observed, job completed " + "and parked") @test("cloud.oversize-stream", title="A print longer than the ring is fed while it plays", diff --git a/forgetest/tests/test_cloud_suite.py b/forgetest/tests/test_cloud_suite.py index 28db395..d9ae240 100644 --- a/forgetest/tests/test_cloud_suite.py +++ b/forgetest/tests/test_cloud_suite.py @@ -32,6 +32,13 @@ def fixture(name): return f.read().decode().splitlines() +# What the build under test logs around a print, in the machine's own format. +WARM_UP_LINE = ("2026-08-17T09:44:02.100000+00:00 gfcloud[1522] INFO " + "machine:_dwell warm up: holding 3.0 s") +COOL_DOWN_LINE = ("2026-08-17T09:45:31.700000+00:00 gfcloud[1522] INFO " + "machine:_dwell cool down: holding 10.0 s") + + def cut(lines, marker, count=1): """(before, after) at the count-th line containing marker (the line itself opens `after`).""" @@ -281,8 +288,13 @@ class CloudSuiteTests(unittest.TestCase): lines = fixture(name) pre, rest = cut(lines, "waiting for button") run_pre, rest = cut(rest, "current state: MachineState.RUNNING") # the PRINT's run + # The excerpt was captured before the machine held for a warm-up and + # a rest; the replay carries those two lines where it emits them now, + # rather than editing what the machine actually said that day. + run_pre = run_pre + [WARM_UP_LINE] pre, rest = pre + run_pre + [rest[0]], rest[1:] mid, tail = cut(rest, at_end) + tail = tail + [COOL_DOWN_LINE] return {"Click Done here": lambda: self.append(pre, delay=0.1), at_run: lambda: (self.append(mid, delay=0.05), self.append(tail, delay=tail_delay))}