/mode reports controller=running at the spawn, so the reference dump
raced grblHAL's init writes and captured the supervisor's motion-probe
values (motor_lock 0, step_freq 10000, y_mode at the module default)
instead of the resting state. boot_reference() now waits for the
controller's markers (step_freq/motor_lock/y_mode at their fixed values,
bounded 20 s, GRBL mode only) plus a 1 s settle before dumping, retakes
a saved reference that shows the pre-config state while the boot is
still fresh, and marks it otherwise. Tests cover the wait, its timeout,
the non-GRBL no-op, the pre-config recognition and the retake.
laser_lifecycle_test.py: the button toggle (press = Hold, press = Run;
the arming press is not a pause), lid and interlock cancel mid-job with
the return to the job start and no alarm, and lid_policy=hold.
Acceptance catalog: motion.button-hold-resume, motion.lid-cancel-home
(operator: travel job, lid open -> cancel message, banner, no alarm,
autonomous return, Idle at the start), laser.lid-cancel-mid-fire (live:
emission stops in hardware, cancelled, returned, armed=false, latch
locked, hardware button latch SET). covers name the switch and laser
sources explicitly.
LIGHTBURN.md: what the lid, Stop and the button now do; SAFETY.md: the
door policy and the button as a software layer; BRINGUP.md: item 16
records the whole parity change (host-proven, bench validation pending)
and items 4/12 point at it.
The remaining bench diagnostics run from forgetest's #bench tab. The
tools that also run from a LAN host share scripts/bench/gfbench.py:
GF_HOST names a remote machine (host mode, sysfs through ssh, Grbl and
forgectrl over the LAN); unset, the tool runs on the board itself
(local mode, sysfs directly, everything on 127.0.0.1), which is how the
page runs them - with GF_HOST=127.0.0.1, the panel token in GF_TOKEN
and their data files under <data>/bench/ (FORGETEST_BENCH_DATA). The
helper also reads a machine setting from forgectrl, or from the settings
file on the board while forgectrl is stopped.
Ported: pwm_sweep / pwm_hold (scope = a takeover; the latch relocked,
the write refused if FIRE or LASER_ON reads active), pwm_stream_test
(PASS/FAIL exit), flow_characterize, flow_recheck_char,
flow_warm_validate and flow_matrix (takeovers: forgectrl owns the
thermal hardware, so the page's takeover replaces the tools' own
controller stop/restart, whose command line predated the supervisor;
results and logs in the bench data directory), flow_sustained,
fan_test, temp_calibrate (dry; watch bounded in seconds; the threshold
and the coolant conversion from the shared code), flow_escalate_drill
(cool_confirm_max_s shortened through forgectrl's settings for the
drill and restored; the setting's minimum is the default budget), and
live_fire_drills (<drill> [S] [F], all six drills, host from GF_HOST,
token from the board). flow_matrix joins the registry. What stays
unported cannot run against the machine at all: the two null-sink CI
harnesses and the .puls decoder.
Runner: a scope tool runs inside the takeover wrapper; the bench
environment above is passed to every tool. Tests: test_bench_registry
(registry <-> scripts/bench consistency, every ported tool builds its
command line, every script compiles, gfbench host/local modes) and the
server test (scope tool takeover, the environment reaching the tool).
Local mode smoke-run on the bench (temp_calibrate watch, setting, token)
from /tmp, removed after.
No catalog consequence: bench tools are not image components (dev-only
forgetest); the acceptance catalog is unchanged.
A component pin bump counted as a platform change: the layer content hash
in the platform identity covered the recipe carrying the SRCREV, the
platform is folded into every acceptance fingerprint, so every image
that carried any component update invalidated the whole catalog (dev
image 20260816191951: every test domain-changed after a one-line
forgectrl bump; the two manifests differ only in
platform.layers.meta-forgefirm). The component entry already identifies
the pinned source file by file; the pin double-counted it.
Component pins now live in <recipe>-pin.inc (SRCREV and the PV that
moves with it, nothing else) - forgectrl, grblhal-glowforge and
forgefirm-app here, the BSP components in meta-openglow - and
forgefirm-image-manifest.bbclass leaves *-pin.inc out of the layer
content (FORGEFIRM_MANIFEST_PIN_SUFFIX). Recipe bodies, patches, config
fragments, init scripts and third-party pins with no manifest entry stay
layer content; a pin written into a recipe body still hashes (the safe
direction). manifest-from-tree.py mirrors the rule and reads pins
through the recipe's requires; test_tree_manifest.py proves both
(pin bump: hash unchanged; recipe body or inline pin: changed).
Bitbake resolves the same SRCREV/PV for every pinned recipe.
Docs: ACCEPTANCE.md (what layer content is), kas/README.md (the pin
files in the push order), BRINGUP.md (the finding and the bench
consequence: the first image built with the pin files is itself a
platform change, so its campaign is a full one; pin bumps inherit
after it).
No catalog consequence: nothing in the image's behavior changes; the
change is to the acceptance identity computation, proven by the unit
tests and the CI lint on the tree manifest.
The routing test proves the path every logger takes and stands for the
emitters and relays in every component (fflog in forgectrl and grblHAL,
the SysLogHandler in the Python apps, the supervisor's per-controller
relay, the daemon's fifo relay, the render step): one logger daemon,
rendered rules and the effective record consistent with /logs, the tree,
the daemon's own emitter line, `logger` probes routed by program name in
the ff_line format, a stray program only in system/, kernel lines, relay
processes, nothing written outside the tree. The level-settings test
covers the validators and the configured-vs-effective / pending_reboot
contract. Both PASS on the bench with the existing logs.tree-tail-export
through the real Runner. That test's export call now uses a 300 s client:
the sanitized export took 13.9 s on the target and tripped the 10 s
default. Pin: forgectrl 4d19e9d (the sanitizer skips passes a line
cannot match; 4x faster). Coverage lint enforced: 0 uncovered.
forgectrl reveals the fuse identity only to the token AND the physical
button held, so the token alone must answer 403 with the button message.
The test now asserts the no-token refusal and the token-without-button
refusal and never fetches the identity itself (a 200 would have carried
the fuse password into the result log). BRINGUP: the bench campaign on
the flashed dev image, 7 of 24 passed so far.
scripts/acceptance-gate.py recomputes every catalog test's domain fingerprint
from /etc/forgefirm-manifest.json inside the release rootfs and requires the
committed releases/v<version>/acceptance.json to carry a matching PASS
(inherited results not core and newer than the invalidate epoch; the artifact
self-hashed; the catalog identical to the tree). release.sh runs it after the
build and stages the artifact as a release asset; FORGEFIRM_ACCEPTANCE_SKIP=1
bypasses loudly. scripts/manifest-from-tree.py builds the same manifest from
the recipe pins with git for CI and the workstation; forgetest-ci.yml runs the
unit tests and enforces the coverage lint (every manifest path covered by some
test). docs/ACCEPTANCE.md is the contract; the coverage currency rule and the
status live in BRINGUP.
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.
SAFETY.md and the safing figure name GPIO4_06 for what it is - the
readback of the chain's HV_ENABLE output through U24 (the factory net
label E-STOP is kept as a note); BRINGUP records the rename, the
device-tree polarity flip that makes bit 4 read as HV_ENABLE itself,
the removal of the estop_halts_motion opt-in, and the bench check for
the flash that ships it. Recipe pins move to forgectrl 801f1f3,
grblHAL-glowforge b629c18 and python3-gfhardware c3d1790 (PV 0.1.5).
Loop pull sets interlock_latch_reset + interlock_latch within one 50 ms sample and both clear on reinsert; charge_pump_alive tracks the run and falls 0.45 s after the last charge-pump pulse (matches the measured R*C); estop follows it on both edges. Note that an interlock trip parks grblHAL in Door:0 until a cycle start.
docs/SAFETY.md documents the control board's laser safing chain as
reverse-engineered and bench-verified - parts, every net with its SoC pin,
Linux exposure and polarity, the logic, and the software layers ForgeFIRM
stacks on it - with docs/img/safety-chain.svg. Linked from the README.
BRINGUP: Next work item 11 records the interlock-latch finding (an open
loop never tripped the latch; the kernel now drives INTERLOCK_LATCH_RESET)
and the charge-pump watchdog readback, both code-complete and pinned,
bench validation on the next image.
forgectrl ed2934b, grblHAL da4c8eb, forgefirm-app (gfhardware) 6c7534a -
all pushed and fetch-verified, license population checked for the
multi-license recipes.
- kas/README.md: the real-time rationale rests on the feeder's bounded
queue depth, not ring size; the ring is 16 MiB (~84 s at 200 kHz,
~28 min at the 10 kHz cloud tick), a capacity for cloud-mode preload.
- BUILD.md, kas config, release checklist, cold-build workflow: only
forgefirm and meta-openglow (branch scarthgap) are cloned as
siblings; every ForgeFIRM source repo is fetched by pinned SRCREV.
- UPDATE-SYSTEM.md reads as the present-state design: the cloud-mode
compatibility baseline is the cloud client's configured firmware
version, not release metadata; decisions and open items listed
plainly.
- README.md states what GRBL mode still needs the Glowforge service
for (camera-referenced homing) and what runs without it.
- BRINGUP.md: generic build-host and fwup-lab references, the retained
reproductions of the no-fire drill, the System tab.
- LIGHTBURN.md: the arm-window timeouts are machine settings.
- forgefirm-image.bb describes forgectrl as the machine-services
daemon and points at the right backlog entry.
- American spelling throughout.
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).
K1/K2/K3 pass on image 20260814223300 with software witnesses
(fire_test A/B/U reproduce the scope-pinned reference); GATE A is
closed and live fire permitted. The campaign caught the liveness
probe's inverted doors-bit guard (forgectrl 424f185, hot-deployed,
probe live-verified MOTION OK).
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.