Commit Graph
8 Commits
Author SHA1 Message Date
ScottW514 5249c1ecf8 CAMPAIGN-LOG: the job's limits pass through on a live session
cloud.pause-resume passed on dev image 20260821220926 with the service's
hunt windows (10 to 50 C) ignored as looser and the print's window (33 C,
5 C floor, 116 rpm air-assist floor) matched. The test now quotes the
print's job-limits line rather than the session's first (a hunt's), and
keeps the engine line that carries the header beside the last one.
forgectrl pin moves to e0b41b3 (the "not stricter" notice once per value).
2026-08-21 18:39:43 -04:00
ScottW514 d530f773e2 cloud.pause-resume reads the job's limits through; pin the pass-through
The pulse header's envelope now reaches the cooling engine: the cloud
client (python3-gfhardware c34faa1) derives the coolant window and the
fans' minimum speeds from the header and rides them on every report,
and the engine (forgectrl 57f6064) resolves each as the stricter of its
setting and the job's, never looser and never overruling an off gate.
cloud.pause-resume, which runs a real print, now also reads the client's
"job limits from the header:" line and the engine's "effective limits:"
line from the two logs; two host cases cover the failure paths. The
needle guard lists the engine's phrases as not the app's.

COOLING.md section 2 explains what a cloud job brings with it; BRINGUP
item 19 records the pass-through as landed. Pins: forgectrl 57f6064,
forgefirm-app c34faa1 (0.1.14+git); fetch-verified.
2026-08-21 18:07:41 -04:00
ScottW514 6c1d68f2c3 Judge the cloud resume on the lines the app logs; guard every needle against the pinned app
cloud.pause-resume failed a print that paused, resumed with its laser
lead, completed and parked: the test waited for the single line "button
pressed while paused; resuming", and the app has logged that as two
lines since its feeder work ("button pressed while paused", then
"resuming (laser lead N ticks)" from _resume_retraced). The replay
fixture carried the old wording, so the host test kept passing.

The pause and resume are now judged on PAUSE_LINES + RESUME_LINES
through one checker shared by the three tests that drive a pause
(cloud.pause-resume, the streamed pause, the pause-then-lid test), which
also fails on the app's "resume refused" line with the reason. The
fixture carries the app's two lines.

So the wording cannot drift silently again: tests/test_cloud_needles.py
reads every log phrase the cloud suite greps for out of cloud.py (the
left side of each `x in ln`, every wait_log needle, the mark tuples, and
the phrases it builds) and checks each against the logger calls in the
app sources at the revisions the recipes pin, read from the manifest
cache the tree manifest builds (the sibling checkouts locally),
placeholder-aware under a rule that never lets a placeholder stand for
the phrase itself. CI now builds the tree manifest before the unit
tests so the cache is there. The old needle fails that check.

Replays added: the second press seen but no retraced restart, and a
refused resume. 159 unit tests pass; coverage lint clean. No catalog
consequence beyond the suite module's own hash.
2026-08-21 14:09:51 -04:00
ScottW514 aaabfdf9b9 Check the progress a print reports, where a print already runs
Two tests already run a print end to end, and progress is a property of a
running print, so the checks go there rather than into a test of their own
that would cost the operator another job.

cloud.pause-resume takes the job that fits the ring: the client names the
length it is reporting against, and the operator is asked the question only a
person can answer, whether the bar actually moved.

cloud.oversize-stream takes the job that does not fit, which is where a moving
denominator would show: the kernel's program total grows all run long under a
live feed, and the test already samples it growing, so the check is that the
figure progress divides by is larger than that - the job, not the count the
ring had swallowed when the run started.

The forgetest replay plays a captured log from a build that predates the line,
so it carries the line where the current build emits it, as it already does
for the warm-up and the rest.

BRINGUP's cloud item now says a print reports itself again, and what is left
on it is a print watched from the app.
2026-08-20 14:16:27 -04:00
ScottW514 d122a6ff1d Check a print's warm-up and rest where a print already runs
cloud.pause-resume runs a print end to end, which is exactly what the job
lifecycle needs to be seen: a non-zero hold before the first fire, a non-zero
rest after the park, and neither on the connect-time hunt in the same
session. Folding the checks in there costs the operator nothing, where a
test of its own would cost another print.

The forgetest replay noticed first: it plays a real captured log from a build
that predates those lines. Rather than editing what the machine said that
day, the replay carries the two lines where the current build emits them.

BRINGUP's cloud item now names what is actually open on the header keys, the
park and the two periods, and records that the pause constants were looked
for in the wrong place.
2026-08-20 10:05:38 -04:00
ScottW514 cd5098b6b3 Acceptance catalog: merge the tests that share a setup (39 -> 35, live 10 -> 7)
A sweep of the whole catalog for the overlap the drill work found. The test
applied was "do these share a SETUP", not "do these share a subsystem":
combining only pays where a human waits - an arm press, scrap, a takeover,
a mode entry, a lid choreography - and it costs failure isolation, because
the campaign inherits per test and a merged test invalidates as a unit. The
17 auto tests were left alone for exactly that reason: they cost no operator
time and separate ids give the coverage map and the domain invalidation
finer teeth.

  kernel.k3-unlock + kernel.fire-abu -> kernel.fire-line
      Four phases behind ONE takeover of the pulse device instead of two:
      A/B/U on the FIRE line, then the mid-ramp unlock. Same HV-not-good
      gate, same zero duty, same safe state on the way out.

  laser.expected-stop + laser.kill-mid-fire -> laser.armed-kill
      Both ways an armed job is killed, on one scrap setup: the supervisor's
      expected stop (with its separate operator-judged restart) and then a
      SIGKILL of the restarted controller. The second phase re-reads the pid
      after the restart, so it kills the process the supervisor just spawned.

  laser.lid-cancel-mid-fire + laser.pause-resume-live -> laser.pause-resume-lid-cancel
      One armed burn in the order the factory uses the machine's controls:
      press (pause - emission stops, latch stays UNLOCKED, armed window
      stays open), press (resume), lid (cancel, reset without alarm, return
      to the job start, button latch SET). One arm press instead of two.

  cloud.lid-abort + cloud.interlock-abort-park -> cloud.lid-interlock-abort
      Two prints in one test: the lid, then the interlock with the lid opened
      during the park. The tail both share - park complete, kernel counters
      back at the job start, ':cancelled', latch locked, armed window closed -
      is one helper now, so both triggers are judged the same way.

Not merged, though they share code: cloud.gfhome-homing and cloud.hunt-lid-open.
Both drive Machine._hunt (gfhome.py and gfcloud.py build the same gfhardware
machine, so the lid ungating is the same lines), but each is about the opposite
value of the same variable - gfhome's homing is camera-corrected and needs the
lid CLOSED in GRBL mode, the hunt test needs it OPEN through a cloud-mode
connect. Merging would put a mid-test mode switch back into the cloud tests.

Shared live-test prologue (the arm cue, the mark job, the wait for the emission
witness) and the post-kill trail/judging are helpers now.

Host proof: 104 unit tests - the merged cloud test replays the machine's own
lid-abort excerpt twice, once with the interlock substituted for the trigger,
and three negative cases hold it honest (loop already open, a park the lid can
interrupt, a stop that is not edge-driven) - and the coverage lint at 0
uncovered across 35 tests. Catalog time 137 -> 125 minutes, 96 of it attended.
2026-08-17 08:39:35 -04:00
ScottW514 0870a835c4 Acceptance catalog: the rest of the lid/button/interlock drills, and a pause/resume chain-timing tool
Catalog 34 -> 39. Every remaining bench drill of the lid/button parity work
is now a test, with drills that exercise the same path combined:

  motion.lid-cancel-home   also cancels from a hold - a job paused on the
                           button is ended by the lid, never resumed - so the
                           armed window and the hardware button latch stay in
                           agreement by construction.
  motion.cancel-abort      also asserts what a sender abort must NOT do: it
                           stops where it stopped and never returns home. The
                           return-to-start belongs to the lid policy alone.
  motion.interlock-cancel-park (new)  the interlock loop cancels like the lid,
                           and the lid opened during the return home does not
                           interrupt it.
  motion.lid-policy-hold (new)  the other policy: Door park, no cancel, no
                           return, and a cycle start finishes the move. The
                           setting is restored on the way out.
  laser.pause-resume-live (new)  the button pause/resume during a live cut:
                           emission stops, the latch stays UNLOCKED and the
                           armed window open (a pause is not a cancel), the
                           next press resumes and the job finishes.
  cloud.interlock-abort-park (new)  the same interlock/park pair in cloud mode.
  cloud.pause-cancel-paths (new)  the two non-finishing ends of a print, each
                           from the state the factory ends it in: paused on the
                           button then cancelled by the lid, and cancelled from
                           the app while running.

The shared cancel tail (reason reported, reset without an alarm, position
kept, head back at the job start with the KERNEL counters confirming it) is
now one helper, so every trigger is judged the same way.

scripts/bench/resume_dark_lead.py: samples LASER_ON, FIRE, HV_ENABLE, the
charge-pump watchdog, the button and the doors off the SoC pads at ~2 kHz
through /dev/mem, with motion dated from the kernel step counters, across a
pause and a resume. Levels are taken at idle and everything after is reported
as a change from that baseline, so no polarity assumption is baked in. Dry by
default, with --auto driving the pause and resume through ! / ~ for an
unattended rehearsal; --run live adds the dark lead between FIRE and LASER_ON,
in milliseconds and in millimeters at the job's feed.

Bench registry: argument specs can name a flag (--feed 600) instead of being
positional, and an optional argument with an empty default is left off the
command line entirely.

Host proof: 104 unit tests (20 in the cloud suite - the two new cloud tests
replay the machine's own lid-abort excerpt, with the interlock and the app
cancel substituted for the trigger, and fail for the right reasons), coverage
lint 0 uncovered across 39 tests.
2026-08-17 07:54:47 -04:00
ScottW514 86ce0419e5 forgetest: cloud tests stay in cloud mode; the page can ignore prerequisites
The cloud job tests (lid-abort, lid-during-button-wait, hunt-lid-open,
pause-resume) run in cloud mode and leave the machine there: enter_cloud
reuses a live session (pid-scoped from the client's own websocket state
lines) and switches once from GRBL mode, declaring the change to the
baseline; nothing switches back. Each test judges the log from its own
window, the print by its own "print [id]: finished" line, and waits the
service's follow-up moves out (wait_quiet) before it ends. hunt-lid-open
restarts the cloud client through the supervisor's stop/start lever for
a fresh connect. The former switch-back is what failed the last bench run
of hunt-lid-open (409 machine is not idle: the service was still
re-finding the head after the lid closed).

The baseline is mode-aware: in cloud mode the client owns the GRBL
controller's init values, the lid lamp, and the position counters; the
mode itself is preserved unless the run declared the change
(Context.mode_changed); controller_mode is never handed back as a bare
setting (a bare write left the persisted mode out of step with the live
one). The undeclared-change restore now uses the captured state, which
the post pass never saw before.

The acceptance page gets an "Ignore prerequisites" switch (remembered by
the browser): POST /start {ignore_requires} starts a test whose requires
are unmet, and the run records the unmet prerequisites in its evidence
and log; they stay required for the release. The cloud tests' requires
no longer chain through cloud.mode-switch.

Proof: tests/test_cloud_suite.py replays the four tests on the bench's
own gfcloud excerpts (fixtures/) and the run loop's emitted pause/resume
lines under the real runner Context against a fake forgectrl; baseline
mode tests and the server override test; the whole suite (98) and the
coverage lint pass. Catalog consequence: cloud.* fingerprints move with
the module; the catalog hash moves with the requires.
2026-08-17 06:42:48 -04:00