Commit Graph
11 Commits
Author SHA1 Message Date
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 942d9dde99 Drill the backtrack boundary, and pause a streamed print
kernel.backtrack-bounds plays a program, stops it, and holds the readback
to the bytes it played less the deceleration tail: a step past the boundary
has to be refused rather than quietly shortened, and the run at the boundary
has to play out and come back idle. Motors locked, latch locked, duty zero,
so nothing moves and nothing fires.

cloud.oversize-stream pauses and resumes the live-fed print it already has
running. That is the pause the kernel change makes possible, and it costs a
minute of a job that is on the bed either way.

BRINGUP's pause bullet, its ring facts and item 18 all said a ring under a
live feed has nothing left to back into. The gap the writer keeps clear says
otherwise; what is still open for GRBL mode is the bookkeeping above the
ring, not the kernel below it.
2026-08-20 07:41:41 -04:00
ScottW514 7bd9c45fd3 Accept a print longer than the ring
cloud.oversize-stream drives a job the ring cannot hold and checks the
signature of a live feed: the load reporting the job as longer than the
ring, the device in live-feed mode, the kernel's program total growing
during the run, no underrun, and a clean cancel afterwards.

read_program_total gives the catalog the counter that growth is read
from.
2026-08-19 21:21:31 -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
ScottW514 e908db7f3a cloud.* lid/button tests: judge only the print's own window
cloud.lid-during-button-wait failed on a healthy machine: it counted
every "starting run" since the session start, and a cloud session runs
the connect-time hunt and several service moves before the print. The
checks now use the print's own window: runs between "waiting for
button" and the print's ":cancelled" (none allowed); the print's run is
the "starting run" after its button wait (lid-abort, pause-resume); the
lid edge timed against the stop is the last edge before the stop line
(an earlier open to place the scrap is not the one); hunt-lid-open
judges the hunt's own terminal line and refusals before it (service
moves after the hunt are rightly refused with the lid open).
2026-08-16 21:41:10 -04:00
ScottW514 7bf8e3d4ed forgefirm-app: bump to 37854f0 (0.1.8+git) - the park clears the ring first; cloud.lid-abort checks the kernel counters
cloud.lid-abort now proves the park with the machine's own counters
(cloud clears them at every job start, so a completed park reads back
at (0,0)); stale ring bytes replayed ahead of the park would not.
2026-08-16 20:59:54 -04:00
ScottW514 c255b265c5 Acceptance: cloud lid-abort, lid-during-button-wait, hunt-lid-open, pause-resume
Four cloud-mode catalog tests driven from the app with the operator,
proven from the client's log (the same record the wire gets), forgectrl
/status and /cool/status, and the kernel latch readback:

- cloud.lid-abort (live): the lid edge reaches the controlled stop
  within 60 ms, the head parks with the lid still open, the latch
  relocks, the armed window closes, the print ends ':cancelled';
- cloud.lid-during-button-wait (operator): the lid at the white-button
  prompt relocks and cancels; no run starts;
- cloud.hunt-lid-open (operator): the connect-time hunt runs and
  completes with the lid open;
- cloud.pause-resume (live): the button pauses (stop + backtrack) and
  resumes; the job completes and parks; nothing relocks or cancels.

Shared helpers enter/leave cloud mode the way cloud.mode-switch does
and return the head afterward.
2026-08-16 19:11:23 -04:00
ScottW514 a80d7aabfb forgetest: cloud tests prove the session from the client's log and hand the head back
cloud.mode-switch took the optional connect-time firmware probe file as
the evidence of a live session and failed on a bench where that check is
off; the evidence is now gfcloud's own authenticate/ws-connect lines in
the unified log after the switch, the probe recorded when present. Cloud
mode's connect clears the kernel position counters at the head's start
and its hunt homes the head to the corner: the test tells the runner the
counters were re-zeroed (Context.counters_rezeroed) and jogs the head
back by the counter-measured displacement, and hands the lid lamp back
at the level it found. cloud.gfhome-homing documents that it leaves the
machine homed at the corner.

Baseline: a displaced head is jogged back along its own path by the
kernel-measured X/Y delta through the GRBL controller (bounded 100 mm,
waits out a controller respawn backoff); Z is never touched.
2026-08-16 14:21:54 -04:00
ScottW514 c0f53a865f forgetest: the release acceptance tool and the bench diagnostics page
A stdlib-only daemon on the dev image (HTTP :8090) that runs the acceptance
catalog against the machine from a self-contained page, keeps the append-only
result log under /data/forgetest, and exports the release artifact the gate
reads. Tests declare kind (auto / operator / live), hardware (api / takeover),
coverage globs, prerequisites, and core membership; a test's domain
fingerprint is the hash of the manifest files its globs select plus the
platform and its own implementation, so a PASS stays valid exactly while
nothing it covers changed. Campaign rules: a FAIL ends the campaign, the core
(image health, kernel latch and drills, one live emission witness) is never
inherited, invalidate-all forces a full campaign, no SKIP. Live tests need the
operator acknowledgment and the physical arm press through the controller;
takeover tests stop forgectrl for the duration with a crash-recoverable
marker; the tool never touches the laser latch.

Catalog v1: 24 tests ported from the proven bench drills with their recorded
pass criteria (image, kernel K1-K3 and fire A/B/U, forgectrl API and logs,
motion incl. dead-man, cooling, live laser, camera, update, cloud). The bench
tab lists every scripts/bench tool and runs the board-side ones as
subprocesses (takeover tools wrapped). 44 host unit tests, including the gate
verification fixtures. Installed only by forgefirm-image-dev, with the bench
scripts under /usr/share/forgetest/bench.
2026-08-15 15:57:11 -04:00