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.
forgetest - the ForgeFIRM release acceptance tool
The daemon behind http://<machine>:8090/ on the dev image: runs the
acceptance catalog against the machine, keeps the append-only result log,
decides which results still apply to the image that is running, exports
the release artifact scripts/release.sh gates on, and serves the bench
diagnostics page. The contract - catalog, campaigns, fingerprints,
inheritance, the gate, the coverage rule - is
the Acceptance page of the documentation site.
Run the host tests
cd forgetest
python3 -m unittest discover -s tests -v
Run the daemon on a workstation (against a mock or a manifest file)
FORGETEST_DATA=/tmp/ft FORGETEST_MANIFEST=../tree-manifest.json \
FORGECTRL_URL=http://<machine> python3 -m forgetest --port 8090
scripts/manifest-from-tree.py produces tree-manifest.json from the recipe
pins; the coverage lint is python3 -m forgetest.coverage --manifest ....
Environment
| Variable | Default | Purpose |
|---|---|---|
FORGETEST_DATA |
/data/forgetest |
results.jsonl, bench.jsonl, token, export/ |
FORGETEST_MANIFEST |
/etc/forgefirm-manifest.json |
the image manifest |
FORGETEST_PORT, FORGETEST_HOST |
8090, 0.0.0.0 | listener |
FORGETEST_BENCH_DIR |
/usr/share/forgetest/bench |
the installed bench scripts |
FORGETEST_BENCH_DATA |
<FORGETEST_DATA>/bench |
passed to bench tools: where they keep their data files (with GF_HOST=127.0.0.1 and the panel token in GF_TOKEN) |
FORGETEST_MARKER |
/run/forgetest.active |
takeover marker |
FORGECTRL_URL, FORGECTRL_TOKEN_FILE |
http://127.0.0.1, /data/forgefirm/panel.token |
forgectrl client (HTTP; the token authorizes writes from the board) |
FORGECTRL_TLS_URL |
https://127.0.0.1 |
forgectrl over HTTPS (self-signed, unverified), for the login test |
GF_SYSFS_ROOT |
/sys/glowforge/ |
kernel module sysfs |
GRBL_HOST, GRBL_PORT |
127.0.0.1, 23 | Grbl TCP |
Adding a test
Register it in the subsystem module under forgetest/suite/ with
@test(...): id subsystem.name, kind, hardware, mode (the controller
mode the test needs; the runner switches to it first), covers,
requires, always, steps. The body gets a Context (log, check, fail,
prompt, confirm, instruct, sleep, evidence, forgectrl, sysfs,
grbl, takeover). Return normally for PASS, raise runner.Failed for
FAIL. Then run the unit tests and the coverage lint.