mirror of
https://github.com/openglow-org/forgefirm.git
synced 2026-10-15 17:19:55 -07:00
558889659c1ec9c3acec2f00c02c682451fd4eb1
100
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
558889659c |
setup_burns: the module docstring describes setup.focus alone
setup.floor left the catalog in |
||
|
|
4c6ca8d701 |
test_setup_suite: only registered tests named
The file named setup.mirror (gone at |
||
|
|
f9c298a347 |
Catalog: eleven tests leave for their host proofs
Each left after its host cases passed (forgectrl 1dc85f6) and with the coverage lint clean at the pins, its only failure the known setup.check-lens entry on src/lensfind.*: - forgectrl.tokens: host_routes.sh's scoped tokens (a route refused to a token not given it, neither token in the list or the store), with tokens_test and auth_scoped_test. - update.product-gate: host_update.sh's product gate on the upload, with fwproduct_test. - setup.first-run-page: tests/wizard_walk.py. - setup.check-sensors, setup.check-intakes, setup.check-cameras, setup.check-flow-verify, setup.check-envelope, setup.floor: host_setup.sh's checks and the floor burn, with wizcalc_test. envelope_test.py's cases are unchanged. setup.floor's emission stays with setup.focus and laser.emission-witness. - motion.job: jobpost_test, with jobrun_test and lease_test; jobpost.* is covered by exthost.motion-job, which stays. - exthost.motion-jog: host_routes.sh's credential case. Two HOST_TESTED rows, each a binary forgectrl's build.yml runs at the pin c274f19 that proves the path the lint named: src/fwproduct.* to fwproduct_test, src/grblport.* to grblport_test. exthost.motion-job now requires motion.jog-roundtrip in place of motion.job. bedsize.py held setup.check-envelope alone and goes; forgectrl.py loses _words_of and its hw import, motion.py the job runner's helpers, setup_burns.py FLOOR_KEY and FLOOR_KEYS. forgetest's own tests: test_setup_dark.py loses the sensors and intakes judges and the departed ids; test_setup_burns.py loses the floor's cases, and the guards burn() and preview_ok() hold for the focus burn (the program's tail and reach, the thickness in inches and the one refused, a prompt not answered, the witnesses) run on setup.focus. Stay: forgectrl.auth (the fuse view's button refusal on the real button); forgectrl.settings-bounds (the lid lamp: cam.c writes /sys/glowforge/pic/lid_led, not under GF_SYSFS_ROOT); forgectrl.panel-serves (src/ui.h, which no binary at the pin proves; Z inside the lens reach is setup.check-lens's); logs.routing; setup.account-login (the account's render and its home, image content); setup.ssh-until-reboot (src/paths.h); setup.machine-name (udhcpc -x hostname and no mDNS responder, image content); setup.first-run-flow (src/button.*, led.*, advisories.*, hooks.h and the four advisory documents); exthost.motion-job (jobpost.*, which no binary at the pin proves). Proof: the coverage lint, 55 tests; pyflakes; test_setup_burns, test_setup_dark, test_takeover_position and test_image (54 OK). Shared text moved the fingerprints of exthost.armed-freeze, exthost.platform, exthost.service, forgectrl.auth, forgectrl.panel-serves, forgectrl.settings-bounds, laser.armed-kill, laser.emission-witness, laser.pause-resume-lid-cancel, motion.cancel-abort, motion.deadman, motion.jog-roundtrip, motion.liveness-probe, motion.microstep-modes, motion.pacing, motion.release, motion.step-timing-under-load, setup.account-login, setup.check-airflow, setup.check-lens, setup.check-motion, setup.check-switches, setup.cloud-header-capture, setup.first-run-flow, setup.focus, setup.machine-name, setup.ssh-until-reboot and update.slots-and-signature; exthost.motion-job's own text moved with its requires. |
||
|
|
754c239e08 |
test_image: the hostname and the banner scripts, run over a scratch tree
MachineNameTests runs the layer's own forgefirm-hostname and forgefirm-banner in private mount, UTS and network namespaces: /run a fresh tmpfs, a scratch directory over /sys/class/net, /etc a read-only overlay with the scratch hostname and issue over the host's (so each script makes its copy under /run/forgefirm and writes through its own bind mount, as at boot). unshare -Urmun where user namespaces are open, sudo -n unshare -mun otherwise; skipped only where neither makes a namespace. - the name: wlan0's last four hex digits (forgefirm-12ab with eth0 beside it), eth0's with no wlan0, eth0's with a zero wlan0 MAC; the live name and /etc/hostname agree. - the banner: the image's text, then the heading and one URL per global address of wlan0 (a veth carrying 192.0.2.5, 2001:db8::5 and fe80::5): https://192.0.2.5/ and https://[2001:db8::5]/, the link-local address left out, no marker line, no .local name. Proof: test_image.py under Linux by both namespace routes (9 tests OK), and on Windows with the two skipped. Mutants of scratch copies of the scripts, each failing: eth0 read before wlan0, a zero MAC taken, the file written without the script's bind mount, a marker line in the banner, a .local URL, the link-local address kept. setup.machine-name stays: the DHCP client's -x hostname and no mDNS responder on the image are image content. Nothing under meta-forgefirm changed. |
||
|
|
b3a40b4aea |
Catalog: twelve tests leave for their host proofs; image.health reads the board temperatures
Each test below leaves for the host cases that now hold it, every one
run and passing before this commit:
- cooling.fans-quiet-after-motion, cooling.gate-off (its engine half),
cooling.floor-and-warm-up, cooling.tec-drive, cooling.fire-watch-tiers,
cooling.crash-watch-plumbing, cooling.critical-tier and
cooling.fan-gate-trips: forgectrl's cool_flow_test and cool_gate_test
(forgectrl 8e9cc0a). The jog and M8/M9 report stays in grblHAL's
cooling_test.c and cool_report_test.py; real heater climb, a lit tube,
tach spin-down and fans meeting their floors stay with
cooling.flow-verify, cooling.flow-under-load, cooling.fail-tier-stop,
cooling.aa-offset-calibrate and setup.check-airflow.
- motion.gate-waits-for-lid: super_test D2 (forgectrl 8e9cc0a). The
physical lid read stays with setup.check-switches.
- motion.lid-cancel-home: laser_lifecycle_test's hold-lid-cancel (forgefirm
|
||
|
|
107676b77d |
laser_lifecycle_test: the lid cancels a job held on the button
Rule 16, hold-lid-cancel: a travel job held by a press, then the lid opened. The controller reports "lid opened - job canceled", prints the reset banner, raises no alarm, and returns the head to the job start (MPos X within 0.05 mm of where the job began); a press once the lid is closed again resumes nothing (Idle, the position unchanged). This is the part of motion.lid-cancel-home no host test held: the lid during Run is test_lid_cancels_and_returns, and the kernel playing the return stays with laser.pause-resume-lid-cancel's live lid phase (check_kernel_returned). Proof: the whole harness passes on the null-sink build as grblHAL's CI builds it (-DGFMCODE_WAIT_S=5.0): every earlier case, then hold-lid-cancel, lid-hold and sigterm-mid-job. Acceptance cost: none. scripts/bench/ is outside every fingerprint. |
||
|
|
6594f57362 |
Catalog: motion.port-jog and motion.tray leave; two leftovers of the audit
Two catalog tests leave for the host proofs that carry them; the catalog goes from 80 to 78 (78 @test in forgetest/forgetest/suite/). - motion.port-jog. forgectrl's tests/host_routes.sh (7b2f629 on e69d967) runs the controller port's routes against the port's stand-in: a jog refused without the token, a jog that is no number 400 with nothing on the port, a numeric jog on the port as G91 G21, cancel, release and energize each one line, GET /motion/state passing the port's state on with state and sender, GET /motion/sender 200. The port beside a sender, its polls, the cancel, the client first and the bound are grblHAL's ctlport_test.py and forgectrl's grblport_test; motion.release keeps the physical currents. The helpers before it (_port_state, _port_idle, _words) stay: bedsize, laser and motion.job read them. - motion.tray. host_routes.sh holds POST /motion/tray (tray=in on the port, tray=up 400) and the panel page's switch (id="traybtn", trayToggle, /motion/tray); grblHAL's tray_test.py the mode, its offset, its persistence and its frame; forgectrl's tray_test the daemon's side. suite/tray.py held only it: the module and its import go. The coverage lint then named forgectrl src/tray.c and src/tray.h, and nothing else; grblhal-glowforge's src/glowforge_tray.* stays covered by another catalog test, so it gets no row. One HOST_TESTED row takes forgectrl's, to forgectrl:tests/tray_test.c on forgectrl:.github/workflows/build.yml, which the pin c274f19 runs (./build/tray_test). Two of the audit's leftovers, in the same change: - test_manifest.py's ImplementationHashTests sat after the __main__ guard, so running the file directly skipped it. It is above the guard now, byte for byte: python3 -m unittest test_manifest.py and python3 test_manifest.py (from tests/, where its helpers import resolves) each run its 7 cases, and 34 in all; before, the direct run ran none of them. - laser_lifecycle_test.py's test_lid_open_in_wait said an alarm is raised; the docstring now says what the test asserts: the lid-open cancel is reported and no alarm is raised. The assertions are unchanged and the harness was not run. The coverage lint on the tree manifest finds no uncovered path and no host-tested row problem across the 78 tests; its one finding is setup.check-lens's empty src/lensfind.* entry. pyflakes reports nothing on motion.py, manifest.py, test_manifest.py or laser_lifecycle_test.py. The full forgetest suite was not run. Fingerprints, measured against the parent: 17 tests move with motion.py's shared text: the 11 left in motion.py, and homing.manual, laser's four and setup.check-envelope, whose modules read it. The pending pin bumps from forgectrl c274f19 and forgeext 2d0391e already owe all 17; this deletion adds none to the owed list. The two leftovers move no fingerprint. releases/ is unchanged. |
||
|
|
40c478d56c |
Catalog: forgectrl.lease leaves
forgectrl.lease leaves for forgectrl's tests/host_lease.sh (0086e65), which passed as build.yml runs it: with the gate open and cnc/state idle, the switches check held at its first wait names wizard:switches, kind hardware, as the holder; POST /diag/flow-verify, /curve/record, /logs/export, /mode with the mode in force, /controller/start and /settings are each 409 in the holder's name, none for an idle machine or a closed gate; the abort frees the lease and the same settings write is 200; lease.changed carries the take before the free. lease_test and super_test case K hold the lease's own rules. The catalog goes from 81 to 80 (80 @test in forgetest/forgetest/suite/). _EventStream, _lease and _text, which only it read, go with it, and so does urllib.parse, which only _EventStream used; socket stays for lan_ip. No test requires the id, and no unit test names it. The coverage lint on the tree manifest then named forgectrl src/events.c and src/events.h, and nothing else beside setup.check-lens's empty src/lensfind.* entry. One HOST_TESTED row takes them, to forgectrl:tests/events_test.c on forgectrl:.github/workflows/build.yml, which the pin c274f19 runs (./build/events_test). The lint then finds no uncovered path and no host-tested row problem across the 80 tests; its one finding is setup.check-lens's. test_manifest.py (34) passes, and pyflakes reports nothing on forgectrl.py or manifest.py. The full forgetest suite was not run. Fingerprints, measured against the parent: 26 tests move with forgectrl.py's shared text: the 4 left in it (auth, panel-serves, settings-bounds, tokens), the 5 left in exthost.py and the 17 of the setup family, whose modules import lan_ip from it (exthost.py and setup.py, and through setup.py the rest). The pending pin bumps from forgectrl c274f19 and forgeext 2d0391e already owe 24 of them; exthost.platform has been owed since |
||
|
|
70e13bdd88 |
Catalog: setup.mirror and logs.tree-tail-export leave; the catalog's address in forgetest-ci
Two catalog tests leave for the host proofs that carry them; the catalog
goes from 83 to 81 (81 @test in forgetest/forgetest/suite/).
- setup.mirror, for forgectrl's tests/host_ext.sh (0086e65): with a
temporary account and two logins, a package's check that session A
starts is owned and A's, owned and not B's, and the token's with no
session; B's abort is 409 and the run goes on; B's takeover makes it
B's; B's abort ends it aborted. Its section header, "the lifecycle",
held only it. setup.check-sensors and setup.account-login stay.
- logs.tree-tail-export, for forgectrl's tests/host_guard.sh (0086e65):
GET /logs names forgectrl, the tail carries a line just written, an
unknown logger is 404, and the export carries README.txt, the
installer's directory (a probe without its MAC or IP address, and
install.log) and system/extensions.json, with neither the panel token
nor the camera key in any member. _LOG_COVERS, the _PROBE_ constants,
and the gzip, io, and tarfile imports, which only it read, go, and so
does logs.routing's requires on it. logs.routing stays.
forgectrl.lease stays. Its proof passed (forgectrl's tests/host_lease.sh,
0086e65), but its deletion left forgectrl src/events.c and src/events.h
uncovered, a path outside the two this chunk may row, so it does not
land.
The coverage lint then named forgectrl src/sanitize.c, src/sanitize.h,
src/extpkg.c and src/extpkg.h, and nothing else. Two HOST_TESTED rows
take them, to forgectrl:tests/sanitize_test.c and
forgectrl:tests/extpkg_test.c on forgectrl:.github/workflows/build.yml,
which the pin c274f19 runs (./build/sanitize_test, ./build/extpkg_test).
forgetest-ci, after the tree-manifest step: the key comparison moves
there unchanged, and a step beside it checks the catalog's address.
From forgectrl-pin.inc's SRCREV it reads src/extpkg.h out of the
manifest cache (git show) and takes EXTPKG_INDEX_URL; from the
checked-out catalog's tools/publish.py it takes the basename of the file
passed to gh release create; the define must be exactly
https://github.com/openglow-org/forgefirm-extensions-catalog/releases/latest/download/
and that basename. A missing pin, define, or basename fails. Nothing
requests the address or fetches the asset. The step's run text, taken
from the workflow, passes from the tree root (c274f19's address and
index-1.ffi) and on a /tmp copy of the tree; controls on that copy,
publish.py passing index-2.ffi and extpkg.h naming another repository:
each fails the comparison. The key step's text is unchanged and was not
run again.
test_manifest.py (34) passes. The coverage lint on the tree manifest
finds no uncovered path and no host-tested row problem across the 81
tests; its one finding is setup.check-lens's empty src/lensfind.*
entry. pyflakes reports nothing on setup.py, logs.py or manifest.py.
The full forgetest suite was not run.
Fingerprints, measured against the parent: 23 tests move with
setup.py's and logs.py's shared text: the 5 left in setup.py, the 12 in
the modules that read setup.py (setup_dark.py's 9, setup_burns.py's 2,
bedsize.py's setup.check-envelope), the 5 left in exthost.py, which
reads it too, and logs.routing. The pending pin bumps from forgectrl
c274f19 and forgeext 2d0391e already owe 21 of them, and
exthost.platform has been owed since
|
||
|
|
b90299acdc |
Catalog: the package routes, the catalog, a package's check and the camera key leave
Four catalog tests leave for the host proofs that carry them; the
catalog goes from 87 to 83 (83 @test in forgetest/forgetest/suite/).
Each proof passed; none skipped.
- exthost.package-routes, for forgectrl's tests/host_ext.sh (05848ac):
against a compiled stand-in that is a live host, GET /ext/status and
POST /ext/package are 403 without the token with the host not run,
the status passes the host's list through with host.running true and
the stand-in's pid, enable runs as the host's command, a bad action
and a bad id are 400 with nothing run, and a package that is not
installed is 409 in the host's words. extpkg_test holds the relay's
argv; the state file, the required-holds directory and removal are
forgeext's install_test.py and run_test.py; the page card is
check_ui_js.py. REQUIRED_HOLDS, which only it read, and the "the hold"
section header over it go.
- exthost.wizard, for host_ext.sh (05848ac): the check listed, run to its
prompt with no lease holder, answered yes, ended with its summary and
listed done; the setup record's wizards and the gate as they were; a
second run aborted; a check of a package that is not installed 404.
wizpkg_test holds each step to its form. suite/extwizard.py held only
it: the module and its import go.
- exthost.catalog: install_test.py and extpkg_test already assert its
behavioral claims, and the one fact the bench test did not prove, that
the image's extension key is the catalog's published key, is now a
forgetest-ci step: the catalog is checked out at main as a sibling and
meta-forgefirm/recipes-forgefirm/forgefirm-keys/files/ext/forgefirm-ext.pub
is cmp -s'd against its keys/forgefirm-ext.pub, either one missing a
failure. suite/extcat.py held only it: the module and its import go.
- camera.key-read, for forgectrl's tests/host_lan.sh (05848ac): with
panel_open_reads=0, GET /cam/status from the LAN client over HTTP and
HTTPS is 403 with no key or a wrong one and 200 with the key as ?key=
or the header, the key alone writes no setting, and a rotation stops
the old key and the new one reads. camera.py's json import, which only
it used, goes.
The coverage lint then named forgectrl src/wizpkg.c, src/wizpkg.h,
src/camkey.c and src/camkey.h, and nothing else. Two HOST_TESTED rows
take them, to forgectrl:tests/wizpkg_test.c and
forgectrl:tests/camkey_test.c on forgectrl:.github/workflows/build.yml,
which the pin c274f19 runs (./build/wizpkg_test, ./build/camkey_test).
src/extpkg.* stays covered (logs.tree-tail-export, logs.routing), and
forgeext/** by exthost.service.
The key step's run text, taken from the workflow, passes from the tree
root with both files present; controls on a /tmp copy, a published key
that differs and one that is missing: each fails. test_manifest.py (34)
passes. The coverage lint on the tree manifest finds no uncovered path
and no host-tested row problem across the 83 tests; its one finding is
setup.check-lens's empty src/lensfind.* entry. pyflakes reports nothing
on exthost.py, camera.py or manifest.py. The full forgetest suite was
not run.
Fingerprints, measured: 10 tests move, the 5 left in exthost.py
(armed-freeze, motion-jog, motion-job, platform, service) and the 5 left
in camera.py (frame-health, h264-stream, lid-privacy, sensor-profile,
snapshot). The pending pin bumps from forgectrl c274f19 and forgeext
2d0391e already owe 9 of them; exthost.platform is owed already since
|
||
|
|
30c0a05b86 |
Catalog: exthost.panel-install leaves
exthost.panel-install leaves for the host proofs that carry it; the catalog goes from 88 to 87. - The upload sink is forgectrl's tests/host_ext.sh (005b159): 403 without the token, 409 and not idle while cnc/state reads running with the host never run, the host's refusal at 400 in its words with the staging file gone, an archive the host takes staged byte for byte as tier unverified with consent button, the discard, an install with nothing staged at 409, and a key without the button at 409 with no key written. The consent each tier takes, the phrase, the grants' form, the button the install and a key are passed, and the key's name and the host's refusal of it are forgectrl's extpkg_test; the tiers themselves are forgeext's install_test.py. - Its section header, "installing through the panel", held only it, and EXT_STAGE and the contextlib import, which only it read, go with it. Its requires line goes with the function. Three stay. exthost.package-routes: extpkg_test has the status document, the holds, disable and enable, remove, a bad action and a bad id at 400 with nothing run, and the host's refusal at 409, but not the 403 without the login on GET /ext/status and POST /ext/package, which is main.c's guard and no case of extpkg_test's. exthost.wizard: a package's check takes no lease (wizdark.c), and the lease is held in memory, so there is no lease file to assert absent at the prompt. exthost.catalog: install_test.py and extpkg_test assert every listed claim, and what is left is the image's own key. The coverage lint names no uncovered path, so no HOST_TESTED row is added: forgectrl's src/auth.* stays covered by other catalog tests, and src/extpkg.* by the three that stay. test_setup_suite.py (31) and test_manifest.py (34) pass. The coverage lint on the tree manifest finds no uncovered path and no host-tested row problem across the 87 tests; its one finding is setup.check-lens's empty src/lensfind.* entry. pyflakes reports nothing on exthost.py. The full forgetest suite was not run. Fingerprints: exthost.py's shared text moves, so 8 tests move: the 6 left in exthost.py, and exthost.catalog and exthost.wizard in the modules that read it. The pending pin bumps from forgectrl c274f19 and forgeext 2d0391e already owe 7 of them; exthost.platform is the one this adds. releases/ and TEST-AUDIT.md are unchanged. |
||
|
|
cfa6557847 |
Catalog: the sender keep-out, the event stream and the page's call leave
Three catalog tests leave for the host proofs that carry them; the
catalog goes from 91 to 88. Each proof passed; none skipped.
- exthost.sender-keep-out, for forgeext's run_test.py (ebdbf59): a
package granted sender.keep_out posts /v0/sender {"out": true}, the
stand-in takes it only with the host's token, and the claim is kept
fresh beside the holds, raised in the host's words, until the package
is disabled. The machine's side is forgectrl's senderout_test.
suite/extsender.py held only it: the module and its import go.
- exthost.events, for forgeext's run_test.py (ebdbf59) and forgectrl's
host_lan.sh (b272b3a): the host holds one stream while a package wants
events and lets it go when none does, a poll at the head comes back
empty at its one-second deadline, a waiting poll is answered 0.10 s
after an event; and the host's slot is a loopback peer's alone, the
same header from a LAN address leaving the host's stream open.
forgeext's evfeed_test and forgectrl's events_test hold the two
daemons' own sides. Its section header, the operator's door, held
only it, and EVENTS_MAX_STREAMS, EVENTS_HOST_HEADER and _stream, which
only it read, go with it.
- exthost.page-call, for forgeext's sdk_test.py (ebdbf59): disabled, the
page package's socket name is gone and its call is refused in the
host's words; enabled, the same call answers 200. sdk_test.py already
held the calls, their form and refusals, and the frozen service;
forgectrl's extpkg_test holds the relay. suite/extcall.py held only
it: the module and its import go.
The coverage lint then named forgectrl src/senderout.c and
src/senderout.h, and nothing else; one HOST_TESTED row takes them, to
forgectrl:tests/senderout_test.c on forgectrl:.github/workflows/build.yml,
which the pin c274f19 runs. grblhal-glowforge's src/serial.* stays
covered by a catalog test and gets no row; holdkeep, evfeed, call and
sandbox stay under exthost.service's forgeext/**, and forgectrl's
src/events.* under forgectrl.lease.
test_setup_suite.py (31) and test_manifest.py (34) pass. The coverage
lint on the tree manifest finds no uncovered path and no host-tested row
problem across the 88 tests; its one finding is setup.check-lens's
empty src/lensfind.* entry. pyflakes reports nothing on exthost.py or
manifest.py. The full forgetest suite was not run.
Fingerprints: exthost.py's shared text moves, so 9 tests move: the 7
left in exthost.py, and exthost.catalog and exthost.wizard in the
modules that read it. The pending pin bump from c274f19 already owes 8
of them; exthost.platform is the one it adds, as before. releases/ and
TEST-AUDIT.md are unchanged.
|
||
|
|
b5f355cb61 |
Catalog: exthost.ui-delivery leaves
exthost.ui-delivery leaves for the host proofs that carry it; the catalog goes from 92 to 91. - forgeext ui, in forgeext's tests/install_test.py (23156a4): an installed package's page comes back byte for byte as the html string, with bytes its length; an id that is not installed and a package the operator disabled are each refused in words. - forgectrl's relay of the page and the settings, in tests/extpkg_test.c on forgectrl's build.yml: the page served, a disabled package's page 404, the settings read, a value over its bound 400. The install-time mismatch between the ui capability and a page stays with forgeext's own tests. It has no glob of its own, so no HOST_TESTED row. exthost.sender-keep-out stays: forgeext carries a package's claim on the Grbl sender to the machine with the token it reads only from /run/forgefirm/ext-host.token, so run_test.py does not prove it, and suite/extsender.py is unchanged. test_setup_suite.py passes (31 tests). The coverage lint on the tree manifest finds no uncovered path and no host-tested row problem across the 91 tests; its one finding is setup.check-lens's empty src/lensfind.* entry. pyflakes reports nothing new in exthost.py. The full forgetest suite was not run. Fingerprints: exthost.py's shared text moves, so 12 tests move: the 8 left in exthost.py, and exthost.page-call, exthost.catalog, exthost.wizard and exthost.sender-keep-out in the modules that read it. The pending pin bump from c274f19 already owes 11 of them; exthost.platform is the one it adds, as before. releases/ and TEST-AUDIT.md are unchanged. |
||
|
|
b447ae77f1 |
Catalog: the LAN redirect, the release check, and the update job's lease leave
Three catalog tests leave for the host proofs that carry them; the catalog goes from 95 to 92. - setup.https-only-writes, for forgectrl's tests/host_lan.sh (13273a0): from a LAN peer in a network namespace, reads served over HTTPS and HTTP, three writes over HTTP each a 302 to the same path on HTTPS that acts on nothing, the same write served from loopback, and the token writing over HTTPS with no account. - update.release-check and update.job-locks, together, for forgectrl's tests/host_update.sh (acc3dcf), against a release server of its own: GET /update/release's keys, POST /update/check with a v<semver> tag, a size above 0, notes and new; the dismissal refused for a version that is not a tag, taken for the tag, cleared by an empty one; and a probe download that holds the machine (a settings write and a G21 job each 409 in the job's name, update:download of kind system the holder), ends refused by the signature check leaving no file, and lets the same settings write through after. update.release-check was the last test in suite/update.py; _VERSION_RX and the re import, which only it used, go with it. update.job-locks was the only test in suite/updlock.py: the module and its import in suite/__init__.py go. setup.https-only-writes was the only test of setup.py's "HTTPS boundary" section. tests/test_setup_suite.py drops its id. update.product-gate stays: fwup is on neither this host's PATH nor CI's, so host_update.sh does not prove the five uploads, and no HOST_TESTED row is added for src/fwproduct.*. No other HOST_TESTED row either. test_setup_suite.py passes (31 tests). The coverage lint on the tree manifest finds no uncovered path and no host-tested row problem across the 92 tests; its one finding is setup.check-lens's empty src/lensfind.* entry. pyflakes reports only the unused local plain in update.slots-and-signature, which HEAD already has. The full forgetest suite was not run. Fingerprints: setup.py's and update.py's shared text move, so 33 tests move: the 6 left in setup.py, the 25 in the modules that read it, and update.slots-and-signature and update.product-gate. The pending pin bump from c274f19 already owes 31 of them; setup.machine-name and exthost.platform are the two it adds, as before. releases/ and TEST-AUDIT.md are unchanged. |
||
|
|
21046cd6ff |
Catalog: setup.cert-page leaves
setup.cert-page leaves for forgectrl's tests/host_guard.sh (4d2d109): GET /settings reports a 95-character tls_fingerprint, and over HTTP and over HTTPS, with no token, GET /cert is 200 with no redirect, that fingerprint and the host's name, and GET /cert.pem is a PEM certificate. The catalog test reached the board by its LAN address; the page is what it proved. The catalog goes from 96 to 95. It called no helper of its own. src/tls.* stays on the login, HTTPS, first-run and TLS-record tests, src/ui/wizard.html on image.license-bundle and setup.mirror, and src/ui/wizard.js on forgectrl.auth, exthost.wizard and setup.mirror. No unit test names it. No HOST_TESTED row: the pinned workflow does not run host_guard.sh. test_setup_suite.py passes (31 tests). The coverage lint on the tree manifest finds no uncovered path and no host-tested row problem across the 95 tests; its one finding is setup.check-lens's empty src/lensfind.* entry. pyflakes is clean on setup.py. The full forgetest suite was not run. Fingerprints: the deletion moves suite/setup.py's shared text, so 32 tests move: the 7 left in setup.py and the 25 in the modules that read it. The pending pin bump from c274f19 already owes 30 of them; setup.machine-name and exthost.platform are the two it adds, as before. releases/ is unchanged. |
||
|
|
098cfdf193 |
Catalog: setup.record-export leaves
setup.record-export leaves for forgectrl's tests/host_guard.sh (84779c8 on d21e200): the record as JSON with the token (schema 1, the sheet id GET /wiz reports, no password hash, a plain read not an attachment), the download an attachment of the record named with that id, the printable page text/html with no script, the sheet id, "setup record" and an <h3> per completed wizard, both routes 403 without a token or a login, and the sanitized export carrying system/setup.json with the same id and wizards, no $6$, and no panel token anywhere in the archive. The catalog goes from 97 to 96. One HOST_TESTED row: forgectrl src/recordhtml.*, the one path no other catalog test covers, to tests/recordhtml_test.c (no script, the sheet id, the step headings, the escaping), which forgectrl's build.yml runs at the pinned revision. src/setup.* and src/wiz.* stay on the setup family, src/logs.* on exthost.service, forgectrl.lease and the two log tests, and src/main.c, src/ui/index.html, src/ui/panel.js and src/ui/wizard.html on the tests that already name them. tests/host_guard.sh is not named: the pinned workflow does not run it. tests/test_setup_suite.py drops the id, the record-export hardware assertion, and the export lines of the lifecycle covers test (the covers set and its two assertions); the mirror's assertions stay. test_setup_suite.py passes (31 tests). The coverage lint on the tree manifest finds no uncovered path and no host-tested row problem across the 96 tests; its one finding is setup.check-lens's empty src/lensfind.* entry. pyflakes is clean on setup.py and test_setup_suite.py. The full forgetest suite was not run. Fingerprints: the deletion moves suite/setup.py's shared text, so 33 tests move: the 8 left in setup.py and the 25 in the modules that read it (setup_dark, setup_burns, bedsize, exthost, extcall, extcat, extsender, extwizard). The pending pin bump from c274f19 already owes 31 of them; setup.machine-name and exthost.platform are the two it adds, as before. releases/ is unchanged. |
||
|
|
1e28eb92cd |
Catalog: setup.factory-return leaves
setup.factory-return leaves for forgectrl's tests/host_guard.sh (d21e200): POST /restore/factory-return with the token and no confirm is 400 and names confirm, confirm=0 is 400, GET /update/status shows no job running, GET /slots lists the archives (run only where /usr/sbin/ffboot is not on the host), and GET /setup carries the confirmed call; confirm=1 is never sent. The catalog goes from 98 to 97. It was the only test in the "the operator" section of suite/setup.py, and called no helper of its own. tests/test_setup_suite.py drops its id and test_the_factory_return_never_runs_the_return. src/update.* stays covered by the update tests, src/ui/wizard.js by the wizard and mirror tests, and src/main.c by the tests that already name it. test_setup_suite.py passes (31 tests). The coverage lint on the tree manifest finds no uncovered path and no host-tested row problem across the 97 tests; its one finding is setup.check-lens's empty src/lensfind.* entry. pyflakes is clean on both files. The full forgetest suite was not run. No HOST_TESTED row: the pinned workflow does not run host_guard.sh. Fingerprints: the deletion changes suite/setup.py's shared text, so the 34 tests that read it move again: the 9 left in setup.py, setup_dark 9, setup_burns 2, bedsize 1, exthost 9, extcall, extcat, extsender and extwizard 1 each. The pending pin bump from c274f19 already owes 32 of them; setup.machine-name and exthost.platform are the two it adds, as before. releases/ is unchanged. |
||
|
|
33e8c6e43f |
Catalog: logs.level-settings leaves
logs.level-settings leaves for forgectrl's tests/logs_test.c (9317b54): the six levels taken and verbose and 7 refused, port 70000 refused, udp and tcp taken and tls refused, a server name with spaces refused, and a remote level changed since boot listed as configured against effective with pending_reboot raised, and lowered once they match. The settings route's refusal of a value its validator rejects, with the file left byte for byte, is tests/host_routes.sh's bounds case. The catalog goes from 99 to 98. It was the last test in suite/logs.py and called no helper of its own, so nothing else goes. logs.tree-tail-export and logs.routing stay: they are the only catalog covers of forgectrl src/fflog.*, and the pinned workflow runs neither fflog_e2e.sh nor the new log tests. setup.record-export keeps its requirement on logs.tree-tail-export. The coverage lint on the tree manifest finds no uncovered path and no host-tested row problem across the 98 tests; its one finding is setup.check-lens's empty src/lensfind.* entry, which the forgectrl pin bump clears. pyflakes is clean on logs.py. The full forgetest suite was not run. Fingerprints: the deletion changes suite/logs.py's shared text (the two separator lines), so logs.tree-tail-export and logs.routing move; no other module imports it. The pending forgectrl pin bump from c274f19 already owes both, so this commit adds none to it. releases/ is unchanged. |
||
|
|
71556b2480 |
Catalog: the setup gate, the advisories, the extensions' consent and cloud-off leave
Four catalog tests leave for the host proofs that carry them; the catalog goes from 103 to 99. - setup.gate-blocks-controllers, setup.advisories-rehash, setup.extensions-consent and setup.cloud-disabled-surface. forgectrl's tests/host_setup.sh (91d30f1) runs the four HTTP proofs against the built daemon with the controller up: each reason the gate holds on and the live fan-count close and reopen; the first-run advisories, their hashes, and an acceptance that takes the press back; the extensions' advisory, its five refusals, and the consent; and every cloud selection refused while cloud mode is off, POST /mode included. builtin_test.c holds the cloud sweep, sha256_test.c the digest, and settings_dir_test.c the search bit the consent adds to the data directory. - gate_is_open and _GATE_COVERS go with the gate test, ADVISORY_DOCS and the hashlib import with the rehash; nothing else calls them. The record builders, REQUIRED_STEPS, SAFETY_PHRASE, Restore and request stay. - The requires that named them: _FIRST_RUN_REQUIRES held only the two deleted ids and goes, so setup.first-run-flow requires nothing from it and setup.first-run-page requires setup.first-run-flow alone; exthost.service keeps exthost.platform. - Coverage: HOST_TESTED rows for forgectrl src/sha256.* (tests/sha256_test.c) and src/builtin.* (tests/builtin_test.c), both run by forgectrl's build.yml at the pinned revision. docs/advisories/** stays behavioral: setup.first-run-flow, which accepts them, covers safety-and-risk.md, licenses.md, privacy.md and cloud-service.md, and exthost.service, which turns extensions on over it, covers extensions.md. - tests/test_setup_suite.py drops the four ids from the id list and the takeover list, the gate test's precheck assertion (the method now names the intake prechecks only), and CloudDisabledSurfaceTests, with the imports only that class used; the docstring no longer describes it. test_setup_suite.py passes (32 tests). The coverage lint on the tree manifest finds no uncovered path and no host-tested row problem across the 99 tests; its one finding is setup.check-lens's empty src/lensfind.* entry, which the forgectrl pin bump clears. pyflakes is clean on the four files. The full forgetest suite was not run. Fingerprints: deleting the four functions changes suite/setup.py's shared text, so 35 of the 99 tests' implementation hashes move: every remaining test in setup.py (10), and the modules that import it at module level, setup_dark (9), setup_burns (2), bedsize (1), exthost (9), extcall (1), extsender (1), extwizard (1), and extcat (1, through its import of exthost). The next forgectrl pin bump already owes 33 of them; exthost.platform and setup.machine-name are the two it adds. releases/ and TEST-AUDIT.md are unchanged. |
||
|
|
7172bba92b |
Catalog: the report channel, the event stream and what-changed leave
Three catalog tests leave for the host proofs that now carry them; the catalog goes from 106 to 103. - cooling.report-channel. forgectrl's tests/host_routes.sh (e69d967) runs the channel against a live daemon: a run report with the controller's secret writes exhaust 65535; no secret, a wrong secret and a short secret are 403 and the duty file is unchanged; an accepted idle while the ring still reads running writes 32768; the secret dies with the controller. cool_report_test.py and super_test case L own the secret, and cooling.fans-quiet-after-motion owns the live M8/M9 return to the idle duty. The forged Host: localhost was the same secret refusal. - events.stream. events_test.c owns the hello, the replacement, and motors.released; host_routes.sh owns the live cap (three peers held, a fourth 503, GET /settings 200 beside them, closing one admitting the next). forgectrl.lease and exthost.events still cover src/events.*, and _EventStream stays for forgectrl.lease. - setup.what-changed. setup_test holds tray -> laser.focus and fan -> airflow under the image's table beside tube -> laser.floor; host_routes.sh holds /wiz/changed (403, 400) and that every step the menu names is one GET /wiz lists. src/setup.* and src/wiz.* stay covered by the other setup tests, and help.js, panel.js and index.html by camera.key-read. preserved() and flag_of() go with it; recommended() stays. tests/test_setup_suite.py drops setup.what-changed from the id list, the takeover list and the covers lookup, and its flag_of case keeps only the recommended() assertion. test_setup_suite.py passes (36 tests). The coverage lint on the tree manifest finds no uncovered path across the 103 tests; its one finding is setup.check-lens's empty src/lensfind.* entry, which the forgectrl pin bump clears. pyflakes on the four files reports only the unused t0 in cooling.py that HEAD already has. The full forgetest suite was not run. Fingerprints: the cuts change the shared text of suite/cooling.py, suite/forgectrl.py and suite/setup.py, so 56 of the 103 tests' implementation hashes move (cooling 12, setup 26, forgectrl 5, and exthost 13 through its import of suite/forgectrl.py). The next forgectrl pin bump already owes 54 of them; exthost.platform and setup.machine-name are the two it adds. releases/ is unchanged. |
||
|
|
8e1fe5090d |
Harnesses: the null-sink cases other tests prove leave
The grblHAL harnesses keep what only a running controller shows; the cases a host unit test in grblHAL-glowforge proves, and the log and reply wording, leave. - laser_lifecycle_test: test_arm_waits_for_the_fans goes. laser_arm_test case A4 (grblHAL-glowforge) proves the fan grace stretching the arm's acknowledgment budget, on the same arm path. The docstring names it. - xy_mode_test: case_x32, case_invalid and the log checks go; the default, x16 and the low tick stay. xy_scale_test holds each mode's tick and the not-a-mode parse. - planner_blocks_test: one restart, at $398 1000; the 400 pass and the fresh-store probe go. - ctlport_test: test_poll_lf and test_crlf go (the CR LF poll stays); in test_sender_out the network turn-away goes (serial_test holds it), and the busy gating, the grant and the outlives-client part stay. test_port_dark is whole. - mcode_test: the timeout case runs on the host build's 5 s GFMCODE_WAIT_S (grblHAL-glowforge's CI configures it; the image keeps 30 s) and still shows the hold, at 5.1 s; 5 s outlasts the 3 s test_dark watches the wait; test_table refuses one form per parser branch. test_timeout_holds and test_dark stay. - envelope_test: test_unset and test_bounds go (lens_home_test holds the clamp); test_armed and the open, apply and close cases stay. - manual_home_test: the out-of-range offset block goes, a repeat of gfhome_clamp_home_mm (lens_home_test); the modal-M3 FIRE check and every release and energize case stay. - cool_report_test: one non-hex secret and one CR LF injection stay; runner-malformed and the exact header-set checks go. A bad secret is never sent and a good one is. - tray_test: test_offset keeps 20 mm; test_errors keeps a bad P and a bad axis; the frame shift, envelope, barrier, persistence, port op, unreferenced lens and camera home stay. Proof: with grblHAL-glowforge's matching commit, the eleven harnesses its CI runs pass under Linux against the null-sink build: nine on a build with a 1 s M-code wait, and mcode_test and tray_test, the two that wait at an M-code, on the 5 s build CI configures (-DGFMCODE_WAIT_S=5.0). pyflakes reports nothing in the nine changed files. Push this before grblHAL-glowforge's commit. Acceptance: none; host harnesses only. |
||
|
|
520d62ac05 |
forgetest tests: the confirmed trims of the tool's own unit tests
The cases that tested a mock, a timer, a wording or a table, or that
replayed a judgment a host test already holds, leave forgetest/tests.
What catches a false PASS, keeps the bench safe, or guards the gate
stays.
- test_baseline: the fake sysfs tree is a mixin with no test_ methods,
so ExtensionFreeTests and BaselineModeTests no longer re-run
BaselineTests' 25 cases (133 runs, now 45). CoolPublishTests, the
lamp-fade cases and TransientNotLeftoverTests go; PositionDeadbandTests
keeps a reading past the band and an unreadable reading, both never
excused. Both latch-relock cases stay.
- test_fixture: MdnsTests, the three timing cases, the probe's log
wording, and nine prompt-routing variants go. A live test never runs
unattended, the arm-press opt-in, the short-press edge, the presence
press handing over the arm press, and the arm notice dropping at the
press stay (35 cases, now 21).
- test_cloud_suite: three false-PASS guards stay: the latch open at a
dark print's button, a lid stop that is not edge-driven, the kernel
refusing the resume. The guards for the latch unlocking in the hold
and a paused print resuming left with their catalog tests in
|
||
|
|
8536ae7076 |
laser_stream_test: the analog copies, the in-run levels and the curve run leave
The analog dose model runs on no machine; the density sessions beside these prove the same rules on the shipped model. - Sessions J and K (M5 at idle, the next job's first cut) run under density only. - Session C (churn, analog) goes; its pad budget (no runaway pad across cycle churn) moves onto session G, density-churn, which already held FIRE, termination and the gaps on the same job. - Session H (a level change inside a run costs no byte) goes: every density power byte is already held at full duty by check_density; the byte count was a cost, not an energy rule. - Rule 19's end-to-end curve run goes: laser_arm_test.c case P pins the curve's mapping. The rolloff sessions keep the curve in force. - The constants only those sessions read go (CURVE_DEFAULT, curve_density_for, CURVE_S, JOB_CURVE, JOB_LEVELS); the header's rules 11, 16 and 19 say what the harness now holds. Proof: laser_stream_test passes whole on a null-sink build of the grblHAL-glowforge working tree under Linux (23 sessions). pyflakes is clean. Acceptance: none. The harness runs in grblHAL-glowforge's CI. |
||
|
|
09cf9242a9 |
laser_lifecycle_test: the sessions the unit tests and the stream prove leave
Each case below repeated a rule another test in CI already proves, read assertion by assertion next to it in the 2026-10-13 test audit: - test_job_window: laser_arm_test.c case I (S changes, and now M5 then M3, inside the window never re-prompt; df7087b) and laser_stream_test session K (M2 closes the window, the next job re-arms). - test_hold_grace: test_resume_after_grace makes the same assertion. - test_verdict_blocks_arm, test_arm_waits_for_engine_ack, test_arm_proceeds_on_a_late_ack: laser_arm_test.c cases C, A2, A3 and cooling_test.c's arm-time gate. test_arm_waits_for_the_fans stays. - test_sender_change: laser_arm_test.c case H; the disarm on a client change is unconditional. - test_jog_does_not_extend_window: laser_arm_test.c case S10. - test_verdict_fail_tier_ends_job: laser_stream_test rule 25 and laser_arm_test.c case V2. - test_verdict_pause_resumes_without_press: laser_stream_test rule 24, which now counts one arm across the pause, and case V1. - test_sender_change_mid_job: merged into test_sender_change_holds_and_rearms, which takes its "held inside the move" position check. - test_sender_change_then_reset: the window is closed and the latch locked by the sender change itself (case H); a reset from Hold is not energy. The header's rules 1, 2, 5, 11, 12 and 14 keep their numbers and name the tests that now prove them. Proof: laser_lifecycle_test passes whole on a null-sink build of the grblHAL-glowforge working tree under Linux (17 sessions), and laser_stream_test with it. pyflakes is clean; no helper was left without a caller. Acceptance: none. The harness runs in grblHAL-glowforge's CI. |
||
|
|
280ceee849 |
laser_stream_test: rule 24's pause holds the window open: one arm
Rule 24 proved the pause tier holds lit to the stop, masks FIRE while held, never writes the latch and resumes lit on the clean verdict. What only laser_lifecycle_test's verdict-pause session held was that the window stayed open through it: the resume armed nothing new. Rule 24 now counts the arms, so that session can leave. - verdict-rehold: exactly one "laser armed" across the pause, the re-hold and the resume. Proof: laser_stream_test passes whole on a null-sink build of the grblHAL-glowforge working tree under Linux. pyflakes is clean. Acceptance: none. The harness runs in grblHAL-glowforge's CI. |
||
|
|
8149a44f93 |
laser_stream_test: session B's FIRE-gap check moves to its density twin
Session B ran JOB_M3_TERM under the analog model; session F runs the same job under density and checked its span, density and termination, but not check_fire_gaps (no stepless run carrying FIRE), which B alone held. F now runs it, and B goes: analog never runs on a machine, and the analog ladder keeps its own termination and gap checks. - check_fire_gaps on density-full, its worst gap in the PASS line. - Session B's run and checks go; the JOB_M3_TERM comment names F. Proof: the harness passes whole on a null-sink build of the grblHAL-glowforge working tree under Linux, density-full with a worst fire gap of 270 ticks. pyflakes is clean. Acceptance: none. The harness runs in grblHAL-glowforge's CI. |
||
|
|
c91f6daa9e |
laser_stream_test: rule 14 runs under the density model
Rule 14 (a level set while the stream is idle still cuts at that level) ran only under the analog model, which no machine runs: its check counted fire ticks per analog duty. Under the shipped density model the level is the FIRE density at full duty, and nothing proved a stale level there. - Session I runs JOB_IDLE_S under DENSITY_CONF_FLOORED and judges it with rule 18's check_density: every power byte at full duty, and each move's span at its own S's density (23/127, 46/127, 81/127), not the one before it. The stream still ends dark. - The header's rule 14 says the level is judged as the FIRE density. Proof: the harness passes whole on a null-sink build of the grblHAL-glowforge working tree under Linux (densities 0.1812, 0.3624, 0.638 against 0.1811, 0.3622, 0.6378). A control judging the same stream against the levels in reverse order fails. pyflakes is clean. Acceptance: none. The harness runs in grblHAL-glowforge's CI. |
||
|
|
f8dc21b309 |
forgetest: motion.button-hold-resume leaves the catalog
motion.button-hold-resume held on the machine that the button holds a travel job and the next press resumes it to its target. The host proves it now: laser_lifecycle_test rule 7 runs the same job (M5, G91, G1 X40 F300) through the toggle and lands it within 0.05 mm of +40 ( |
||
|
|
a0a4edb322 |
laser_lifecycle_test: a travel job takes the button toggle and lands on its target
Rule 7 proved the button's pause/resume toggle on an armed job only, and nothing checked that the move resumed from the hold reaches its target. The catalog's motion.button-hold-resume held both on the machine with a travel job; it can leave the catalog once the host does. - test_button_pause_resume, after its armed half, runs the catalog's job: M5, G91, G1 X40 F300 (8 s). It must reach Run without asking for the arm press; a press holds it part-way; a second press resumes it to Idle within 0.05 mm of +40. - Rule 7's line in the header and the case's docstring say so. Proof: the case passes on a null-sink build of the grblHAL-glowforge working tree under Linux (the armed half as before; the travel job held at 8.747 mm and resumed to 39.999 mm). A control, the same job held and soft-reset instead of resumed, ends at 8.747 mm, which the landing check fails. pyflakes is clean. Acceptance: none. The harness runs in grblHAL-glowforge's CI. |
||
|
|
67ac8f4064 |
forgetest: events.button-telemetry leaves the catalog
events.button-telemetry held on the machine that one physical press reaches the event stream as pressed then released through its 25 Hz switch poll, and the telemetry event's fields. The host proves the rules now: - the press between two 5 Hz gathers, idle snapshot, scripted switch word: forgectrl events_test test_short_press (d8d07fc); - the telemetry field string, and that a press the machine is waiting for (arming, armed, leased, a button wait) is no event: events_test test_button. The physical button reaching the switch word stays in the catalog: setup.check-switches. - evmore.py, the test's own module, goes with its import in suite/__init__.py; nothing else imported it. - Its covers (forgectrl src/events.*, main.c, status.*) stay covered: events.stream holds all three, and other catalog tests hold main.c and status.*. No HOST_TESTED row is needed. Proof: the coverage lint reports 0 uncovered paths and 0 host-tested row problems over 107 tests (its one failure is setup.check-lens's src/lensfind.* entry, which the forgectrl pin bump clears); the gate self-check refuses an empty artifact cleanly. The unit modules that load the whole catalog pass under Linux: test_artifact, test_campaign, test_cooling_order, test_fixture, test_manifest, test_operator. Acceptance: the catalog hash moves; no remaining test's fingerprint moves (the module goes whole). |
||
|
|
0350b943d7 |
forgetest: laser.py's unused motion imports, and the two helpers no test calls
laser.py imported wait_state, wait_state_text, wait_left_state and
drain_text from motion.py and used none of them. wait_state and
drain_text stay in motion.py, which uses them; wait_state_text and
wait_left_state had no caller anywhere, so they go.
Proof: pyflakes no longer reports laser.py's four unused imports and
adds no finding. The coverage lint reports 0 uncovered paths and 0
host-tested row problems over 108 tests (its one failure is
setup.check-lens's src/lensfind.* entry, which the forgectrl pin bump
clears); the gate self-check refuses an empty artifact cleanly; the
forgetest host suite passes under Linux (539 tests).
Acceptance: the shared text of laser.py and motion.py moves, and with
motion.py's the tests of every module that imports it (motion, laser,
tray, updlock, and bedsize through homeoff). Against the image on the
bench that adds no run:
|
||
|
|
35add7cad0 |
forgetest: laser.power-floor leaves the catalog; laser.py's dead names go
laser.power-floor read $$ off the controller and checked $35 is the floor, that it lands at PWMSAR 12 or above, and that $31 is 0. The host proves all of it now: laser_stream_test rule 18 reads $$ on a fresh controller and after the arm ( |
||
|
|
d7379d9fc0 |
laser_stream_test: rule 18 reads $31 and the floor off the controller
Rule 18 proved a typed $35 is overwritten at the arm and the ladder
renders through the floor key, but took the shipped settings on faith:
the harness mirrors $31 = 0 and the board's 10 % floor as constants. The
catalog's laser.power-floor read them off the machine; it can leave the
catalog once the host does.
- The floor-derived session reads $$ on the fresh controller (no floor
key: the shipped default) and again after the arm overwrote the typed
$35. Both reads must show $31 = 0 (the floor, not $31, sets the bottom
of the S range) and $35 = the board's floor, and that floor must land
at PWMSAR_FLOOR_MIN (12) or above.
- A set floor key is laser_arm_test.c case J's (12.5 honored, an
out-of-range key falling back to 10, 0 honored). forgectrl keeps no
default for laser_floor_density: an absent key is the controller's
own floor, so there is no second default to compare.
Proof: laser_stream_test passes whole on a null-sink build of the
grblHAL-glowforge working tree under Linux ("$31=0 and $35=10 (PWMSAR
12) fresh and after the arm"). A control session with a floor key of 5
reads back $35 = 5 both times through the same parse, which rule 18
would fail. pyflakes is clean.
Acceptance: none. The harness runs in grblHAL-glowforge's CI.
|
||
|
|
21f53e4e83 |
forgetest: laser.verdict-cut leaves the catalog; the stream rules hold its claim
laser.verdict-cut held, on the machine, that a stale cooling verdict
mid-cut holds the job dark under the open window with the latch
untouched, and that the clean verdict resumes it lit with no press.
Every part of that is a rule the host now proves:
- dark while held: laser_stream_test rules 24 and 27, check_hold_dark on
every verdict hold's stationary stretch, and check_fire_gaps (
|
||
|
|
ef15c55577 |
laser_stream_test: the verdict's holds ship no FIRE while held
Rules 24 (the pause tier, held and held again) and 27 (a stale verdict) asserted the deceleration into each hold ran lit to the stop, the stretch between the holds dark, and the cut lit after the clear, but not that the stream is dark inside a hold: the central claim of the catalog's laser.verdict-cut, which can leave the catalog once the host proves it. Rule 21 proved it only for a plain feed hold. - check_hold_dark: no FIRE tick inside a hold's stationary stretch, its edges aside (one step pulse straddles each edge: rule 21's margin, HOLD_EDGE_TICKS, 120 ticks at x8 scaled to the mode: 480 at the x32 default). - Rule 24 checks both of its holds; rule 27 checks its one hold. - check_fire_gaps runs on both sessions, as on rule 21's. - The header's rules 24 and 27 say so; their PASS lines report the held stretches. Proof: laser_stream_test passes whole on a null-sink build of the grblHAL-glowforge working tree under Linux: verdict-rehold dark while held for 22620 and 22622 ticks, verdict-stale for 22621, check_fire_gaps clean on both. A negative control on check_hold_dark: a dark 5000-tick hold passes, and one FIRE tick inside it fails. pyflakes is clean. Acceptance: none. The harness runs in grblHAL-glowforge's CI. |
||
|
|
571f702113 |
forgetest: the emission witness samples a rapid after its M5; m5-rapid-dark leaves
The rule that a rapid after an M5 ships no FIRE is the host's (laser stream rule 16). What only the machine shows is LASER_ON staying off with FIRE clear while the chain can fire; kernel.fire-line's dark sample runs with the chain unarmed. laser.m5-rapid-dark held that sample in a test of its own, with its own arm and press. The emission witness, which every campaign runs, now holds it. - The square ends at M5 with no program end, so the window stays open. - The dark point: the LASER_ON count reads 0 after the M5, the window still armed, and the SoC lock line released (laser_locked false: the engine's armed flag can read true after the latch has locked, and a dark reading with the latch locked is the unarmed case). All three are checked before anything else is sent. - Only then is the rapid sent: G0 X20, a 2.5 s dwell, G0 X-20, a 0.5 s dwell, the program end. Sending it after the dark point keeps the mid-square dwell's own zero from being taken for the dark point. - The test samples until the window closes (a dwell reports Idle, so Idle cannot end it), with the controller's machine X beside each sample. - Every sample from the dark point until the window closes reads no emission, and the HV current stays at its idle reading (HV_DARK_MAX); the reports show the head out at +20 mm and back at the start inside that stretch, and the latch reads released on the sample at +20 mm. - The square still has to show emission and an HV rise, judged on the samples before the M5; the beam detector is judged there too. - The job-based disarm is timed from the head being back: under 10 s, where the idle grace is 60 s. - The rapid's list and its sampler live inside the test, and the deletion takes only m5-rapid-dark's own lines (decorator through body), so laser.py's shared text is byte-for-byte unchanged. - live_fire_drills.py's m5dark note and the bench README say the emission witness now carries the drill's check. Proof: the fingerprint parts of laser.py before and after: the shared text unchanged, every other test's own text unchanged. In the controller, glowforge_laser.c keeps the latch unlocked from the job's first laser-on through M5/M3 toggles and relocks it at program end or after the laser_disarm_s spindle-off grace (60 s by default), so the dark point and the rapid fall inside the window. The coverage lint reports 0 uncovered paths over 110 tests (its one failure is setup.check-lens's src/lensfind.* entry, which the forgectrl pin bump clears); pyflakes adds no finding; the forgetest host suite passes under Linux. Acceptance: only laser.emission-witness moves, and as an always-required test it must pass on the bench in the next campaign before a release can inherit its PASS. |
||
|
|
6a434bbf77 |
forgetest: nineteen tests a host test already proves leave the catalog
The catalog is for what needs the machine or an operator. Each test below asserted software rules that a host test in CI already proves, read assertion by assertion next to that host test before it left; the record of that reading is kept with the audit. Safety rules (each approved for deletion by the owner on that reading): - laser.disarm-in-hold: laser_lifecycle_test test_resume_after_grace. - laser.arm-wait-lid: laser_arm_test E-G, laser_lifecycle_test test_lid_open_in_wait. - laser.port-dark: ctlport_test test_port_dark (port jogs, a long jog and its cancel, four injected words, the panel ops: no FIRE tick under an open window with M3 modal). - motion.respawn-gate: super_test A and D. - motion.interlock-cancel-home: the lifecycle loop cases, laser_arm_test G. - motion.lid-policy-hold: laser_lifecycle_test test_lid_policy_hold. - cloud.lid-during-button-wait, cloud.dark-print, cloud.verdict-refuse, cloud.paused-lid-cancel: test_machine_lid_button.py. - cooling.fan-duty-readback: cool_fanwrite_test. - cooling.intake-one-fan: cool_intake_test A-E. - exthost.hold-pause-tier: holdkeep_test, run_test.py, cool_flow_test P, holds_test. Other rules: - homing.cloud-offsets: manual_home_test [own-offsets], test_gfhome_motion; the real session stays in cloud.mode-switch. - exthost.core-range: version_test, install_test. - exthost.operator-destinations: run_test.py, netrules_test, install_test, extpkg_test. - exthost.lifecycle: run_test.py's ext.shutdown read before the stop. - exthost.mcode: the null-sink mcode_test, forgectrl mcode_test, sdk_test, install_test. - motion.soft-limits: z_envelope_test's homed cases, which now assert no step before the alarm, and envelope_test. - cooling_intake.py, extcore.py, extdest.py, extlife.py and extmcode.py go whole; homeoff.py keeps only the camera-home helpers that cloud.mode-switch, setup.check-envelope and their meta-tests use. - The helpers only the removed tests used go with them. - exthost.armed-freeze still runs the cloud suite's dark-print helper, whose latch checks still run; its description says so. It now requires cooling.floor-and-warm-up, the dark print's own prerequisite, in place of the removed cloud.dark-print. - HOST_TESTED: forgectrl src/holds.* (holds_test) and src/mcode.* (mcode_test), whose only catalog home left with these tests. - The meta-tests that replayed the removed tests go: test_cooling_intake_suite.py, the cloud replay cases and their fixture. The one that catches a false PASS stays and runs the dark print as armed-freeze does: a latch open at the button fails. - Text that named a removed test (exthost.motion-job's description and comment, test_baseline's docstring) no longer does. Proof: the coverage lint on a manifest from the recipe pins reports 0 uncovered paths and 0 host-tested row problems over 111 tests (its one failure is setup.check-lens's src/lensfind.* entry, which the forgectrl pin bump clears); pyflakes adds no finding; the gate self-check refuses an empty artifact cleanly; the forgetest host suite passes under Linux (547 tests); the catalog loads with 111 tests after armed-freeze's description edit. Acceptance: the catalog hash moves, so the next image opens a new campaign. Every remaining test in laser.py, motion.py, cloud.py, cooling.py, exthost.py, homeoff.py and bedsize.py moves its fingerprint (their modules' shared text changed); exthost.armed-freeze moves by its requires. |
||
|
|
bef1bf9902 |
z_envelope_test: a move refused past the bed ships no step
The homed cases asserted that a move past X max, Y max or X min draws ALARM:2, and the harness's own description said "before any motion", but nothing checked the motion. The catalog's motion.soft-limits held that half on the machine, by the kernel counters; it leaves the catalog with this harness as its proof. - Every session mirrors the shipped stream (GFSINK_DUMP) into its work directory; steps() counts the tick bytes with an X, Y or Z step bit, as ctlport_test.py does. - Each refused move is judged by the step count across it: unchanged, or the case fails with the ticks that shipped. - The move inside the bed must ship steps: the control that shows the count sees motion at all. Proof: z_envelope_test passes on a null-sink build of the grblHAL-glowforge working tree under Linux, every case including the three refusals before any motion and the control. Acceptance: none. The harness runs in grblHAL-glowforge's CI. |
||
|
|
7246707df5 |
forgetest: the gate test leaves the step lists to forgectrl's setup_test
setup.gate-blocks-controllers asserted on the machine which steps the image's table requires (GET /wiz's missing list) and recommends (each not run yet, never the gate's parts or the optional steps). Both are the setup table's decisions over a record, software with no hardware in them, and forgectrl's setup_test now proves both on the image's own table: the required steps in order (10b), the missing list emptying when they are done (4b), and the recommended steps in order, each not run yet (10b). The catalog keeps what needs the machine: the supervisor holding and spawning the controller, the process, and the Grbl port. - The two missing-list checks and the recommended-list check leave the test's body; its description says where they are proven. - RECOMMENDED_STEPS and NEVER_RECOMMENDED go with them (no test read them any more), and the meta-test case that only compared them. Proof: the forgetest host suite passes under Linux (test_setup_suite with the case removed among it); the coverage lint is unchanged. Acceptance: every setup.py test (the constants were the module's shared text), setup.gate-blocks-controllers among them. |
||
|
|
ed1322518b |
forgetest: the lint takes python -m unittest given a test's file or module
forgectrl's CI runs its dev-server mock test as `python3 -B -m unittest -v tests/test_devserver_mock.py`, and the workflow judge counted python -m unittest only as `unittest discover`: a HOST_TESTED row for that test was refused as not run. _unittest_names: an argument to python -m unittest (not discover) runs the test when it is the test's file (resolved against the step's working-directory) or a dotted name whose module is that file (tests.test_x, tests.test_x.Case.test_one); a -k pattern's value and an argument with a shell variable are not names. Proof: host tests: the CI's own command line, a module, a module with a class and a method; not a module beside it, nor a -k pattern naming it. The manifest tests (34) pass, and the forgetest host suite passes under Linux. Against forgectrl's pinned workflow a row for tests/test_devserver_mock.py is accepted; every earlier verdict on the real workflows is unchanged. Acceptance: none. forgetest is dev-only and in no fingerprint; the lint runs in CI, not in a campaign. |
||
|
|
8003d6f18c |
forgetest: the burns cover only what the burns run
BURN_COVERS named src/accel.* and src/wizcalc.*, carried over from the sheet test the burns replaced. The burns' path is wizlive.c, then wiz_lens_rehome() and lenshome_run(); neither file is on it, and the focus burn holds the lens window as found rather than measuring stops. An accelerometer or dark-wizard change re-required both burns for nothing. Both files stay covered: src/accel.* by the cooling tests, src/wizcalc.* by the setup checks that run it (lens travel among them). Proof: the coverage lint on a manifest from the recipe pins reports 0 uncovered paths, as before. test_setup_burns, test_manifest, test_campaign and test_artifact pass under Linux, and the forgetest host suite passes under Linux. Acceptance: setup.focus and setup.floor (BURN_COVERS is setup_burns.py's shared text, so both fingerprints move; the catalog hash changes with the covers). |
||
|
|
a9c794be34 |
forgetest: a path a host test proves needs no catalog test
The coverage lint failed CI on any behavioral path no catalog test covered, so every software change pulled a new or wider acceptance test in with it, though the catalog is for what needs the machine or an operator. A path whose behavior a host test in CI proves now has a row in HOST_TESTED (manifest.py): the component, a glob, the host test, and the CI workflow that runs it, the last two as "<repository>:<path>" - a file of a component in the manifest (its tests/ and .github/ files are listed there), or "forgefirm:" and a path in this repository (the null-sink harnesses, which grblhal-glowforge's workflow runs). - coverage_report counts a host-tested path as covered. Fingerprints are unchanged: a path no catalog test covers is in no fingerprint, so a change there makes no acceptance run necessary. - host_row_problems reports a row whose glob selects no behavioral file; a host test or workflow that does not exist (a component file missing from the manifest, a "forgefirm:" file missing from the checkout, the dev-only forgetest, no repository named); a workflow that should be readable and is not; one that does not check out the test's repository; and one with no step that runs the test. - The workflow is read at the pinned revision: read_source takes a component's blob from the bare repo manifest-from-tree.py fetched for its source URL (the checkout's .manifest-cache, or --sources), a "forgefirm:" file from the checkout. With no sources (an image) it is not read and the lint says so; with sources, a read that fails is a problem, never a pass. - workflow_steps reads the workflow's steps by indentation (uses, run, working-directory, with), comments dropped, whole lines and from " #"; a quoted scalar loses its quotes, a quote inside a command stays. checkout_path finds where the test's repository is checked out (its own: the checkout naming no repository; another: the checkout of <owner>/<name>, the name from the component's source URL). - workflow_runs looks at what a step's run commands execute, split into simple commands (;, &&, ||, |, newlines, then/do/else; a backslash continuation joined), with sudo and its flags, env, time, exec and variable assignments passed over, each path resolved against the step's working-directory and compared with the test's workspace path: the command itself (a script run directly, a C test's built binary <dir>/<stem>); the script an interpreter runs (python, bash, sh) or a for loop's list, by path or a glob matching the whole path segment by segment; unittest discover starting in the test's own directory with a pattern its name matches (test*.py unless -p); pytest given the test or its own directory, for a name it collects. An argument to anything else (echo, cat, a build target), a step's name, a comment, a trigger's paths filter, or a path that only ends like the test's does not count. - The lint prints the row problems and fails --enforce on them, as on an uncovered path or an empty covers entry; --json carries them as host_tested, with workflows_read (false when a workflow that should have been read was not). It reads HOST_TESTED when it runs. - The table starts empty: rows come with the tests that leave the catalog. Proof: host tests: a host-tested row takes its paths out of the report and moves no fingerprint; good rows report nothing; each kind of bad row is reported with its reason; a workflow that runs one harness and names another only in its paths filter, a comment line and an inline comment runs the first and not the second; workflow_runs on a built binary, sudo and a binary, a script, a harness under another checkout, pytest on a directory, a for-loop glob and unittest discover under a step's working-directory, and not under another one; and not on a paths entry, an inline comment, a glob or a discover that the path only ends like (tools/tests/..., scripts/bench/tests/...), a longer name, another file, a step's name, or a commented line in a block; not on echo with the stem or the binary's path, a build target, cat of a script, unittest discover from a directory above the test or with a pattern its name misses, or pytest on a directory above it; on discover with -p '*_test.py', pytest given the file, a script run directly and by bash, and sudo -E with assignments continued onto the next line; checkout_path for its own and another repository and none; repo_name from a source URL; a component's workflow read from a bare repo's blob, judged, not judged without the sources, and a blob that cannot be read reported; the command line with a workflow it cannot read reports workflows_read false and the read failure, and fails --enforce. The manifest tests (34) pass on Windows and under Linux; the forgetest host suite passes under Linux. Against the recipe pins and their fetched workflows: rows for forgectrl's tray_test, forgeext's holdkeep_test, sandbox_test (under sudo) and run_test.py (sudo, env and assignments over three lines), the laser_stream_test harness (grblhal-glowforge's checkout of forgefirm), gfhardware's test_machine_lid_button (a for-loop glob under its working-directory), gfutilities' test_frame (pytest on tests) and forgetest's own test_manifest (unittest discover under forgefirm/forgetest) pass; rows for forgectrl's fflog_e2e.sh, the bench script fire_test.py and kas/source-bundle.yml (only in forgetest-ci's paths filter) are refused. The lint on that manifest reports 0 uncovered paths and 0 host-tested row problems. Acceptance: none. forgetest is dev-only and in no fingerprint; the lint runs in CI, not in a campaign. |
||
|
|
1191389a2b |
forgetest: no press is ever held; the arm notice goes down at the press
- The presence press at the Ready gate polled forgectrl's /status over HTTPS every 0.25 s plus the request's own time, so a short press fell between two reads and the operator held the button for seconds. It is read as an edge from the switches' input events (hw.ButtonEdges, no grab), opened before the notice goes up: a press of any length counts. Events read and not yet looked at are kept, so a short press whose release arrives in the same read gives the release to the next wait. With no switches device (the host harnesses) the status is polled at 50 ms. - An operator's arm notice (arm_press, press_now) stood until the test's next step, over a press already taken. A watcher takes it down the moment the controller puts the button out at the press; a notice put up meanwhile is left alone. Proof: host tests: a press and release written back to back on a FIFO standing in for the switches' device pass the Ready gate in well under a second with the status never showing the button down (the first version dropped the release with the press and waited 10 s: the test caught it); the arm notice stays while the button is dark or lit and goes down when it goes out, and a notice put up meanwhile stays. The fixture (35), setup (87), manifest (26) and operator (22) host tests pass. Acceptance: these are the harness's own paths; every live and operator test with a press runs through them. |
||
|
|
4c5dde2549 |
forgetest: the burns, the lens and the intake checks follow forgectrl's review fixes
- baseline: the lid lamp fades to a new level (the kernel's smooth trigger), so the hand-back reads it until it holds the wanted level twice in a row, for up to 3 s, before it is a leftover. motion.deadman failed its hand-back reading 232 against 236 0.05 s after the supervisor re-applied 236 to a respawned controller. - setup.check-lens and setup.focus: grblHAL reads the lens window and the edge only at its start, and their restore came after the wizard had restarted the controller on the test's values. Each test now waits for the step to end, puts the settings back, and restarts the controller (stop, start, running and verified with a new pid) on every exit path; cloud mode restarts nothing. - setup.focus and setup.floor: the served program ends with G0 Z<edge>, G0 X0 Y0, G92.1, M2; setup.floor requires setup.focus and checks the scrap's thickness against the lens reach; the burns cover lenshome. - setup.check-intakes: the two assertions that could not fail are gone; an intake-count prompt fails the test unanswered (the bench reference's intakes both turn). The precheck's refusal names no setting. - setup.check-airflow: the machine's count rules (intake 2 turning writes 2; silent on a one-fan machine, the count is not written). Shared text: setup_dark.py's precheck string (all setup_dark tests, setup_burns, setup.check-envelope), setup_burns.py's imports, covers, docstring and preview check, cooling_intake.py's docstring; every test they move covers paths forgectrl changed in the same work. Proof: the forgetest host suite passes (548), with new host tests for the lamp's fade, the restarts on pass, fail and abort, the floor's Focus refusal and reach, the tail without G92.1, the intakes prompt, and the airflow count rules; pyflakes is clean on the changed modules. Acceptance: these are the tests. |
||
|
|
005ff8008b |
forgetest: the coolant flow calibration is a required step; the gate's missing list
forgectrl requires cooling.flow after preferences, machine, and cloud, and GET /wiz lists the required steps the gate waits for as missing. - REQUIRED_STEPS is the form steps plus cooling.flow, in the gate's order; REQUIRED_FORM_STEPS are the form steps. cooling.flow leaves RECOMMENDED_STEPS. - setup.gate-blocks-controllers expects "setup required: preferences, machine, cloud, cooling.flow" on a record with no step done, with all four named in missing, and an empty missing list once they are done and the gate is open. - setup.first-run-flow walks the form steps alone: its seed keeps the checks done, the calibration among them, so the gate's reason narrows from the three form steps to open. Proof: the setup host tests (74) and the manifest tests pass; the host test of record_with_required_steps keeps the four. Acceptance: these are the tests. |
||
|
|
748d28686d |
forgetest: the setup burns need two witnesses and record the beam sensor
setup.focus and setup.floor follow forgectrl: a burn counts on the tube current and the kernel's LASER_ON samples, and its result records the beam sensor's rise and beam_sensor_seen, never requiring a rise (a head may give no output from the sensor). The check reads that both are recorded and logs them; the descriptions name the two witnesses. Proof: the setup host tests (74) and the manifest tests pass, two of them new: a burn whose sensor gave no output passes, and a result that does not record the sensor fails, each with the settings put back. Acceptance: these are the tests. |
||
|
|
ecbfe64cef |
forgetest: update.slots-and-signature reads the apply job's log lines
The foreign-signature drill's refused apply now also checks the forgectrl log, from the size it had before the apply: "update: writing the uploaded archive into slot <x>" and "update: the apply failed: archive is not signed". The edit sits inside the test's own function. Proof: the manifest tests pass and the module imports clean; on the bench reference with the logging forgectrl, the same drill by hand logged both lines. Acceptance: this is the test; it covers src/update.c already. |
||
|
|
a729197a8f |
forgetest: the first-run flow checks the machine facts see the head
setup.first-run-flow, after the machine step, checks that GET /wiz's machine facts read the head as present with a hardware id. The facts had read every head as not detected (forgectrl looked for "hw_id=" where the module prints "id="); the bench reference always has a head, so the check holds on every campaign. Proof: the setup host tests and the manifest tests pass; on the bench reference with the fixed forgectrl, GET /wiz reads the head as present with hw_id 044c. Acceptance: this is the test; it covers src/wiz.* already. |
||
|
|
ed52e752ee |
forgetest: the setup required up to the intake fans; the burns replace the sheet
The catalog follows forgectrl's setup (openglow-org/forgefirm#4): the gate is the advisories with the press, the account, the required steps (preferences, machine, cloud), and the intake fan count; every other step is recommended; the setup sheet is retired for two self-contained burns and a dark lens travel check; the override file is gone. - setup.gate-blocks-controllers installs four records: without consent, without the account, and without the three required steps the controller is gated with the reason naming the cause; with them done the gate opens, the controller runs, and the other steps are recommended as not run. Clearing cool_intake_fans closes the gate live with its reason, and putting it back as found reopens it. - setup.override-until-reboot is deleted with its helpers, and forgetest.init no longer writes the override file at boot. - setup.first-run-flow follows the reason from the three required steps to open; setup.first-run-page walks the choice screen after them; setup.what-changed reads the one-level map with the gate unmoved. - New setup.check-intakes (the count by the tachs equals the machine's, the setting put back as found) and setup.check-lens (the window written equals the stops found or the fallback, and the settings and /status hold it; both keys restored). A precheck refuses the intake and airflow checks while cool_intake_fans is unset, so neither can leave it unset in the middle of a campaign. - setup_burns.py replaces setup_sheet.py: setup.focus and setup.floor answer the gantry stops, the home, the scrap and the thickness, and keep the preview, emission and window checks; motion.tray starts the focus burn for its tray-out refusal. Proof: the forgetest host suite (529 before the required-steps edits, and the full suite after them) and the manifest load pass; pyflakes reports no undefined names. The coverage lint is clean against forgectrl's working tree; against the current pin, setup.check-lens's src/lensfind.* glob selects no file until the forgectrl pin moves to the commit that adds it. Acceptance: these are the tests themselves; the campaign runs them with cool_intake_fans set on the bench. |
||
|
|
dfd32d5fd1 |
Release v0.0.8
FORGEFIRM_RELEASE is 0.0.8, and releases/v0.0.8 carries the release notes and the acceptance artifact that forgetest exported on the bench reference. The artifact is from campaign c-20261010224522-439d on the pins-only dev image 20261010224118: authorized, 128 of 128 tests satisfied, 48 run on that image and 80 inherited. scripts/acceptance-gate.py against that build's release manifest gives AUTHORIZED, with inputs identical to the bench image. |
||
|
|
35221de791 |
forgetest: every machine action a test takes is declared
The queues route a test by its kind and its declared actions: an auto test runs unattended with or without the bench fixture, and an operator test whose actions the fixture covers runs unattended only while it does. exthost.events opens and closes the lid through Context.act but was declared auto with no actions, so the unattended queue ran it with no fixture on the network, and it waited 180 s for a person who was not there: the fx29 campaign (image 20261010213702) ended on "lid open was not seen on the machine within 180 s" with forgefixture.local silent to mDNS and 172.16.1.171 silent to ping. - exthost.events: operator, actions lid, with the operator's steps. - events.button-telemetry: the same defect with the button; operator, actions button, with the operator's steps. - setup.check-switches: declares interlock beside lid and button; the check works the interlock loop on a Pro. test_fixture's new CatalogTests case reads every catalog test's source and fails a test that calls Context.act on a channel it does not declare, or that is auto and calls it at all. It found these three and no other. Fingerprints: the three decorators are each test's own text, so these three move and no other (diffed across the change, 128 tests). Proof: the whole host suite passes (509 tests) and pyflakes finds no undefined name. RoutingTests already prove the routing these declarations feed: an operator test the fixture covers moves to the unattended queue, one it does not stays attended, and with no fixture the routing is the old one. Acceptance: the change is the three tests' declarations; with the fixture up each runs unattended through it as before, and with the fixture down each stays in the attended list instead of stranding the unattended queue. |
||
|
|
46b6df1c2d |
Pin forgectrl 0.1.30 on the pushed head
- forgectrl c274f19 (0.1.29 -> 0.1.30): a machine built with one intake fan. cool_intake_fans 1 publishes the second intake as not fitted and never judges it, and the setup's airflow check writes the count it finds (openglow-org/forgefirm#3). Proof: bitbake -c fetch forgectrl from this pin succeeds (2 tasks). The tree manifest from the pins holds forgectrl at c274f19 (218 files), and the coverage lint over it finds no uncovered path and no empty entry (128 tests). Acceptance: cooling.intake-one-fan and setup.check-airflow gate the change, with every test whose coverage names src/cool.*, src/wizdark.*, src/wizcalc.*, src/main.c or src/ui/**. The forgectrl and forgetest trees of this pin passed cooling.fan-gate-trips, cooling.intake-one-fan and setup.check-airflow bind-mounted on image 20260928194539; the campaign runs on the image built from these pins. |
||
|
|
16a8ae9ba3 |
forgetest: cooling.intake-one-fan, and setup.check-airflow reads the intake fan count
forgectrl c274f19 adds cool_intake_fans: at 1, a machine built with one
intake fan, the second intake is published not fitted and never judged,
and the setup's airflow check writes the count it finds.
cooling.intake-one-fan, in a suite module of its own, proves both halves
on a machine with two intake fans with the setting alone. With
cool_intake_fans 1 and an 8 s grace, a session at the cut fan profile
must publish intake_2 as not fitted with no floor while it reads a real
speed, run OK, and judge intake_1 ok at the intake floor; with the intake
floor at its legal maximum it must trip AIRFLOW (hold, fire blocked) with
intake_1 TRIPPED and intake_2 still not fitted, and the fault must end
with the session; restored, every fan reads ok at its floor. It starts
only from a machine with both intakes judged, requires
cooling.fan-gate-trips, and puts the count, the intake floor and the
grace back as found on every exit path.
setup.check-airflow now reads the count from the result: 2 needs all
four fans at least 1000 rpm, 1 needs intake 2 reported {"fitted": false}
and the other three ok; either way the setting must read what the check
found, and the count is put back with the five floors.
Fingerprints: the new test has a module of its own, so no cooling test
moves; the setup.check-airflow edit is inside its function. Diffed across
the change: 127 -> 128 tests, one added, setup.check-airflow the only
one moved.
Proof: the touched host tests pass (test_cooling_intake_suite 4,
test_setup_dark 10, test_cooling_suite 25) and pyflakes is clean.
test_cooling_intake_suite replays the test against a scripted engine,
and two broken engines fail it with the settings put back: one that
ignores the setting, and one that takes intake 1 out with intake 2.
test_setup_dark's new AirflowCheckTests take a two-fan and a one-fan
result, and fail a count the setting does not match and a result with
no count. On the bench reference, image 20260928194539 (dev) with these
files and forgectrl c274f19 bind-mounted, cooling.fan-gate-trips,
cooling.intake-one-fan and setup.check-airflow pass with a clean
baseline before and after each (forgectrl's commit has the readings).
|
||
|
|
72907f311a | Update README | ||
|
|
3873013383 |
v0.0.7 release notes: the text as revised after publishing
The notes were rewritten after v0.0.7 was published: the extensions paragraph and every item under "Also new". The release page on GitHub now carries the same text, followed by its "Installing your own build" section. The v0.0.7 tag keeps the notes as first published. |
||
|
|
2ea501f975 |
Release v0.0.7
FORGEFIRM_RELEASE is 0.0.7, and releases/v0.0.7 carries the release notes and the acceptance artifact that forgetest exported on the bench reference. The artifact is from campaign c-20260929000044-e1ea on the pins-only dev image 20260928194539: authorized, 127 of 127 tests satisfied, 7 run on that image and 120 inherited. scripts/acceptance-gate.py against that build's release manifest gives AUTHORIZED, with inputs identical to the bench image. |
||
|
|
1bc15fe70a |
forgetest: theme.css from forgectrl at its new pin
The pin to forgectrl 4b39663 brings theme.css's rules for the Extensions tab and a package's page in the panel's theme (94d8af5), which forgetest's shared copy did not have, so check-ui-vendor would fail forgetest-ci. The copy is forgectrl's file at 4b39663, byte for byte; the rules only add classes forgetest's page does not use. Proof: scripts/check-ui-vendor.py, fetching forgectrl's files from GitHub at the pinned revision, reports the shared UI files identical. |
||
|
|
66b39c6f2b |
Pin forgectrl 0.1.29, forgeext 0.1.1, grblHAL-glowforge 0.1.20 on the pushed heads, and meta-openglow in the kas lock
The work since the last pins, pushed to each component's default branch, is what the recipes now fetch: - forgectrl 4b39663 (0.1.28 -> 0.1.29): the manual home offset and the gantry stops by name in the panel, the Extensions tab and ui.background, the Grbl sender keep-out, the head camera for local viewers and live video on a package's page, the display units in the bridge's self answer, the crumb tray's switch and offset, the Lens card's fields saved, the listener's cipher order (ChaCha20-Poly1305 first), and the camera pipeline (coherent capture buffers on the GPU path, the two-pass NV12 render, the frame gate, the /cam/status timing, one switch fd). - forgeext 2d0391e (0.1.0 -> 0.1.1): ui.background, sender.keep_out and live video in the SDK, a package removed and installed again started from the new install, and the same ffx archive for the same input. - grblHAL-glowforge b34636f (0.1.19 -> 0.1.20): the manual home offset, the gantry stops by name, the controller port's sender out and in with the keep-out, and the crumb tray's M103 P0 and P1. - kas lock: meta-openglow f1f7f58 -> f012d8e, kernel TLS with the NEON crypto and the TX records in cache lines of their own (patch 0016). Proof: bitbake -c fetch of forgectrl, forgeext, grblhal-glowforge, linux-fslc and kernel-module-glowforge from these pins succeeds (10 tasks), with kas moving layers/meta-openglow to f012d8e. The tree manifest from these pins holds forgectrl at 4b39663 (217 files), forgeext at 2d0391e (90) and the driver at b34636f (177), and the coverage lint over it finds no uncovered path and no empty entry (127 tests). Acceptance: the same trees, through the externalsrc overlay and the mirrored meta-openglow at f012d8e, are image 20260928182434, on which all 127 tests are satisfied (the unattended campaign and the attended tests the operator ran). The meta-openglow move is a platform change; the image built from these pins alone is next. |
||
|
|
83686fc712 |
forgectrl.tls-records: a window another client connected in is measured again
The test judges each connection by the kernel's TLS counters and the crypto engine's interrupt, and both are the machine's, not the connection's. On image 20260928175056's campaign the test before it restarted forgectrl, the operator's panel (six HTTPS connections from the bench PC) reconnected in the same second as aes-1.3#1, and the kernel counted 7 sessions each way where the test wanted exactly 1; the connection itself chose AES-128-GCM, carried the page byte for byte, and counted no decrypt error. _fetch now also reads the machine's accepted TCP connections (PassiveOpens, shared by both families); a case whose window saw any connection but its own is measured again, up to 30 times after a pause drawn from 0.1 to 0.9 s (so no steady poller, such as a page polling this suite once a second, stays in step with it), and fails if every window was contested. The judgments are unchanged: exactly one session each way for a cipher the kernel seals, none for CBC, the engine's interrupt counting the AES records and not the ChaCha20 ones. Proof: pyflakes clean. On the bench reference, image 20260928175056, with an openssl client connecting to :443 every 0.25 s for the first 6 s of the run and the drill polling this suite once a second: the image's test fails (desktop-1.2 counted 2 sessions each way), the fixed test passes with contested windows measured again (at most 4 attempts a case), and passes again without the extra clients (at most 4). Acceptance: the change is forgectrl.tls-records itself; tlsrec.py holds no other test, so no other fingerprint moves. |
||
|
|
f47ddad839 |
exthost.platform: the frozen window opens once the freeze has landed
The test took its first reading of the frozen probe's cpu.stat the moment cgroup.events said frozen 1, and wanted 0 us over the next 1.5 s. The kernel reports a task frozen before its last way into schedule() is charged to the group, so a reading at the event can come before that charge lands. On image 20260928170058's campaign the frozen probe was charged 16 us and the test failed. The first reading now comes 0.2 s after the event; the window and its judgment (0 us) are unchanged. Proof: pyflakes clean. On the bench reference, a spinner under cpu.max 5% in a cgroup of its own, frozen 520 times with the first reading taken both ways: read at the event (polled every 50 ms, as the test does), the next 0.5 s was charged 4 times (13 to 20 us); read after the 0.2 s settle, never. Acceptance: the change is exthost.platform itself, inside its own function, so no other test's fingerprint moves. |
||
|
|
eb9e61d3c6 |
motion.soft-limits: past the envelope the home armed, and stopped before it fails
After a home the driver's X/Y envelope reaches to the far edges the bed check measured (envelope_x_mm, envelope_y_mm, held by gfhome_clamp_envelope_mm to 50 mm up to the travel plus 30 mm), and to $130 by $131 only on a machine that was never measured. The test aimed its refusals at $130 + 5 and $131 + 5, which holds only on an unmeasured machine. On the bench reference, measured at X 508 and Y 288.925, image 20260928153805's campaign sent G90 G1 X500 F600, the controller rightly ran it, and the failed check ended the test before its soft reset, so the head drove the whole 476.6 mm while the restores were refused with 409 for 40 s; the jog check after it would have run a 500 mm jog the same way. The test now takes the far edges as the driver does and sends each move and the jog 5 mm past them, and it stops a move that was not refused (the soft reset, now ahead of the checks) and a jog that was not refused (the jog cancel) before it fails. The judgment is unchanged: ALARM:2 with no kernel motion, error 15 for the jog, a move inside the bed runs. Proof: pyflakes clean. On the bench reference, image 20260928153805 with this suite bind-mounted, the test passes: the far edges 508 and 288.925, G90 G1 X513, Y293.9 and X-1 each refused with ALARM:2 and 0.0 mm of kernel motion, the jog refused with error 15, the move inside the bed run, the head handed back where it was found, the baseline clean. Acceptance: the change is motion.soft-limits itself; its own function and description carry it, so no other test's fingerprint moves. |
||
|
|
55e789bc34 |
camera.frame-health: five seconds of stream, none withheld, and its timing
forgectrl now publishes a stream frame only once the frame after it has come back clean, withholds both neighbors of a frame the receiver flagged, and reports health.withheld and a per-stage timing block in /cam/status. camera.frame-health keeps its snapshot burst and adds five seconds of MJPEG: at least 30 frames dequeued, none flagged, none withheld, a withheld count and a timing block present while the viewer watches, and capture end to publication above 0 and under 250 ms. Proof: the test fails on image 20260928000227's forgectrl, which reports neither the withheld count nor the timing block, and passes on forgectrl with the frame gate (75 frames, none withheld, 67.3 ms capture to publication, on the bench reference). The unit suite (500 tests), pyflakes, and the coverage lint (0 uncovered paths) pass on the host. |
||
|
|
11031ad87f |
GnuTLS with kernel TLS, and forgectrl.tls-records
The gnutls bbappend builds GnuTLS with --enable-ktls and installs /etc/gnutls/config with ktls = true, so after the handshake the kernel seals and opens forgectrl's HTTPS records (the kernel side is meta-openglow's CONFIG_TLS and patch 0016). It also backports GnuTLS dc016daf: 3.8.4 hands the kernel the record sequence number where a TLS 1.2 ChaCha20-Poly1305 connection's IV belongs, so every such connection failed the kernel's first decryption. forgectrl is the only program on the image that links GnuTLS. forgectrl.tls-records, in its own module (suite/tlsrec.py), reads the result from the outside with openssl s_client on loopback: a desktop offer (AES-GCM first) gets ChaCha20-Poly1305 over TLS 1.3 and 1.2 and the kernel takes both directions' keys; an AES-128-GCM-only offer, three times over each protocol, gets it, the kernel takes its keys, and the CAAM's job-ring interrupt counts the records; a TLS 1.2 CBC-only offer connects and stays in GnuTLS (the control for the kernel's counters); every copy of the panel page equals the plain-HTTP copy byte for byte, with no decrypt error; the kernel's drivers are rfc7539(chacha20-neon,poly1305-neon) and ctr-aes-caam with ghash-ce. Proof: on the image before these fixes the test failed on both bugs it names (the TLS 1.2 ChaCha20 page arrived empty with a decrypt error; the AES-GCM pages arrived damaged); on image 20260927224418 it passes. The unit suite (500 tests) and the coverage lint (0 uncovered paths) pass on the host. |
||
|
|
cd84786fc5 |
tray_test.py and motion.tray: the crumb tray's mode on the null-sink controller and on the machine
- scripts/bench/tray_test.py, run by the driver's CI: M103 P1 and P0 move Z and its envelope by tray_offset_mm on the lens step grid, tell the sender, write and remove the marker beside the shared config, and ship no step; a missing P, P2, P0.5, P-1, and an axis word are errors that change nothing; the switch behind a move waits for the move; the port's tray op with a sender connected, its count exact, refused out of form, during a program, and at a package's M-code; the mode across a soft reset and a killed controller, a start over the marker taking the lens reference in the tray-out frame; the offset's range; an unreferenced lens; a camera home in both modes handing the runner the same park. The kernel-idle half of the barrier needs the pulse device: a null sink is always idle. - scripts/bench/ctlport_test.py: the dark case carries tray out and in under an open armed window. - forgetest: motion.tray in its own module, suite/tray.py, so no other test's fingerprint moves. It switches the mode both ways through the panel's route and through M103 from a Grbl client and judges each switch: /status's mode, Z, and reach moved by the offset's grid, the port's state, the marker, the reference and its source unchanged, the client told, the kernel's counters unmoved. A jog to the Z the lens read before a switch is refused in the other mode and taken back in the mode found; a setup card started with the tray out is refused in the operator's words; the panel page carries the switch. The mode goes back as found in a finally. It does not restart the controller, which would drop the X and Y reference: persistence is tray_test.py's. - The bench registry and README list tray_test.py. Proof: tray_test.py ALL PASS against the driver with the mode, and six driver copies each missing one part of it fail. The driver's whole CI harness list passes. The forgetest unit suite: 500 tests OK. The coverage lint over the working trees: nothing uncovered, 126 tests. On the bench reference, with the binaries hot-deployed on the fx18 image: motion.tray PASS, and FAIL against the driver whose anchor lost its source word on the second switch. Acceptance. motion.tray is new; the suite and bench files are in no other test's fingerprint. |
||
|
|
6e71ac86f0 |
forgetest: exthost.sender-keep-out, camera.lid-privacy for local viewers, and ctlport_test's keep-out
- suite/extsender.py (new module, so no other test's fingerprint moves): exthost.sender-keep-out. A reference package whose service claims the Grbl sender out as it starts, installed with the sender.keep_out grant (and refused without it), on an idle GRBL-mode machine with no sender connected. It checks the holder and the ext: lease of the extension kind, the host's claim file fresh past forgectrl's grace, a sender from the machine's LAN address turned away with the message and one from the loopback admitted, the operator's release (lease gone, network welcomed, the package reading released), a new claim after a restart, and the stale end with the notice after a disable; everything is put back as found. - camera.lid-privacy: the head camera answers the panel's token and the extension host's header with the lid open, and refuses the cloud client's form (no token, no header) and a stream for no local viewer; the lid camera stays refused to everyone. covers gains src/auth.*. - scripts/bench/ctlport_test.py: test_sender_out on the null-sink controller build (Sender(host=) and closed, own_address(), generation()). Proof: forgetest's unit tests pass; the coverage lint passes against the working trees' file lists. Against the current pins it reports src/senderout.* as selecting nothing until forgectrl's pin moves to the revision that adds it, which lands with this. On the bench reference with the suite mounted: exthost.sender-keep-out PASS (first run found the check racing the host's first write; the test now waits for the file and holds past the grace), and camera.lid-privacy passed every check of its body (its hand-back failed only on the controller gated by the privacy advisory's Revision 5 before it was accepted). ctlport_test.py passes in the driver's CI. |
||
|
|
1727b819cb |
homing.manual and the harnesses: the gantry stops by name
The parts the manual home rests against are the gantry stops in homing.manual's title and comments, in manual_home_test.py's docstrings, and in a comment of the baseline's hand-back. No check changes. Acceptance: homing.manual's own text changed, inside its function, so no other test's fingerprint moves. manual_home_test.py passes against the driver. |
||
|
|
b494bbecb5 |
homing.manual and the host harness: the manual home jogs to an offset origin
homing.manual runs two homes. With manual_home_x and _y unset, $H plays no byte and declares X0 Y0 (the checks it had). With 5 and 3, $H declares the head at minus them and jogs it to X0 Y0 before it answers: the sender is told the declared position, the kernel plays the move, Grbl and forgectrl read X0 Y0, and a jog behind the origin draws error:15. The test restores homing_mode and both offsets as found, and returns the head by what the machine read, so a move cut short still comes home. The change stays inside the test's own function. manual_home_test.py: case 7 is the offset's new meaning (declared at minus the offset, the move dark under a modal M3, the port's home, the clamps); a new case cancels the move with a jog cancel and expects it to stop short with $H saying where; case 8 reads the declared position from the sender's message. The bench README says so. Proof: manual_home_test.py ALL PASS against the driver change (ten cases), and its cancel case fails against a build with the driver's cancel hook left out. On the bench reference, fx14 with the driver change and this suite file in place of the image's, homing.manual PASS in campaign c-20260926000059-e731: with no offset no byte and X0 Y0; with 5 and 3 the sender was told X-5.002 Y-3.000, the kernel played 1067 and 640 steps, Grbl and forgectrl read X0 Y0, error:15 behind the origin; settings restored and the baseline clean. The forgetest unit tests pass. Acceptance: homing.manual is the test; its covers are unchanged (the driver's src/**, forgectrl's status and main.c). |
||
|
|
b2e0f91342 |
forgetest: theme.css from forgectrl at its new pin
The pin to forgectrl 55bfae9 (
|
||
|
|
a05a226305 |
kas lock: meta-openglow onto the head that pins python3-gfhardware 021323d
meta-openglow 2978522 -> f1f7f58. The library's pin lives in the BSP
layer and still named 6cc4f45, so the image built from
|
||
|
|
9f7744b459 |
Pin forgectrl 0.1.28, forgeext 0.1.0, grblHAL-glowforge 0.1.19, forgefirm-app 0.1.31 on the pushed heads, and meta-openglow in the kas lock
The extensions work, merged into each component's default branch and pushed, is what the recipes now fetch: - forgectrl 55bfae9 (0.1.27 -> 0.1.28): the machine lease, the job runner, the controller port client and motion routes, scoped tokens, the event stream, the built-in registry, the operator's door to extension packages and the Extension packages card, the catalog from its own repository, and privacy advisory revision 4. - forgeext 711f099 (first pin, 0.1.0): the extension host, and ffx new making a package's repository. - grblHAL-glowforge 0da0fe6 (0.1.18 -> 0.1.19): manual homing, the X and Y motor release, the controller port, custom M-codes, the envelope rule, and the homing runner's report secret. - forgefirm-app 021323d (0.1.30 -> 0.1.31): the report secret, and a homing motion cut short failing the home. - kas lock: meta-openglow 71fad52 -> 2978522, the kernel's sandbox options (NF_TABLES, LANDLOCK, MEMCG, the cgroup scheduler) and their notes. Proof: bitbake -c fetch of forgectrl, forgeext, grblhal-glowforge, python3-gfhardware, gfhome, gfcloud and python3-ffmachine from these pins succeeds (14 tasks), with kas moving layers/meta-openglow to 2978522. The tree manifest from these pins holds forgectrl at 55bfae9 (211 files), forgeext at 711f099 (90) and the driver at 0da0fe6 (175), and the coverage lint over it finds no uncovered path (124 tests). Acceptance: the meta-openglow move is a platform change, so the whole catalog runs again on the image built from these pins. |
||
|
|
8dc7c0bc56 |
forgetest: the stale boot reference judged at a held uptime, on both sides of the limit
test_stale_preconfig_reference_is_retaken_on_a_fresh_boot read the host's
uptime after boot_reference() had read it to decide, and expected the
decision its own reading implied. On a CI runner the uptime crossed
BOOT_MAX_AGE_S (600 s) between the two readings: boot_reference retook
the reference and the test expected the stale one (forgetest-ci on
|
||
|
|
0eb0a16452 |
forgetest: forgeext's packages/ exemption goes with the packages
OpenGlow's packages left forgeext for repositories of their own, so the
non-behavioral entry ("forgeext", "packages/**") names nothing. The
manifest test checks the shared package workflow's kit action as
non-behavioral in its place (".github/**" covers it). The manifest tests
pass (26); manifest.py is in no fingerprint.
|
||
|
|
bfcfd7a376 |
forgetest: exthost.catalog on the catalog's own repository and its judged index
The address is the catalog repository's index-1.ffi. On the scratch root the index lists the probe in two versions: 1.1.0 asks for a capability no firmware has, and read back it is kept and not offered, the offer being 1.0.0. An older index is refused however well signed, and the one kept stays. An index withdrawing the probe's 1.0.0 makes inspect refuse it in OpenGlow's words. GET /ext/catalog carries core_checked. The covers widen from forgeext src/install.c to src/install.*. The test still never requests the index's address. Its own module (suite/extcat.py), so only exthost.catalog's fingerprint moves. forgetest's 500 unit tests pass; the function was read against _forgeext, fc.get and fc.post, and ctx.check, since no host test runs a suite function. |
||
|
|
d2307a899e |
forgetest: the cooling tests judge the log line their own run wrote
Six checks in five cooling tests read forgectrl's log tail once for the line the engine writes when it acts: the TEC-on line (cooling.tec-drive), the off gate's line and the run-end temperature line (cooling.gate-off), the warm-up release and the floor's off line (cooling.floor-and-warm-up), the critical line's off line (cooling.critical-tier), and the two put-back lines (cooling.fan-duty-readback). The engine writes to the device first and logs after, and the line reaches the file through rsyslog a moment later still: on the bench reference cooling.tec-drive read thermal/tec_on = 1 and then the tail a few milliseconds before "TEC on: coolant 26.3 C over 24.5 C, airflow up" landed, and failed. A tail read also takes a line an earlier run left behind for this run's. Each check now marks forgectrl's log before the action that makes the line (the settings write, the M8, the duty written behind the engine's back) and waits up to 5 s for the line among those written after the mark. Every line was checked against where cool.c writes it: gates_apply for an off gate, the run's end for the temperatures, the warm-up release, fans_verify for a put-back, the TEC policy for the TEC. The helpers are motion's log offset pair, imported inside each test's function as the fire-watch tests already do; the tests that poll the tail already (_tail_wait) are unchanged. Proof: tests/test_cooling_suite.py's fake engine writes its lines to a scratch forgectrl log as well as to the fake tail, and a new case puts an earlier run's off-gate line in the log with the engine writing none this run: it fails as it must, and against the checks before this it passes. 25 cooling cases pass; forgetest 500 OK. On the bench reference, image 20260925183749 with this file mounted: cooling.tec-drive, cooling.gate-off, cooling.floor-and-warm-up, cooling.critical-tier and cooling.fan-duty-readback PASS, each baseline clean. Acceptance: the five tests are the change; their fingerprints move and no other test's does. |
||
|
|
8570c0141e |
forgetest: motion.soft-limits runs its own camera home and hands the head back
motion.soft-limits homed only when the machine was not homed already, and then with whatever homing_mode the machine had: it counted on the home cloud.mode-switch used to leave behind. cloud.mode-switch now drops its camera home when it hands the head back, so the test started unhomed, and under the operator's homing_mode = manual its $H was a manual home, declared at once where the head stood (2.2 s, no web-service session), and the test failed on gfhome's missing completion line. When it did home, it left the head at the corner and the machine homed. The test now sets homing_mode = gfcloud and unsets the camera-home offsets for its run, so the envelope is the bed from the home corner, and always runs its own camera home, every motion of it judged whole (homeoff.judge_whole_motions). A setting is written when the controller takes it (it refuses one for a moment after a Grbl client closes). At the end the settings are put back as found and homeoff.camera_home_return drops the home and jogs the head back to where the test found it. The helpers are imported inside the function: homeoff imports this module. Proof: on the bench reference, image 20260925183749 with this file mounted, motion.soft-limits PASS: the camera home in 52.6 s, every motion completed; X max, Y max and X min refused with ALARM:2 and no motion, the jog past the bed refused with error 15, the move inside the bed ran and came back; the settings put back; the head jogged back 28.95/13.03 mm to where the test found it; the baseline clean. forgetest 499 OK. Acceptance: the test is the change; its fingerprint moves and no other test's does. |
||
|
|
800863fb09 |
forgetest: the cloud tests wait out the service's thinking
wait_quiet took the machine as quiet after 8 s without the start of a motion, a park, a run or a lens homing. Between a lid image and its next move the service is working on the image and the log is silent, and on the bench reference the re-hunt's moves came 9.6 s apart: cloud.mode-switch switched back to GRBL mode in the middle of the re-hunt, twice, the first time canceling a motion at 988 of its 1002 steps. Every line of a service action now counts as activity: the requests, the image uploads, the action ends, the runs, the parks, the motions and the lens homing. The quiet is 30 s. Over 1901 motion, lid-image and hunt requests in the bench reference's logs, half came within 1.6 s of the line before, 99 percent within 12.8 s, and two after more than 30 s (32.8 and 62.2 s). return_head goes: cloud.mode-switch hands its cloud stretch back through the cloud client's log (homeoff.cloud_mode_return), and nothing else called it. RETURN_MAX_MM moves to homeoff, its one user. Proof: tests/test_cloud_suite.py's new case lands a motion, a lid image 0.8 s later and the next move 1.6 s after the motion, with the quiet at 1 s: the quiet comes after the move. With the old activity marks it is declared after the image, before the move. forgetest 499 OK. Acceptance: cloud.mode-switch gates the change on a machine, its re-hunt waited out before the switch back. The fingerprints of every test in suite/cloud.py and of every module that imports from it move: 29 tests, the cloud tests, events.button-telemetry, the exthost tests, homing.cloud-offsets and setup.check-envelope. |
||
|
|
cfef6a1b84 |
forgetest: the camera-home tests hand the head back where they found it
homing.cloud-offsets, setup.check-envelope and cloud.mode-switch let the
service move the head, and ended with it where the service left it: at
the camera home, or, in cloud.mode-switch, under the camera, where the
service's re-hunt had taken it. Each told the baseline the counters had
been re-zeroed at the starting position, so the hand-back saw nothing to
do. No counter reading can say where the head was found: every service
motion zeroes the counters at its start, and the home and every
controller start zero them again.
suite/homeoff.py now holds the helpers for a test that lets the service
move the head:
- session_travel sums a client's own record of each motion's end ("end
positions (x, y, z)", the counters the motion zeroed at its start, in
x8 steps, the one mode a service motion runs at). A motion with no end
on record, or a log rotated under the run, leaves the travel unknown.
- camera_home_return drops the camera home, jogs the head back by the
session's travel plus what the counters read since the home, starts
the controller once more so the counters read zero where the head
began, and tells the baseline. cloud_mode_return does the same for a
stay in cloud mode, from the cloud client's log.
- A travel that cannot be known fails the run and moves nothing. A
hand-back that fails while the test is already failing is logged, and
the test's own failure is the one reported.
- judge_whole_motions fails a homing motion that stopped short: the
position declared after it is false.
cloud.mode-switch imports the helpers inside its function, so no other
cloud test's fingerprint moves. Its cloud stretch no longer goes through
return_head, which read 0/0 after the controller's start and left the
head under the camera.
Proof: tests/test_camera_home_return.py, 15 cases on the machine's own
log lines (a session stopped short, a whole three-motion one, a cut log,
a refused motion, homed and unhomed counters, a restart since the home,
the first failure winning). tests/test_cloud_suite.py's mode-switch fakes
now zero the counters and remove the anchor at a controller start, write
the anchor at the home, and move the counters on a jog: 10 cases, a
failed hand-back that must not hide the test's failure among them.
forgetest 498 OK. On the bench reference, with the driver and the runner
fixes: homing.cloud-offsets, setup.check-envelope and cloud.mode-switch
PASS, each ending with the head where it was found; cloud.mode-switch
jogged its cloud stretch 246.06/139.01 mm back with the counters across
the jog agreeing, and every baseline was clean.
Acceptance: the three tests are the change; their fingerprints move and
no other test's does.
|
||
|
|
d40ebb090e |
cool_report_test: the homing runner is handed the report secret
The runner case asserted that the homing runner the controller starts does not inherit GF_REPORT_SECRET. The runner is the reporter while a gfcloud $H holds the machine (the controller sends no report then), so the assertion described the defect: every camera home ran with nobody reporting, and the cooling engine's hung-controller dead-man stopped the session's first motion. The case now requires the runner to be handed exactly the controller's secret, and two new cases require that it be handed none when the controller was started with no secret (runner-none) or with one that is not 32 hex digits (runner-malformed). The harness's descriptions in scripts/bench/README.md and in the bench page's catalog say so. Proof: all 11 cases pass on the driver that hands the secret over (grblHAL-glowforge, "gfhome: the homing runner is handed the report secret"); the driver before it fails the runner case: "the homing runner was handed [], not '0123456789abcdef0123456789abcdef'". Acceptance: a host harness in the driver's CI; no catalog test runs it. forgetest/forgetest/bench.py changes one description string, which no test judges. |
||
|
|
ef2390f7d4 |
forgetest: a takeover hands back the cloud client it found
In cloud mode forgectrl's start at the end of a takeover starts a cloud
client, and what that client is comes only from gfcloud's one-start
markers, which the client that starts reads and takes down. The takeover
started it bare: the online client, with the service's connect-time
hunt. Seen on the bench with exthost.armed-freeze, which runs its print
under the offline service and puts the setup record back under a
takeover: the machine came back online in cloud mode, the hunt homed the
head at 14:21:01, the run's result was written at 14:21:02, and the
service sent a head move at 14:21:08, after the run had ended, where the
next test's switch to GRBL mode would have cut it off. It also broke the
harness's own rule that every cloud client start it makes comes up
without the hunt.
The takeover now reads, on entry, whether the running cloud client is
the offline service: the client itself holds the listening socket at
/run/gfcloud-offline.sock (hw.listens_on reads /proc/net/unix and the
process's own descriptors, so a socket file another process left behind
does not count). forgectrl's start is made under the no-hunt marker in
cloud mode, and under the offline marker as well when the offline
service was found. After the start the client that came up must listen
on the offline socket within 60 s; a miss is recorded on the run's
baseline capture and the post pass turns it into a leftover ("cloud
client at the takeover end", not restored: a cloud test starts the
client it needs), which fails the run. A marker no client read (the
gate, standby, a fault) is taken down, so a later start never comes up
under it. The judge never breaks the exit path.
Proof: forgetest's unit tests pass on the host (481 OK). The new
tests/test_takeover_client.py drives a real enter and exit against the
fake forgectrl, a fake /proc, and an init script that plays gfcloud's
start: the offline service comes back offline, an online client after
it fails the run, a start that never happened takes the markers down,
an online client comes back without the hunt, a stale socket file is not
the offline service, GRBL mode starts under no marker. Negative
controls: without the markers four of them fail, without the judge
three. Bench drill on the bench reference with this package from /tmp:
GRBL mode, the offline service started as enter_offline starts it (pid
4834), a takeover; the client forgectrl brought back (pid 4898) read
both markers, logged "OFFLINE service", listened on the socket, and in
the 10 s after had no web session, no hunt, and no motion. The bench was
then handed back to GRBL mode as found.
Acceptance: runner.py, baseline.py and hw.py are in no test's
fingerprint, so no result moves; the catalog test that exercises the
change is exthost.armed-freeze, whose takeover now fails the run unless
the offline service comes back.
|
||
|
|
82869cc45c |
No acceptance test requests a counted release file
update.job-locks started a real download in every campaign: POST /update/download fetched the published release's forgefirm.fw, whose GitHub download count is how installs are tracked. ext.catalog asked POST /ext/catalog/refresh, which fetches the extension index's fixed address, index.ffi, counted the same way. update.job-locks now starts the download job with probe=1, forgectrl's probe download: the release's acceptance.json through the download's own path, refused by the signature check and discarded. The record is small and the job can end within a second, so the test writes the settings at once after the 202 (the lease is taken before it) and runs the probe again, up to three times, when the job ended first. A posted job is refused in the job's name beside it, /status names the holder when it is read while the job runs, the job must end refused by the signature check, and the staged download must be as found (size and mtime) with no probe file left. Its covers gain forgectrl's src/relcheck.*. ext.catalog no longer asks the refresh. The refusals before a fetch stay, and the index kept must be unchanged after them. The refresh (curl, https alone and bounded, 502 in curl's words, 409 in the host's, the file removed) is proven on the host by forgectrl's extpkg_test with a stand-in for curl, and the fetch of a listed package and the tiers an install takes from it by forgeext's install_test, as the description now says. Nothing else in the tree requests a counted release file: the installer's own request of forgefirm.fw is an install, and its unit test puts a stand-in curl first in PATH. Proof: forgetest's unit tests pass on the host (474 OK, 4 skipped) and pyflakes finds no undefined name. Acceptance: the change is the two catalog tests themselves; their fingerprints move, and update.job-locks now covers src/relcheck.* too. |
||
|
|
b7dd23d539 |
forgetest: a takeover judges where the head stands
forgectrl's start at the end of a takeover is a controller start, which
re-zeroes the step counters wherever the head then stands. After it a
head left out reads as home, so the baseline's position check after the
run could not see a head a test left out before its takeover.
setup.check-envelope did exactly that on 2026-09-23 and passed (fixed by
|
||
|
|
308a033146 |
laser.verdict-cut judges the hold by the gated output; laser.disarm-in-hold presses at the arm
Both tests failed for the operator on the bench reference on 2026-09-24, image 20260923232513, from the harness and not the machine. laser.verdict-cut (00:10:52) needed the kernel's sampled LASER_ON count to read 0 within 2.5 s of the hold. That count latches once a second over the second before, so it reads 0 only once a whole window has closed inside the hold, up to 2 s in, and this hold lasted 1.37 s. Every sample of the hold read bit 0 of interlock_circuit as 1: the gated LASER_ON, active low, dark. The dark judge now reads the gated output itself, cnc/laser_on (1 = on), about 3300 times a second on the bench reference, yielding the CPU between reads (the stream threads run SCHED_FIFO). Its witness is the same reads over the second before the pause, which must see the cut lit (10 reads or more, none failed). The hold is dark when every read that falls wholly inside it, from 0.3 s after the first Hold:0 to the last Hold row, reads off, none failed, and there are at least 500. The 0.3 s is the pause tier's first deceleration, lit on purpose, still playing out of the driver's 200 ms queue and its 10 ms lead when the controller reports Hold:0. The daemon's pause is 3.0 s, as the code already had and the description now says. dark_span() is pure, kept inside the test's function, and tests/test_laser_verdict.py runs it through the function's code object over synthetic trails (8 cases: a dark hold, the lit deceleration inside the drain, emission after it caught, a burst the resume ends, failed reads counted, the drain counted from the first Hold:0, no Hold:0, and a hold too short for the drain). laser.disarm-in-hold (00:06:23, 180 s) pressed the button through ctx.act at once, before the job reached its arm wait, so the press was lost and the move never started. It now uses ctx.arm_press(), which presses when the button lights, and waits for Run. Proof: forgetest's unit tests pass on the host (474 OK, 4 skipped). Only the fingerprints of laser.verdict-cut and laser.disarm-in-hold move. Acceptance: the change is the two catalog tests themselves; both are attended (laser emission, the operator present) and a campaign runs both again. |
||
|
|
dc9bede034 |
forgetest: the first /state parses each suite module once
The first GET /state after forgetest starts hashes every test's implementation, and it took from 81 s to more than 12 minutes on the bench reference. Two causes: - sibling_imports() read and parsed a test's module and every sibling it imports, transitively, once per test: 280 ast.parse calls for 26 suite modules. module_parts() kept its own parse, sibling_imports() kept none. - The page gives up on a poll after 20 s and polls again; the server thread goes on computing. Each new poll started the same cold work beside the first, all of it on the one CPU, so the longer the first took the more copies ran. That is the spread between 81 s and 12 minutes. Each module's text and tree are now read and parsed once and kept, as are its direct sibling imports, and the implementation hash is filled by one thread at a time: a poll that arrives while it is computed waits for it instead of repeating it. catalog.forget(path) drops what is kept about a file, for the unit tests that edit their modules. The hashes do not move: every test's implementation hash and domain fingerprint on the image manifest of 20260923220034 is byte-identical before and after, so no result is invalidated. On the host the cold computation went from 7.00 s to 0.29 s (280 parses to 25). On the bench reference (the file bind-mounted on image 20260923220034, the page open) the first /state answered in 8 to 12 s after a restart, where the same restart earlier in the evening had not answered after 7 minutes. tests/test_responsiveness.py pins both: each suite module parsed at most once while every test's implementation hash is computed, and three threads reading one test's hash compute it once. With the old behavior put back by a patch both fail (280 parses for 26 modules; the hash computed 3 times). forgetest's unit tests 454 OK. forgetest is the dev-only harness, outside the catalog's coverage; its catalog consequence is none, since no fingerprint moves. |
||
|
|
56af6eaab2 |
setup.check-envelope: the head goes back to the home, or the run fails
The test's cleanup jogged the head back to the camera home through the panel's relative jog, the increment taken from the port's position. The core refused it every time (error:15, "the move would leave the work envelope"), eight tries each run, and the test logged "the head is not back at the home" and passed. The takeover that puts the setup record back restarts the controller, which zeroes the kernel counters where the head stands, so the baseline read (0, 0, 0) and called the machine clean. On the bench reference the head was left 10 mm from the home in X and in Y by every run of 2026-09-23 that got that far (three PASSes and one FAIL, images 20260922225653, 20260923084705, 20260923220034). Why the jog was refused: the port reports the step counters, while the core adds a relative jog to its own parser position. The bed check's last jogs, sized from the counters, leave the two apart by up to half a step. The arithmetic reproduces every logged value: the parser held X 60.1 after the answers, the port read 60.099, the check jogged -50.099 toward X 10, so the parser took 10.001, which the step grid (213.33 steps/mm) turned into 2134 steps, read back as 10.003. The test's return of -10.003 then targeted X -0.002, and a camera home's envelope begins at exactly 0, so the core refused the whole jog. No product path returns the head by a relative jog sized from the counters; the panel's Jog card sends fixed steps. Now the head goes back in one jog from a Grbl client to the home in machine coordinates ($J=G90 G53, 0.5 um inside the envelope's start, far under half a step), and the run fails unless the port reads the head on the home's step. That reading is taken before the restart, because nothing after it can see a head left out. Bench reference, image 20260923220034 with the suite file bind-mounted: a negative control that aims the return 1 mm off the home FAILs with "the head is not back at the home (0.0, 0.0, 3.08): [0.998, 0.998, 3.08]" while the baseline still says clean; the fix PASSes with "$J=G90 G53 X0.0005 Y0.0005 F1200 -> ok" and the head at [0.0, 0.0, 3.08]. forgetest's unit tests 454 OK; the coverage lint passes with --enforce on the image's manifest. |
||
|
|
c95b5aa771 |
update.job-locks: ask the releases API when nothing was checked
The test took the release to download from GET /update/release, the kept answer of the daily check. That answer does not outlive a restart of forgectrl, and a campaign restarts it many times after update.release-check has run, so on the bench reference (image 20260923084705) the test found "not checked yet" and failed with "no release is published to download". When the kept answer was never checked, the test now asks POST /update/check itself, the way update.release-check does. PASS on the bench in the unattended queue (the check asked, 200; the download of v0.0.6 held the lease and ended; no archive left behind); forgetest's unit tests 452 OK. |
||
|
|
d45520c218 |
exthost.motion-jog: the test needs GRBL mode
The package's jog goes through the controller port, which only the GRBL controller serves. The test declared no mode, so it ran in whatever mode it found. Alone it always found GRBL mode; in the unattended queue it runs after the cloud tests, which leave the machine in cloud mode, and on the bench reference (image 20260923084705) the jog came back 409 "the GRBL controller is not running". It now declares mode="grbl", so the runner switches before it starts. The other tests without a mode were checked for the same need: none of them moves the machine or reaches the port (update.job-locks posts a job, and the job route refuses on the lease before anything else, in either mode). exthost.motion-jog PASS on the bench in the queue after the cloud tests; forgetest's unit tests 452 OK. |
||
|
|
2870daa2aa |
logs.routing: forgeext is one of the loggers
The extension host has had its own logger (log_forgeext_disk and log_forgeext_remote, a block in the rendered rules, and /data/log/forgefirm/forgeext/) since it joined the image, and GET /logs reports it. The test's list of loggers never gained it, so the first run of logs.routing on an image that carries forgeext failed on the bench reference (image 20260923084705): "loggers reported: [... 'forgeext' ...]". The list now names forgeext, so the test also holds its rules block, its effective levels, and its directory, and a stray program's line must not land in forgeext.log either. logs.routing PASS on the bench with this file mounted; forgetest's unit tests 452 OK. |
||
|
|
82c354d70e |
setup suite: the port wait, and the first run's accounts on a failure
Two harness faults found on the bench reference in the campaign on image
20260923084705.
setup.gate-blocks-controllers probed the Grbl port once, at the moment
GET /mode first read the controller running. The supervisor reports
running from the fork and the controller binds TCP 23 a few tens of
milliseconds later, so the probe failed whenever the 0.25 s poll landed
inside that window ("the Grbl port is closed with the controller
running"). Timed on the board with the controller restarted through
forgectrl: /mode read running at 0.066 s, the port accepted at 0.087 s.
The test now waits up to 10 s for the port (50 ms polls) and records
how long it took (port_open_after_s). PASS on the bench (0.001 s).
first_run() replayed the system accounts (forgefirm-users reload) and
removed the temporary home only after its block succeeded. When the
block failed, the records came back but /etc/passwd kept the temporary
account in place of the operator's, and image.health failed after it
(the record's account missing from /etc/passwd). The replay and
the home removal now run in a finally. Proof on the bench: the failing
case run on purpose (setup.first-run-flow against the image's own
forgectrl, which refuses a home by hand at the cloud step) leaves the
operator's account the one account from uid 1000 up and no temporary
home; the same failure before the fix left the temporary account in
/etc/passwd and its home in /data/forgefirm/home.
forgetest's unit tests: 452 OK. setup.first-run-flow and
setup.gate-blocks-controllers PASS on the bench with this file mounted.
|
||
|
|
853f862f03 | Update release | ||
|
|
ac2456261e |
The measured envelope's harness, and setup.check-envelope
scripts/bench/envelope_test.py drives the null-sink controller with the scripted sender and port client of ctlport_test.py, homing manually: unset keys are the axis travel; envelope_x_mm and envelope_y_mm are the far edges at a home and are held to 50 mm up to the travel plus 30 mm; the port's envelope open is refused before a home and under an open armed window, and lets the port's jogs reach the travel plus 30 mm; envelope apply puts the keys' edges back without a home; a status poll and an empty line leave an open envelope open, while the sender's first line closes it before the core reads it (a jog into the margin draws error:15), a running port jog canceled first; a soft reset and the port client going away each close it. It is registered in the bench registry as a CI harness, not a bench-page tool. setup.check-envelope (suite/bedsize.py, its own module): with cloud mode on, the machine is homed with the camera, and the Bed size check runs with its jog prompts answered as the page would, out 60.1 mm in X and 61.1 mm in Y from the home. The ends it reports are the home plus those, the far edges 1 mm short of them are written as envelope_x_mm and envelope_y_mm, and the controller holds them at once: a panel jog 0.5 mm short of each edge runs, and one 0.5 mm past it is refused with nothing moved. A second run is opened, and while its first prompt waits a Grbl client sends one line, which draws the controller's word that the envelope closed and then ok; the port then says the envelope is closed, the next answer ends the check in words, and the keys are as the first run left them. The head is jogged back to the home, and the keys, the homing mode, and the setup record are put back as found, the record under a restart. Proof: envelope_test.py ALL PASS on the host build of the controller of this change. On the bench reference, with that controller and forgectrl of the same change bind-mounted: the first two campaigns found flaws in the test, not the check (jogs of 20 mm made an edge below the 50 mm forgectrl refuses; the second run started while the machine was still busy; the head was not returned to the home), then setup.check-envelope PASS, with forgectrl.auth, kernel.latch-locked-idle, motion.pacing, motion.jog-roundtrip, and motion.port-jog PASS in the same campaign, and cloud.mode-switch, motion.soft-limits, and homing.cloud-offsets PASS after it. forgetest's unit tests pass (452), and the coverage lint passes with --enforce. |
||
|
|
8fc07c47ee |
forgetest: update.job-locks and homing.cloud-offsets
update.job-locks (suite/updlock.py, its own module): with a release published on the releases API, POST /update/download starts the download job (202), which fetches and checks the release's archive and applies nothing. While it runs, /status names update:download, of kind system, as the lease's holder, and a settings write and a posted job are each refused in its name (409). When it ends, the lease is free and the same settings write is taken. The downloaded archive is removed when it was not there before. homing.cloud-offsets (suite/homeoff.py, its own module): with cloud mode on, homing_mode = gfcloud, and the camera-home offsets at (4.5, -3.25), $H runs the web-service homing session, and when it ends the controller declares (4.5, -3.25) on the step grid. The work envelope reaches back to the home on the negative axis: a jog 2 mm out and 2 mm back in Y and then in X is accepted and ends at the home. The homing mode and the offsets are put back as found, each retried until the daemon takes it, since a settings write is refused for a moment after the suite's Grbl client closes. Proof: on the bench reference, update.job-locks PASS after update.release-check, and homing.cloud-offsets PASS in a campaign with forgectrl.auth, kernel.latch-locked-idle, and motion.pacing, which also PASS. forgetest's unit tests pass (452), and the coverage lint passes with --enforce. |
||
|
|
69b56ec1ff |
forgetest: exthost.wizard, a package's own check on the Setup page
exthost.wizard (suite/extwizard.py, its own module): a package with the reference id that asks for wizard, whose service runs a check on its call socket and keeps whether it passed. GET /wiz lists it among the extensions as pkg:<id> with the package's title, not done. POST /wiz/pkg:<id>/start runs it on the wizard runner: GET /wiz/dark shows the package's log line, phase, progress, and confirm prompt, and the machine lease stays free while it waits. The operator's Yes reaches the service as yes, the run ends complete with the package's summary, and GET /wiz then lists the check as done from the package's own answer. The machine's setup record is the same before and after, with no pkg: wizard in it and the gate as it was. A second run is aborted from its prompt, and the service is told. A check of a package that is not installed is 404. Proof: on the bench reference, with forgectrl and forgeext of the wizard change bind-mounted, exthost.wizard PASS, beside setup.check-sensors, exthost.service, and exthost.package-routes PASS on the same daemons. forgetest's unit tests pass (452), and the coverage lint passes with --enforce. |
||
|
|
99d9830991 |
The M-code barrier's harness, and exthost.mcode
scripts/bench/mcode_test.py drives the null-sink controller with the scripted sender and port client of ctlport_test.py: the port's mcodes table and its refusals, error:20 for a number nothing answers, an answered M-code's wait (the head still, the port's state naming it with its words, a port jog busy:mcode, stale and out-of-form answers refused, the answer's words on the console), a refusal and a timeout each holding the job, a soft reset ending the wait, and a wait under an open armed window with M3 S500 shipping no FIRE tick. It is registered in the bench registry as a CI harness, not a bench-page tool. exthost.mcode (suite/extmcode.py, its own module): a package with the reference id that asks for mcode:160 and job_time.run, the one granted, and whose service answers POST /mcode on its call socket. The host's status names it as the one that answers M160. A dark job (out 5 mm, M160 P1, back) through POST /job waits at the M-code with the port's state naming it and the head still over 1 s (the kernel's counters and the port's position), the service answering after 2 s; the job then ends done, every line acknowledged, the head back, the service asked once with the code and its words, no discharge. A job naming M161 fails at that line with error:20 and nothing moved. A job at M160 P2, which the service refuses, is held, and is aborted from there. A job left running by a failed check is aborted before the put-back. Proof: on the bench reference, with the controller, forgectrl, and forgeext of this change bind-mounted, exthost.mcode first FAILED and found the controller announcing the M-code before the kernel had played the last move (the driver now waits for the kernel to go idle), then PASS; exthost.service, motion.job, exthost.page-call, exthost.motion-jog, motion.port-jog, and exthost.package-routes PASS on the same daemons. mcode_test.py ALL PASS on the host build. forgetest's unit tests pass, and the coverage lint passes with --enforce. |
||
|
|
4e47b7fbf1 |
forgetest: exthost.catalog, and the coverage lint no longer allows an advisory away
exthost.catalog (suite/extcat.py, its own module): GET /ext/catalog answers the index the host keeps and the one address it is fetched from. On a scratch root under /tmp, with a throwaway key standing in for the OpenGlow extension key, the machine's own forgeext keeps an index signed with it, and the author key it names for one id makes a package of that id read as community and endorsed, where before it was unverified; the same key on another id counts for nothing. On the machine's own root that index is refused in words, a package handed over as an index is refused by the product gate, and the index kept is left as it was. The relay refuses an id with no such form (400) and one the kept index does not list (404, or 409 with none kept) before anything is fetched, and a refresh from the fixed address keeps OpenGlow's index when one is published there and is 502 in curl's words when none is, the kept index left as it was. Nothing is left in the staging directory. The coverage lint had a gap: coverage_report() let the allowlist's docs/** and **/*.md take out a path the BEHAVIORAL list keeps in every fingerprint, so the four first-run advisory documents were covered by no test and the lint passed. A change to the privacy advisory would have invalidated nothing. A behavioral path is now never allowed away, and setup.advisories-rehash, which accepts every first-run document at its current hash, covers the four. Proof: on the bench reference, with forgectrl 848ccc1 and forgeext a64b933 bind-mounted and the privacy document accepted again at its new hash with the fixture's press, exthost.catalog PASS (the refresh was 502: GitHub answered 404, nothing is published at the address yet), and setup.advisories-rehash PASS with the rest of the campaign. The lint's new unit test reports the uncovered advisory, and the old reading (the override ignored) reports nothing for it, which is the gap. forgetest's unit tests pass (452), and the coverage lint passes with --enforce. |
||
|
|
675fc11cb1 |
forgetest: events.button-telemetry and exthost.lifecycle
Two new tests, each in its own module so that no other test's fingerprint moves. events.button-telemetry (suite/evmore.py): a stream opened at forgectrl's read-only listener, from a loopback source of its own, gets a telemetry.tick within 14 s with exactly its fields (phase, verdict, fire_ok, down_c, up_c, state, lid). With the machine idle and nothing waiting for the button, one press (the fixture's, or the operator's) arrives as pressed true and then false, in that order. That a press the machine is waiting for is no event, and the update and setup edges, are events_test's. exthost.lifecycle (suite/extlife.py): a package that follows its feed is running when extensions are turned off. It reads ext.shutdown with the host's reason and writes it to its data directory before its process ends, the host stops it a second later, and nothing is frozen in that second. Its covers name forgeext's run, machine (the armed question asked during the grace), evfeed, and api. Proof: on the bench reference, with forgectrl a9c45fc and forgeext dbb00e5 bind-mounted, both PASS. exthost.lifecycle FAILed on the host before its grace fix (the service frozen for want of an armed reading) and PASSed after it. forgetest's unit tests pass (451), and the coverage lint passes with --enforce. |
||
|
|
71b44598eb |
test_cloud_suite: the quiet wait's deadline is no longer the test
The cloud suite's host tests replay a print's log from threads, and the suite's quiet wait gave up after 3 s (QUIET_TIMEOUT_S in setUp). On a loaded host the last replayed lines landed after that, and a test failed with "still running service moves after 3 s" instead of its own finding: about one run of the module in five, a different test each time, alone as well as under the full suite. The deadline is now 15 s. It is a deadline and not a wait: a quiet machine is seen at once, so a passing run is no slower. The one test that waits the deadline out on purpose (a motion that never goes idle) sets 3 s for itself; tearDown puts the module's value back. Proof: before, test_cloud_suite failed 2 of 10 runs by itself, every failure the quiet deadline (test_lid_during_button_wait_on_the_bench_excerpt, test_pause_resume_fails_without_the_retraced_restart, test_mode_switch_fails_when_gfhome_never_saw_the_head_move, test_pause_resume_fails_when_the_kernel_refuses_the_resume). After, 10 of 10. forgetest's whole unit suite: 451 tests OK, 4 skipped, in 492 s against 491 s before. A host-test change: no catalog consequence. |
||
|
|
fab8920cd9 |
exthost.operator-destinations: the operator names where a package may connect
A new catalog test in a module of its own (suite/extdest.py). It installs the reference package's id and key asking for net.outbound.operator, with a service that reports at every start what GET /v0/self says it may reach and what one dial to the machine's DNS resolver on TCP port 53 does, and one to the port above it. The resolver is an address every network gives the machine, that is not the machine, and that answers; named by address, it gives the service no resolver of its own. It holds: before anything is named, no destination and the dial refused inside the sandbox (EACCES), and no rule for it in the package's chain; the machine's LAN address and loopback refused in the host's words, a destination out of form 400, an unnamed removal refused; named through POST /ext/dest, the service started again with the destination, connected, still refused one port up, and the chain carrying the rule, with no crash; taken away, started again and refused again, the rule gone. A resolver that takes no TCP connection even from root fails the test in words rather than passing it on nothing. covers: forgeext src/install.*, state.*, run.*, super.*, api.*, caps.*, manifest.*, netrules.*, main.c; forgectrl src/extpkg.*, main.c. Proof: on the bench reference (image 20260922225653, with forgeext 28f829e and forgectrl e2d0d05 cross-built and bind-mounted, and this suite bind-mounted over the installed one), exthost.operator-destinations PASS in 22 s, with exthost.service, exthost.page-call, exthost.ui-delivery, exthost.package-routes, exthost.events, and exthost.panel-install PASS in the same campaign. The first run of it failed in its own reading of /etc/resolv.conf (bytes against text); the fix is in the test's module alone, and the PASS is on the fixed module. |
||
|
|
861f5597e6 |
exthost.page-call: a package's page asks its own service
A new catalog test, in a module of its own (suite/extcall.py) so that no other test's fingerprint moves. It installs the reference package's id and key with a page and a service of its own, whose service reports what it was handed and answers its page's calls on the listening end the host gave it. It holds: FFX_CALL_FD=4 with descriptor 4 a listening socket and 3 and 5 closed; the socket root's 0600 in root's 0700 directory; POST /ext/call relaying a POST and a GET, the service's own refusal passed through as its status and words, and a service asking the API while it answers; five calls out of form refused, 400 by the relay or 409 in the host's words; forgeext call answering alike; and a disabled package refused in words, its service stopped and its socket's name gone, then answering again once enabled. The package, the key, the setting, and the setup record are put back as found, and no socket name is left. covers: forgeext src/call.*, src/run.*, src/sandbox.*, src/main.c; forgectrl src/extpkg.*, src/main.c. Proof: on the bench reference (image 20260922225653, with forgeext 5c13f1c and forgectrl 2cf6b12 cross-built and bind-mounted, and this suite bind-mounted over the installed one), exthost.page-call PASS in 16 s, and exthost.service, exthost.ui-delivery, and exthost.package-routes PASS in the same campaign. forgetest's unit tests: 451, one failure in test_cloud_suite, whose replayed prints flake on their timing alone as well (1 of 3 runs of that module by itself, in a different test each time) and touch nothing of this change. |
||
|
|
b7e7c0fb2c |
Manifest: the advisories are behavior, and forgeext's kit and packages are not
forgectrl embeds its advisory documents in the binary, serves them, and records the operator's consent to one by its hash, so an edited advisory is a changed consent. They are Markdown under docs/, which the non-behavioral list takes out of every fingerprint, and that left setup.extensions-consent's covers entry for docs/advisories/extensions.md selecting nothing: the enforced coverage lint fails on it (exit 1 on the dev image's manifest of 20260922225653), and an edited advisory moved no fingerprint at all. A BEHAVIORAL list now keeps forgectrl's docs/advisories/** in, ahead of the non-behavioral one. forgeext's recipe installs the binary and its init script and nothing else, so packages/ (the official packages, which carry their own acceptance artifact), sdk/ (the author's kit), template/ and tools/ are non-behavioral for the image: without that, an edit to the alignment page or the kit would make every exthost test stale on the next image. Proof: test_manifest passes its 25 cases, the new ones among them; the enforced coverage lint on the dev image's manifest of 20260922225653 exits 0 with no empty entry and nothing uncovered, where it exited 1 before. The whole forgetest suite ran its 451 tests; one, test_cloud_suite's test_pause_resume_passes_on_the_machines_lines, errored under the suite's load and passes 16 runs of 16 alone, at HEAD and on this tree alike: it replays a print against timed hooks. |
||
|
|
2f51e38178 |
exthost.service: a package's frame with the lamp named
The reference service takes two more lid-camera frames through its API socket, with the camera's lamp at 0 and at 1023, and one with a lamp past its range. The test holds both named frames to being JPEGs, the lit one the larger (the lamp reached the camera and changed the picture), and the out-of-range one to a 400 that names the lamp. The service's text is shared by every exthost test, and every one of them already covers forgeext's api.c and run.c, which this change moves, so no test is made stale that was not stale already. Proof: on the bench reference, image 20260922225653 with forgeext cd8b471b and forgectrl 9c9ee557 bind-mounted, exthost.platform and exthost.service PASS: 51,561 bytes at lamp 0, 109,850 at lamp 1023, and lamp 5000 refused. |