The dev image installs the bench tools under /usr/share/forgetest/bench/,
so flow_matrix.py and flow_warm_validate.py invoke the board-side sampler
from there instead of a hand-copied /data/flow_sampler.py; the README
follows. Bench-tool path change only, no machine behavior involved.
A stdlib-only daemon on the dev image (HTTP :8090) that runs the acceptance
catalog against the machine from a self-contained page, keeps the append-only
result log under /data/forgetest, and exports the release artifact the gate
reads. Tests declare kind (auto / operator / live), hardware (api / takeover),
coverage globs, prerequisites, and core membership; a test's domain
fingerprint is the hash of the manifest files its globs select plus the
platform and its own implementation, so a PASS stays valid exactly while
nothing it covers changed. Campaign rules: a FAIL ends the campaign, the core
(image health, kernel latch and drills, one live emission witness) is never
inherited, invalidate-all forces a full campaign, no SKIP. Live tests need the
operator acknowledgment and the physical arm press through the controller;
takeover tests stop forgectrl for the duration with a crash-recoverable
marker; the tool never touches the laser latch.
Catalog v1: 24 tests ported from the proven bench drills with their recorded
pass criteria (image, kernel K1-K3 and fire A/B/U, forgectrl API and logs,
motion incl. dead-man, cooling, live laser, camera, update, cloud). The bench
tab lists every scripts/bench tool and runs the board-side ones as
subprocesses (takeover tools wrapped). 44 host unit tests, including the gate
verification fixtures. Installed only by forgefirm-image-dev, with the bench
scripts under /usr/share/forgetest/bench.
rsyslog replaces busybox syslogd/klogd (VIRTUAL-RUNTIME_base-utils-syslog,
trimmed PACKAGECONFIG) and becomes the only log writer: the appended
/etc/rsyslog.conf sets the inputs and the ff_line format and includes
the per-logger rules that `forgectrl --render-syslog` renders from the
machine settings at boot. forgefirm-logrotate becomes forgefirm-logging:
render before rsyslog starts (S19), sweep the pre-syslog log files into
/data/forgefirm/legacy-logs once, and rotate the tree at boot and hourly
by rename + HUP instead of copytruncate. Pins bumped to the pushed
forgectrl (syslog emitter, Logs tab, export), grblHAL-glowforge (syslog
emitter) and python3-gfhardware apps (syslog handlers, capture dir);
the CI harnesses set FFLOG_STDERR=1 so failure diagnostics keep the
controller's log lines. BRINGUP carries the bench validation checklist
(Next work item 14); this is an image change and rides the next flash.
- platform_drills.py (on the board, forgectrl stopped): dead-man trip
readback, rmmod/modprobe with concurrent attr reads, decay/microstep
readback, LED sequence.
- live_fire_drills.py: ircut (S/F selectable characterization job),
expstop (armed job + POST /controller/stop, controller left stopped),
ctrlstart (separate, operator-approved resume); the token header is sent
in exact case.
- laser_lifecycle_test.py: sigterm-mid-job - SIGTERM during an armed job
must stop it, relock the latch and exit promptly.
The host-side coolant and fan tools call the ssh on PATH, or the client
named by GF_SSH (a wrapper such as a WSL distro's ssh), instead of a
fixed wrapper. The bench README lists every committed tool and data
file, and explains how the coolant-flow fire-gate threshold was
derived from the committed flow-matrix data and how to re-run that
derivation on another machine.
Pulls in the coarse-pacing fix for parked wait-for-operator states (a
machine left in Door or Hold no longer pins ~28% of the core), plus
P10's grblHAL CI/tests and the mlockall-root-only change. Fetch-verified.
Adds pacing_test.py (dry hold/resume + CPU measurement) and records the
diagnosis and bench validation in BRINGUP.
Phase 5 A-1 emission witness and A-5 HV telemetry pass on live burns;
A-2 lid-IR characterized as a weak signal at 40% (gate left watch-only);
pgood confirmed unusable as a witness on this PSU. Phase 4 X-3 (0.1s
job-based disarm on M2) and G-10 (disarm counts down in Hold) pass.
Adds live_fire_drills.py (arm-lifecycle sampler over TCP + HTTP).
gate_a_kernel_drills.py stages the three remaining GATE A kernel
drills (controlled-stop deceleration floor, resume waypoint with the
latch locked, mid-ramp latch unlock) with software witnesses and the
PSU-connector scope point. lid_ir_ambient_baseline.csv is the
fire-watch ambient anchor (600 samples, lid closed, machine idle);
BRINGUP records the baseline statistics and the idle verification of
the emission/fire/HV evidence plumbing.
Four sessions against the reported messages: arm once per job with
M5/M3 persistence and the M2 close (grace pushed beyond the horizon so
only the program-end path can disarm); a displaced sender must re-arm;
the disarm grace counts down in Hold; a blocking cooling verdict
refuses the arm. Test-the-test proven: a build with the job-based
window reverted fails at the first discriminating assertion. Wired
into the controller repo CI alongside the emission harness.
- Controllers stop at K80, before forgectrl at K90: runlevel 0/6 no
longer tears down the cooling engine, fire gates, and broker while a
controller may still be executing a job.
- The grblhal/gfcloud init scripts are real emergency levers: stop
routes through the supervisor (POST /controller/stop - a bare pkill
was safed and respawned seconds later), start resumes supervision,
status exists, and the pkill fallback matches full executable paths
instead of truncated names or bare substrings.
- slotmigrate: the partition grow gets the same 2048-sector tolerance
as the filesystem branch (an exact compare rewrote the MBR at S02 on
every boot on disks where the grow cannot land on the last sector),
verifies it made progress, and the resize2fs retry is bounded at
three attempts with the counter kept on p3 itself.
- Installer: archive product/platform are verified after the signature,
and a validly signed OLDER release now requires an explicit yes
instead of installing as a silent downgrade. All predictable /tmp
paths in the installer and ffboot are mktemp now.
- release.sh rejects multiple positional versions (the last one used to
win silently) and a release without factory-era verification dies
unless explicitly bypassed; mkfw.sh refuses to pack when the public
key for the post-sign self-check is missing.
- forgefirm-logrotate: size-capped rotation (boot + hourly) for the
/data logs - a full /data breaks settings, update staging, and the
controllers own writes.
- Bench build scripts derive every path from their own location or
FF_SRC_TOP/FF_BUILD_TOP and log to mktemp files.
- LIGHTBURN.md: mandatory "Before you cut" safety section; the
walkthrough now reflects the firing machine (dry runs need the
layer output off or M5; live first-cut instructions); the homing
entry documents homing_mode and the gfcloud method; the machine
address is a placeholder.
- README.md: condensed safety section linking the full text and the
regulatory notes.
- INSTALL.md: "Regulatory and legal" section ahead of the install
steps; routine updates route through the panel updater rather than
the installer.
- BRINGUP.md: the release signing key is described as held offline
(no on-disk path); bench address and credential notes removed;
Next-work item 7 corrected (the installer embeds the production
release key); status entry for audit remediation Phases 0-1; the
GATE A kernel drills join the pending image-flash checklist.
- bench scripts: the target host comes from GF_HOST (or argv) instead
of a hardcoded address.
- laser_stream_test.py: per-session controller runs with a hermetic
cooling-verdict publisher; new assertions that every stream
terminates with FIRE clear (including M3 held to stream end) and
that no FIRE bit rides a zero-step gap; a cycle-churn session
exercises the stop/start seams.
Audit findings D-1, D-2, D-3, D-5, D-10, D-12, B-10, and the harness
half of D-4/G-1.
BRINGUP item 2: grbl-mode laser software implemented + bench-verified
without fire (record in the gate list); first light pending.
LIGHTBURN.md: arming/button-press operation, S-max 1000, fire gates.
scripts/bench/laser_stream_test.py: host-side stream-dump contract
verification against the null-sink build.
flow_confirm_drill.py walks the driver's suspicion/confirmation state
machine through every verdict with real pump-off transients in one M8
session; flow_escalate_drill.py exercises the starved-re-check
escalation against a short GFCOOL_CONFIRM_MAX_S. BRINGUP records the
triage resolution (the 2026-08-03 faults were a real transient
stagnation, probable pump airlock - the check was right), the slug/
circulation measurements, and the new check semantics.
The head lis2hh12 (i2c-3 0x1e, direct-I2C at ~530 Hz - st_accel sysfs one-shots are ~6 Hz and the kernel has no IIO triggers) sees rail contact as a 20-40x jolt over the creep baseline within ~4 ms. bump_seek.py: 3/3 detected hits, zero false positives over ~180 mm of creep, jog-cancel stop and back-off. BRINGUP carries the full record and the driver integration design.
Sources live at github.com/ScottW514/forgectrl (history extracted, layout src/ + init/); the recipe pins SRCREV and installs the init script from the repo. Adds the build-forgectrl.sh bench cross-compile script.
Adds the design matrix and its supporting tools, and records in BRINGUP
what the 60-run matrix overturned: sub-40-percent duty mimics flow
(three of five dead-pump trials looked healthier than a working pump),
the operating point and threshold now rest on 25 pooled observations,
periodic re-checks are thermally free with the fans running, and the
settle gate closes a bench-proven miss. Also records what is NOT yet
validated - warm-loop baselines and behaviour under laser heating - as
first-light commissioning items.
Records why the temperature formula changed (and that everything
derived from the old one had to be re-derived), the flow
characterization data at both heater duties, the false negative that
killed the dT-threshold design, and the downstream-rise check that
replaced it. Adds flow_characterize.py to the bench kit.
The UAPI raw->Celsius formula is an explicitly unverified best guess and
every absolute coolant threshold inherits it. temp_calibrate.py records
reference points (measured temperature paired with averaged raw ADC
readings) and least-squares fits the real line per sensor, printing it
against the guess with a difference table. Modes: watch / point / fit.
BRINGUP records the gate as complete with tach-verified results;
LIGHTBURN.md documents the per-layer Air Assist toggle driving the
cut-profile ventilation; fan_test.py joins the bench kit.
Scope on GPIO2_IO30 (the SoC FIRE drive): two 2.000 s FIRE windows,
the second terminated only by the SDMA end-of-data backstop. Measured
2.0000 s exactly with clean edges on BOTH termination paths - normal
completion and true underrun (streaming=1, kernel underrun state
reached and acked; fire_test.py gains the U mode). The backstop drops
FIRE within one tick regardless of how the stream dies.
With the waveform, stream-path, latch, topology, and interlock-
semantics results from earlier today, every standing hardware
verification gate for live fire is now passed; what remains is the
laser-milestone software and a chain-armed first-light procedure.
fire_test.py phases A/B with the scope on the PSU LASER_ON pin, power
byte 0 throughout, HV unpowered, operator-executed unlock: latch LOCKED
severs FIRE entirely (pin flat + kernel laser_enable 0 under 40k
streamed fire bits); latch unlocked with the chain unarmed shows FIRE
live kernel-side while the PSU pin stays flat - the factory board
gates LASER_ON behind OK_2_FIRE exactly like the OpenGlow AND design.
interlock_circuit semantics pinned by the 13-to-7 transition: b0
LASER_ON monitor active low, b1 FIRE active high, b3 latch 1=locked.
The 1-tick FIRE drop measurement is deferred (SoC net inaccessible;
PSU pin needs an armed chain) - documented with both closure paths.
Power-bytes-only program played by the pulse engine with the scope on
LASER_PWM: full duty staircase observed, run-start 100 percent duty
reset confirmed on the pin, consecutive-power-byte drop confirmed
(second byte of a back-to-back pair discarded), and duty persists
after end-of-data - the laser-off guarantee rests entirely on FIRE.
Zero motion (motor_lock=15 + no step bytes; position counters pinned)
and the laser chain silent throughout. Adds pwm_stream_test.py to the
bench kit and records the FIRE/OK_2_FIRE/LASER_ON/HV_EN naming from
the OpenGlow LASER SAFING sheet.
Direct PWMSAR duty steps with the machine in the locked state
(controller stopped, cnc disabled so steppers are unpowered, laser
latch locked, lid closed; laser_on_sampled stayed 0 throughout).
Measured on the pin: 25.0 us period / 40 kHz stable across the full
duty range; 50/25/75 percent confirmed visually; low end
cursor-measured 6.4 vs 6.3 percent commanded (PWMSAR=8), clean pulse.
Matches the register-level audit numbers (divider 13 x 127 counts).
Adds pwm_sweep.py / pwm_hold.py to the bench kit and records the two
remaining pre-live-fire gates (1-tick laser drop at underrun via the
stream path with FIRE observable under a locked latch; interlock
readback semantics) in BRINGUP.
The step backend now lives in github.com/ScottW514/grblHAL-glowforge
(grblHAL driver convention: core submodule, driver.c HAL, board header).
Add build-glowforge.sh, update the BRINGUP runbook (no -t throttle,
EEPROM path, kill-before-scp, producer stats line, reset/disconnect
semantics), and teach bench_m2.py to parse the F: status field (no
spindle registered = no FS: field). Settings-write crash fix is
upstream as grblHAL/core PR 999.
Extract the factory motion profile from the _RESOURCES pulse streams
(new puls_profile.py): 700/590 mm/s2 X/Y accel on v2.6.0 firmware,
202 mm/s travel peak at the 28160 Hz travel tick, PIC currents X 135/33
and Y 22/5 run/hold, decay mode 1. Add the bench_m2.py round-trip jog +
feed-hold suite (all green 2026-08-02: sustained 200 mm/s, exact
returns, clean hold/resume, zero underruns) and update BRINGUP.md:
board moved to a fixed lease at 172.16.1.97, analog config now applied
by the sink itself, $RST=$ note for stale stored settings.
BRINGUP.md is the cold-start reference: project status (audit phases
0-5 complete, Phase 6 spike achieved - first grblHAL-commanded motion
2026-07-26), bench/board access, build+deploy procedures, the step
backend runbook incl. the required analog machine config, the measured
hardware facts bank, and the ordered next-work list. scripts/bench/
preserves the hardware-verification tools (underrun feeder, end-of-
data protocol bench, PWM register check, cross-build scripts).