Let the page run a campaign's remaining tests as a queue

A campaign is mostly waiting for the next Start. The page now offers two
queues, and each takes every test of its kinds the campaign does not
already count as satisfied: Unattended for the auto tests, which need
nobody in the room, and Operator and live for the ones that need somebody
at the machine, since they prompt and they fire the laser. The buttons
say how many they would run and ask before starting; the live queue names
the tests that fire and takes the acknowledgment once, for all of them.

A queue runs one test at a time through the runner's single slot, in
prerequisite order. Registration order otherwise, so a run reads down the
page, but a prerequisite inside the queue always goes first. It stops on
the first result that is not a PASS: a FAIL closes the campaign, and
carrying on would only open a second one behind the operator's back. A
test the runner refuses to start is skipped with the reason on the page
and the rest carry on, which is what happens to an auto test waiting on an
operator one: run the attended queue, then the unattended one again.

The queue lives in the runner, not in the tab, so reloading the page or
closing it leaves the run alone. While one is up it holds the machine
between its tests as well as during them, so a single Start and the bench
tools are refused rather than cutting in. Stop the queue cancels what is
still waiting and lets the run in progress finish; Abort ends that one
too, and lands as the non-PASS that stops the queue. Every run a queue
starts records which one put it there.

Also moves the /state ETag test to the end of its class. It invalidates,
timestamps are whole seconds, and a PASS stamped in the same second as an
invalidate is deliberately not inheritable, so on a fast run it decided
the inheritance an earlier test was checking.

forgetest is a dev-only component and can never appear in a coverage map,
so this has no acceptance catalog consequence.
This commit is contained in:
ScottW514
2026-08-21 10:27:50 -04:00
parent 474e14e0e8
commit 0258268aae
8 changed files with 655 additions and 32 deletions
+17 -2
View File
@@ -122,7 +122,22 @@ bench, or one whose `/data` has been wiped, starts from a full campaign.
and the following ones reuse the live session; nothing switches back -
switch on the control panel when done. `cloud.mode-switch` is the one
round trip and starts in GRBL mode.
4. When *Release authorized: YES*, **Export release artifact**, download
4. Or hand the whole list to a queue. **Run what is left** offers two:
**Unattended** takes every `auto` test the campaign does not already
count as satisfied, and needs nobody in the room; **Operator and live**
takes the `operator` and `live` ones, and needs somebody at the
machine, since it prompts and it fires the laser. Each button says how
many it would run, and asks before it starts: the live queue names the
tests that fire and takes the acknowledgment once, for all of them.
A queue runs one test at a time in prerequisite order and stops on the
first result that is not a PASS, because a FAIL closes the campaign. A
test it cannot start is skipped with the reason on the page and the
rest carry on, which is what happens to an `auto` test waiting on an
`operator` one: run the attended queue, then the unattended one again.
**Stop the queue** cancels what is still waiting and lets the run in
progress finish; **Abort** ends that one too. The queue lives in the
runner, so closing the page or reloading it does not disturb the run.
5. When *Release authorized: YES*, **Export release artifact**, download
`acceptance.json` and `acceptance.md`, and commit them as
`releases/v<version>/acceptance.json` and `.md`.
@@ -253,7 +268,7 @@ recorded in `/data/forgetest/bench.jsonl` and never enter a campaign.
catalog.py @test registry, catalog hash
campaign.py the rules (pure functions)
artifact.py export + gate verification
runner.py one run at a time, prompts, abort, takeover
runner.py one run at a time, prompts, abort, takeover, queues
baseline.py the fresh-boot idle state around every run
server.py / page.py HTTP API + the page (forgectrl's access rules)
bench.py / coverage.py bench registry + subprocess runner; the lint