diff --git a/docs/BRINGUP.md b/docs/BRINGUP.md index 790745c..916a3ad 100644 --- a/docs/BRINGUP.md +++ b/docs/BRINGUP.md @@ -48,11 +48,13 @@ hardware-validated.** modes (cancel-and-return on a lid or interlock open, button pause/resume), bench-validated 2026-08-17. - **Releases are gated by the acceptance tool** (`forgetest`, dev image only): - a 35-test catalog, domain-scoped inheritance, an always-required safety core, - and a release gate that reads the exported artifact. The last full campaign - reached 26 of 26 on the then-current catalog; **no release is cut yet.** + a 42-test catalog, domain-scoped inheritance, an always-required safety core, + and a release gate that reads the exported artifact. The full campaign on + dev image `20260821181036` (the first built on the `-pin.inc` + layout) satisfied 42 of 42 and its export authorizes a release; + **no release is cut yet.** -Current bench state: dev image `20260817124714`, the board resting on the SD +Current bench state: dev image `20260821181036`, the board resting on the SD dev image (eMMC slot 1 = factory 2024, slot 2 = ForgeFIRM v0.1.0, archives in `/data/forgefirm/archive`). @@ -571,12 +573,13 @@ The release acceptance tool — catalog, campaigns, domain fingerprints, inheritance, the always-required core, invalidate-all, the release gate and the coverage currency rule — is specified in `docs/ACCEPTANCE.md`; the tool lives in `forgetest/` and ships only on the dev image (`/etc/init.d/forgetest`, HTTP -:8090). It is **bench-validated**: a full catalog run reached 26 of 26 on the -then-current catalog, and `scripts/acceptance-gate.py` authorized that image's -own manifest from the export. That was an exercise of the release mechanism, -not a release. +:8090). It is **bench-validated**: the full campaign on dev image +`20260821181036` satisfied 42 of 42 (12 run on that image, 30 inherited +under the domain model from the day's earlier dev images) and the export reads "Release authorized: +YES" for that image's manifest. That authorizes a release; it is not one +until `releases/v/acceptance.json` is committed. -- **Catalog: 35 tests** in `forgetest/forgetest/suite/`, every one a port of a +- **Catalog: 42 tests** in `forgetest/forgetest/suite/`, every one a port of a proven bench drill or a bench-verified check — the always-required core (`image.health`, `kernel.latch-locked-idle`, `kernel.k1-k2`, `kernel.fire-line`), `forgectrl.*`, `logs.*`, `update.*`, `motion.*` @@ -983,31 +986,36 @@ Open items only. Anything closed is in `CAMPAIGN-LOG.md`. the carrier a factory-session capture settled: the `type:"progress"` frame that is the periodic settings report, every 30 s and at every phase change, divided by the job's own length rather than by the kernel byte counter that - climbs all job long under a live feed. The remaining gaps are tracked in - `python3-gfhardware/forgefirm-app/docs/CLOUD.md` "Outstanding items": a - live job longer than the ring (built and covered, never yet run from the - service), the memory guards against a real ceiling rather than a reasoned - one, a print watched from the app to see the bar actually move, - packaged-path boot with - `controller_mode = cloud`, the lid-flash LED, and driving the park and the two lifecycle - periods off the header (`CFrh`, `CCwp`, `CCrp`) once a capture confirms - what they mean; the machine warms up and rests on the factory's measured - timings meanwhile, and logs those keys on every job. (`CCbp`/`CCbt`, read - earlier as the pause constants, are refuted: the factory's tag table marks - them report-only, so they cannot appear in a pulse header at all.) The - four actions the service has never been seen to send are no longer open: - they were read out of the factory binary instead. `user_image` is a lid - capture and is implemented; `update_check`, `factory_reset` and - `head_firmware_update` each hand off to a program this machine does not - have (a factory updater, a reset script, a head firmware push), so each - is answered on the wire and none is performed, and `focus` is ignored - exactly as the factory ignores it. Not - inducible from the bench: the - cancel-with-a-rejected-`settings`-action case, a malformed frame (needs a - MITM), a body past the memory guard (the service has no such job to send), - and a wedged feed (a healthy machine will not stall on request). The pulse header's unenforced safety envelope is item 19: it is - listed separately because the enforcement lands in the cooling engine and - has to hold in GRBL mode too, not only under the cloud client. + climbs all job long under a live feed. The `cloud.*` acceptance tests + cover all of it on the bench, a print longer than the ring fed from the + live service included, and the app has been watched reporting a print's + progress. `gfcloud.init` autostart with `controller_mode = cloud` is + validated on a flashed image, and the lid flash follows the action's + `LCfl`. What is left is tracked in + `python3-gfhardware/forgefirm-app/docs/CLOUD.md` "Outstanding items" and + is short: whether the service accepts an 8 MP machine's larger images (no + HD machine has been on the bench), and the pulse header's unenforced + safety envelope, which is item 19, listed separately because the + enforcement lands in the cooling engine and has to hold in GRBL mode too, + not only under the cloud client. The memory guards + (`pulse_reject_threshold_bytes`, 128 MiB of compressed body) stay + reasoned rather than measured, by decision: nothing the service sends + comes near them, and every job logs the body and program sizes the + guards are reasoned from. The lifecycle keys (`CFrh`, `CCwp`, `CCrp`, + `CCup`) are settled as inert, in the factory too, so the configured + warm-up and rest on the factory's measured timings are the model, and + `CCbp`/`CCbt` are report-only tags that cannot appear in a header. The + four actions the service has never been seen to send were read out of + the factory binary: `user_image` is a lid capture and is implemented; + `update_check`, `factory_reset` and `head_firmware_update` each hand off + to a program this machine does not have (a factory updater, a reset + script, a head firmware push), so each is answered on the wire and none + is performed, and `focus` is ignored exactly as the factory ignores it. + Declined outright: SPKI pinning, emulator full-session parity, and the + factory's ten-event pause phase machine. Not inducible from the bench: + the cancel-with-a-rejected-`settings`-action case, a malformed frame + (needs a MITM), a body past the memory guard (the service has no such job + to send), and a wedged feed (a healthy machine will not stall on request). 8. **Shared machine services — remaining polish.** None of it blocking: - **Diagnostics as engine modes.** The flow tools still drive the thermal hardware themselves while the engine suspends its writes; the check @@ -1046,20 +1054,17 @@ Open items only. Anything closed is in `CAMPAIGN-LOG.md`. matter. Only if it still recurs, cap the bus with `max-frequency = <25000000>` on `&usdhc1` (halves Wi-Fi throughput — last resort; the factory ran 50 MHz on these pads). -12. **Release acceptance follow-through.** A full campaign is owed on the first - image built with the `-pin.inc` layout: that image is a platform - change against everything recorded so far, unavoidably, and only from then - on does a component pin bump re-require just the tests covering that - component. It now also carries the 32 MiB pulse ring (DT pool plus the - module default, batched into that flash), which is a platform change in its - own right and the first bench sighting of the larger ring: `image.health` - reads the pool and `ring_mb` back, and a preloaded cloud job past the old - 28-minute cap is the capability proof. `20260817124714` was built after the pin files landed and the - parity tests were driven on it, but no full-campaign export is recorded for - it — confirm on the bench and write the record here. Also still owed: - exercising the ported bench tools from the page (they are registered and - unit-tested, not yet driven from the page), and the first release, which - runs the campaign and commits `releases/v/acceptance.json`. +12. **Release acceptance follow-through.** The full campaign on the first + image built with the `-pin.inc` layout is done: dev image + `20260821181036`, 42 of 42, release authorized (the export is on the + board at `/data/forgetest/export/`). From here a component pin bump + re-requires only the tests covering that component. That image also + carries the 32 MiB pulse ring (DT pool plus the module default): + `image.health` reads the pool and `ring_mb` back, and + `cloud.oversize-stream` fed a print longer than the ring from the live + service. Still owed: exercising the ported bench tools from the page + (they are registered and unit-tested, not yet driven from the page), and + the first release, which commits `releases/v/acceptance.json`. Catalog gaps left from the tool's own plan: `cooling.confirm-escalate` and `cooling.fire-gate-blocks-arm` are not ported (both need the pump switched by hand mid-run, so they are bench-tab material first), and whether @@ -1304,8 +1309,10 @@ Open items only. Anything closed is in `CAMPAIGN-LOG.md`. is not header-legal is rejected too. The service fills the safety-relevant ones with real values per job rather than echoing back what the machine reported. ForgeFIRM applies thirteen tags (the three run fan duties, - `STfr`, the X/Y current, decay and microstep set, `ZSmd`) and drops the - rest. Nineteen of the mandatory ones are among the dropped. + `STfr`, the X/Y current, decay and microstep set, `ZSmd`), refuses the + file on two (`MCsn`, the serial it is locked to, and `PDfm`, the pulse + data format, both checked before a byte reaches the ring) and drops the + rest. Seventeen of the mandatory ones are among the dropped. Nothing here can put energy where it was not commanded: the hardware chain is the emission boundary and no header field touches it, and forgectrl runs @@ -1415,10 +1422,6 @@ Open items only. Anything closed is in `CAMPAIGN-LOG.md`. `beam_detect_abort` aborts it, and a `beam_detect_report` is uploaded afterward at whatever severity the report-upload condition selects. See also item 15, which is circling the same hardware from the other side. - - **`MCsn` and `PDfm` are not checked.** The factory refuses a pulse file - whose serial does not match the machine, and refuses a pulse-data format - it does not recognize. ForgeFIRM runs both. These are cheap: two - comparisons at the top of the job path, and a refusal is a clean abort. Deciding what to adopt is part of the work, not a foregone conclusion. A limit that arrives from a remote service is a limit that service can raise, diff --git a/docs/CAMPAIGN-LOG.md b/docs/CAMPAIGN-LOG.md index 2a67b46..58b9fcf 100644 --- a/docs/CAMPAIGN-LOG.md +++ b/docs/CAMPAIGN-LOG.md @@ -3200,6 +3200,16 @@ controller's message. An operator watching the head come back has no such reference, and fifteen millimeters on a forty millimeter square reads as "back". The test stays as it is. +## 2026-08-21: the first full campaign on a pin-file image + +Completed. Dev image `20260821181036`, the first built on the +`-pin.inc` layout: 42 of 42 satisfied (12 run on that image, 30 +inherited under the domain model from the day's earlier dev images), all +eight `cloud.*` tests run on that image, and the export reads "Release authorized: +YES" for that image's manifest (campaign `c-20260821182204-dc01`, exported +2026-08-21T18:40:30Z, artifact sha256 `6f17f690...43273055`). No release is +cut from it. + ## Superseded status notes ### Shared machine services — remaining polish, as listed 2026-08-13 diff --git a/meta-forgefirm/recipes-forgefirm/forgefirm-app/forgefirm-app-pin.inc b/meta-forgefirm/recipes-forgefirm/forgefirm-app/forgefirm-app-pin.inc index f4065d5..85bf286 100644 --- a/meta-forgefirm/recipes-forgefirm/forgefirm-app/forgefirm-app-pin.inc +++ b/meta-forgefirm/recipes-forgefirm/forgefirm-app/forgefirm-app-pin.inc @@ -4,9 +4,9 @@ # because the component entry already identifies the pinned source # (forgefirm-image-manifest.bbclass). The python3-gfhardware recipe in # meta-glowforge-bsp pins the same repository; move both together. -SRCREV = "f188e4098611663f0649b502f9f31ccc7fd4a4f7" +SRCREV = "b82d37984b16ee426f24eaf980346b3692e9af5d" # Bump PV with every SRCREV move: the hash-derived package version is not # monotonic on its own and buildhistory QA fails the build when it sorts # backwards. -PV = "0.1.12+git" +PV = "0.1.13+git"