Close evidence phase and prepare external M1 build

This commit is contained in:
Codex 2026-10-24 13:50:56 +02:00
parent 11426e7969
commit 862d5a3cca
17 changed files with 167 additions and 126 deletions

1
.build/.gitkeep Normal file
View file

@ -0,0 +1 @@

3
.gitignore vendored
View file

@ -3,7 +3,8 @@ __pycache__/
.pytest_cache/ .pytest_cache/
.venv/ .venv/
.uv-cache/ .uv-cache/
/.build/ /.build/*
!/.build/.gitkeep
# Sensitive/raw acquisition data belongs in ThePirateRay-PrivateStash. # Sensitive/raw acquisition data belongs in ThePirateRay-PrivateStash.
/raw/ /raw/

View file

@ -7,7 +7,7 @@
The Pirate Ray is Makerspace Næstved's clean-room effort to build an The Pirate Ray is Makerspace Næstved's clean-room effort to build an
open-source, safe desktop workflow for its laser equipment. The first target open-source, safe desktop workflow for its laser equipment. The first target
is the **JPT 100 W E2/M7 MOPA fiber laser** with a JCZ/EZCAD2-compatible is the **JPT 100 W E2/M7 MOPA fiber laser** with a JCZ/EZCAD2-compatible
controller and the currently assumed 110 x 110 mm lens. controller and the operator-confirmed 110 x 110 mm lens.
The destination is the useful core of a LightBurn-like workflow: import or The destination is the useful core of a LightBurn-like workflow: import or
create geometry, assign operations and MOPA parameters, preview and validate create geometry, assign operations and MOPA parameters, preview and validate
@ -36,8 +36,7 @@ Phase 0 has established a reproducible evidence and research baseline:
- the public tests, provenance checks, leak guard, and clean-clone validation - the public tests, provenance checks, leak guard, and clean-clone validation
pass. pass.
Phase 0 remains open while we collect machine photos and operator notes directly Phase 0 is complete as an evidence baseline. The settings and device screenshots, bundle,
from the actual control computer. The settings and device screenshots, bundle,
exported preferences, closed-application local settings and three explicitly exported preferences, closed-application local settings and three explicitly
operator-verified projects are preserved and analyzed. The operator confirmed a operator-verified projects are preserved and analyzed. The operator confirmed a
110 mm lens; other local test projects remain unverified. See the 110 mm lens; other local test projects remain unverified. See the
@ -75,23 +74,22 @@ We do not need to begin with a blank-sheet LightBurn clone:
material recipes, simulation, pre-flight checks, packaging, and an addon material recipes, simulation, pre-flight checks, packaging, and an addon
architecture. Its job model already represents frequency and pulse width. architecture. Its job model already represents frequency and pulse width.
The leading route is therefore either to use and improve MeerK40t directly or The accepted route is a Rayforge JCZ addon with BalorMK retained as the pinned
to build a Rayforge JCZ/Balor addon using the narrowest practical layer from behavioral oracle and reference boundary. Packaged interaction remains an M1
BalorMK. This remains a proposed decision until the packaged interaction and release gate, not an architecture-decision gate; see
real-project gates in [ADR 0002](docs/adr/0002-reuse-rayforge-and-balormk.md).
[ADR 0002](docs/adr/0002-reuse-rayforge-and-balormk.md) have passed.
## Next steps ## Next steps
The initial offline architecture spikes have passed. The next work remains The initial offline architecture spikes have passed. The next work remains
hardware-free: hardware-free:
1. collect exterior labels and startup/interlock notes; 1. obtain or produce a reviewed Windows Rayforge build artifact without
2. obtain or produce a reviewed Windows Rayforge build artifact without
installing a toolchain through this repository; installing a toolchain through this repository;
3. run and record the packaged no-device desktop interaction checklist; 2. run and record the packaged no-device desktop interaction checklist;
4. accept, revise, or reject the proposed reuse architecture from evidence; 3. promote only evidence-backed vector MOPA imports through the existing
5. complete and independently review the M3 hardware-protocol template before fail-closed review boundary;
4. complete and independently review the M3 hardware-protocol template before
seeking separate approval for any live controller communication. seeking separate approval for any live controller communication.
An independent desktop application is the fallback only if both existing An independent desktop application is the fallback only if both existing

View file

@ -46,13 +46,13 @@
}, },
{ {
"path": "android/mipmap-anydpi-v26/ic_launcher.xml", "path": "android/mipmap-anydpi-v26/ic_launcher.xml",
"sha256": "e071a8516287e0bacccc1a14a62502f3e1eda8360b2aad3c88c390ccbfa77122", "sha256": "32715ff646f6b9fef7c321138e4a14556c4a1f98a87b06070b56f41643dad718",
"bytes": 275 "bytes": 270
}, },
{ {
"path": "android/mipmap-anydpi-v26/ic_launcher_round.xml", "path": "android/mipmap-anydpi-v26/ic_launcher_round.xml",
"sha256": "e071a8516287e0bacccc1a14a62502f3e1eda8360b2aad3c88c390ccbfa77122", "sha256": "32715ff646f6b9fef7c321138e4a14556c4a1f98a87b06070b56f41643dad718",
"bytes": 275 "bytes": 270
}, },
{ {
"path": "android/mipmap-hdpi/ic_launcher.png", "path": "android/mipmap-hdpi/ic_launcher.png",
@ -136,8 +136,8 @@
}, },
{ {
"path": "android/values/ic_launcher_background.xml", "path": "android/values/ic_launcher_background.xml",
"sha256": "1ff64d109f4173e84bf4d6ed9957b6f37f3456b0db3b19bb17d3a3c1de2687e4", "sha256": "b54bc246c11f5e12b46926eef269f08b67cf612cc8e9a9580f86d95820b1a2b1",
"bytes": 125 "bytes": 121
}, },
{ {
"path": "ios/AppIcon.appiconset/Contents.json", "path": "ios/AppIcon.appiconset/Contents.json",
@ -980,8 +980,8 @@
}, },
{ {
"path": "web/site.webmanifest", "path": "web/site.webmanifest",
"sha256": "ca7277912f7c8ccb373ca252c88050eecc217c355b05d5d6687517947812a32a", "sha256": "e56c1cff35765833a1f4d8d543fdf117d9c6b1572103122340ff037e1fedbac6",
"bytes": 699 "bytes": 666
}, },
{ {
"path": "windows/app.ico", "path": "windows/app.ico",

View file

@ -1,7 +1,7 @@
# Codex continuation brief # Codex continuation brief
- Updated: 2026-10-24 - Updated: 2026-10-24
- Current focus: M1 build, packaging and no-device acceptance - Current focus: produce and accept the packaged Windows M1 checkpoint
- Hardware access: prohibited - Hardware access: prohibited
- Automatic toolchain installation: prohibited - Automatic toolchain installation: prohibited
@ -30,7 +30,7 @@ The repository now provides:
- pinned upstream contract/license manifests and a drift checker; - pinned upstream contract/license manifests and a drift checker;
- a fail-closed API-23 adapter isolating Rayforge's internal driver registry; - a fail-closed API-23 adapter isolating Rayforge's internal driver registry;
- a proposed MIT project-license decision in ADR 0003; Kaz has not accepted - a proposed MIT project-license decision in ADR 0003; Kaz has not accepted
it, so the repository remains unlicensed. it, so the repository remains unlicensed;
- a fail-closed M3 hardware-research protocol, incomplete public plan template - a fail-closed M3 hardware-research protocol, incomplete public plan template
and validator; validation can reach only `ready_for_review` and never and validator; validation can reach only `ready_for_review` and never
authorizes hardware access. authorizes hardware access.
@ -66,19 +66,20 @@ physically disconnected. LightBurn Pro 2.1.03 showed the JPT profile as
`Disconnected`, the 110 mm material library, and explicit `Frequency (kHz)` / `Disconnected`, the 110 mm material library, and explicit `Frequency (kHz)` /
`Q-Pulse (ns)` UI labels. The live preferences, newest native backup, all five `Q-Pulse (ns)` UI labels. The live preferences, newest native backup, all five
general Settings tabs and all three JPT Device Settings tabs are hashed in general Settings tabs and all three JPT Device Settings tabs are hashed in
PrivateStash. Exterior hardware photographs and startup/interlock notes remain PrivateStash. Exterior hardware photographs and startup/interlock notes are
open; see `docs/research/lightburn-control-machine-observation-2026-10-24.md`. deferred to M3 hardware readiness and do not block the offline product
checkpoint; see
`docs/research/lightburn-control-machine-observation-2026-10-24.md`.
M1 is not complete. Do not describe the repository as a finished desktop M1 is not yet a packaged desktop checkpoint. Only these two result-oriented
application until both gates below have real evidence: gates remain:
1. A Windows artifact for the pinned Rayforge revision must be produced on a 1. A Windows artifact for the pinned Rayforge revision must be produced on a
reviewed build runner, verified without execution, and pass the manual reviewed build runner, verified without execution, and pass the manual
no-device desktop checklist. no-device desktop checklist.
2. The known-working private JPT `.lbrn2` serialization evidence gate is met. 2. Promote only complete vector MOPA imports after an explicit unit contract,
Offline mapping and disabled-output workflow staging now have synthetic absent-pulse-width policy and exact machine-context validation. The private
tests. Promotion still requires an independently established unit contract, serialization-evidence gate itself is complete.
explicit resolution of absent pulse width and machine-context validation.
Raw projects, screenshots containing identifiers, machine data and build Raw projects, screenshots containing identifiers, machine data and build
binaries stay out of the public repository. binaries stay out of the public repository.
@ -105,11 +106,13 @@ order:
`docs/research/m1-no-device-acceptance.md`; `docs/research/m1-no-device-acceptance.md`;
4. `scripts/validate_m1_manual_acceptance.py`. 4. `scripts/validate_m1_manual_acceptance.py`.
If no artifact is available, do not install a build environment here. The next If no artifact is available, send the external-builder handoff in
offline implementation task is evidence-backed MOPA import mapping with `docs/research/m1-no-device-acceptance.md`; do not start another research
synthetic tests; Kaz's explicit decision on ADR 0003 is also outstanding. branch or install a build environment here. Kaz's explicit decision on ADR
0003 is also outstanding.
The M3 protocol can also be reviewed offline, but its The M3 protocol can also be reviewed offline, but its
operation allowlist must stay empty until exact read-only request evidence is operation allowlist must stay empty until exact read-only request evidence is
available. Until the external gates and separate hardware approval arrive, available. ADR 0002 is accepted for the offline architecture. Until the
ADR 0002 remains Proposed and no controller backend, USB endpoint access, external gates and separate hardware approval arrive, no controller backend,
physical framing, galvo motion or laser output is authorized. USB endpoint access, physical framing, galvo motion or laser output is
authorized.

View file

@ -1,7 +1,8 @@
# ADR 0002: Prove reuse of Rayforge and BalorMK before building an app # ADR 0002: Prove reuse of Rayforge and BalorMK before building an app
- Status: Proposed - Status: Accepted
- Date: 2026-10-17 - Date: 2026-10-17
- Accepted: 2026-10-24
## Context ## Context
@ -20,7 +21,7 @@ Creating another editor, geometry stack and controller implementation would
duplicate large, safety-relevant bodies of work and permanently increase the duplicate large, safety-relevant bodies of work and permanently increase the
maintenance burden. maintenance burden.
## Proposed decision ## Decision
Do not begin independent desktop product implementation. First run three Do not begin independent desktop product implementation. First run three
offline, version-pinned proofs: offline, version-pinned proofs:
@ -32,12 +33,11 @@ offline, version-pinned proofs:
3. translate one synthetic Rayforge operation stream to JCZ/Balor commands and 3. translate one synthetic Rayforge operation stream to JCZ/Balor commands and
compare it with a BalorMK mock trace. compare it with a BalorMK mock trace.
If the proofs pass, prefer one of these outcomes in order: The proofs pass at the offline architecture boundary. Build the product as a
Rayforge JCZ addon and retain pinned BalorMK behavior as the controller-level
1. use and contribute to MeerK40t if its full application is adequate; oracle. Adapt only the narrowest necessary implementation after licensing and
2. implement and upstream a Rayforge JCZ/Balor addon, adapting the narrowest attribution allow it. Packaged Windows interaction remains an M1 release gate,
practical layer from BalorMK; not a reason to defer the host-architecture decision.
3. extract a small maintained controller library shared by either application.
An independent desktop application requires a later ADR showing concrete, An independent desktop application requires a later ADR showing concrete,
tested gaps in both existing applications. tested gaps in both existing applications.
@ -121,14 +121,16 @@ Subsequent offline evidence:
- [M1 no-device acceptance checkpoint](../research/m1-no-device-acceptance.md): - [M1 no-device acceptance checkpoint](../research/m1-no-device-acceptance.md):
the public regression suite and leak guard now form one transport-free gate the public regression suite and leak guard now form one transport-free gate
over addon lifecycle, workflow, layers, recipes, encoding, negative cases, over addon lifecycle, workflow, layers, recipes, encoding, negative cases,
cancellation/recovery and synthetic imports. The packaged Windows GUI and a cancellation/recovery and synthetic imports. Packaged Windows interaction
known-working private project remain external gates. remains a release gate.
- [Upstream contract and license audit](../research/upstream-contract-and-license-audit.md): - [Upstream contract and license audit](../research/upstream-contract-and-license-audit.md):
both pins and license notices are now machine-verifiable; the documented both pins and license notices are now machine-verifiable; the documented
addon lifecycle is usable, but driver registration and cleanup still rely addon lifecycle is usable, but driver registration and cleanup still rely
on an internal registry seam that should be replaced upstream. on an internal registry seam that should be replaced upstream.
These results satisfy the current model, buffering and no-device integration Three operator-verified private projects subsequently established the
proofs. They do not yet satisfy the packaged human-interaction or real-project project-level `frequency` and `QPulseWidth` serialization needed to implement
import obligations, so this ADR remains proposed. the disabled-output import seam. Together, the evidence satisfies the host
architecture decision. It does not authorize hardware access or claim that the
packaged Windows interaction and import-promotion release gates have passed.

View file

@ -102,8 +102,9 @@ The PirateRay-specific work should remain narrow:
## Gaps that must not be papered over ## Gaps that must not be papered over
1. A known-working private galvo `.lbrn2` is still required to establish 1. Three known-working private galvo projects establish project-level
project-level `QPulseWidth` serialization and omission/default behavior. `QPulseWidth` serialization. Omission/default behavior still requires an
explicit policy before promotion to executable output.
2. The current Windows environment has not launched the pinned Rayforge GTK 2. The current Windows environment has not launched the pinned Rayforge GTK
application; the headless UI contract is not a human interaction test. application; the headless UI contract is not a human interaction test.
3. `RequireFrameBeforeStart` is observed in a LightBurn device export and 3. `RequireFrameBeforeStart` is observed in a LightBurn device export and
@ -111,8 +112,9 @@ The PirateRay-specific work should remain narrow:
framing behavior. framing behavior.
4. Machine-specific frequency, pulse-width, timing and correction limits are 4. Machine-specific frequency, pulse-width, timing and correction limits are
not established by LightBurn documentation or exported recipe values. not established by LightBurn documentation or exported recipe values.
5. Material recipes lack a proven contract for provenance, approval and 5. PirateRay recipe metadata now supplies fail-closed provenance, approval and
machine/lens/correction compatibility. machine/lens/correction compatibility checks; packaged UI interaction is
still part of the M1 desktop checklist.
6. The HTML prototype named by the handoff is absent from the active checkout; 6. The HTML prototype named by the handoff is absent from the active checkout;
its historical controls must not be treated as implemented capability. its historical controls must not be treated as implemented capability.

View file

@ -55,4 +55,5 @@ test explicit unit conversions, field absence and invalid values. It must not
copy private project geometry into public fixtures or enable hardware access. copy private project geometry into public fixtures or enable hardware access.
The reviewed Windows artifact and manual no-device desktop acceptance remain The reviewed Windows artifact and manual no-device desktop acceptance remain
open. ADR 0002 remains Proposed until those gates have also passed. open as M1 release gates. ADR 0002 has accepted the Rayforge-addon architecture
independently of those packaging gates.

View file

@ -38,6 +38,17 @@ runtime is unavailable, it stops and reports the missing prerequisite. Any
toolchain preparation remains a separate, explicitly invoked upstream action toolchain preparation remains a separate, explicitly invoked upstream action
or should happen on a dedicated build runner. or should happen on a dedicated build runner.
### External builder handoff
The external builder needs only a clean checkout of this repository, Windows,
and a reviewed Rayforge-compatible MSYS2/UCRT64 runtime. No laser or controller
may be connected. Run `scripts/build_rayforge_windows_checkpoint.ps1` from the
repository root with a clean checkout and an absent or empty artifact path. A
successful run leaves one transferable directory,
`.build/artifacts/`, containing exactly the portable ZIP, installer, Rayforge
license sidecar and `rayforge-windows-checkpoint.json`. Transfer that directory
as a unit; the receiving side verifies it before executing either binary.
Before staging, the script rejects transport imports in the addon. The build Before staging, the script rejects transport imports in the addon. The build
entry point also rejects tracked Rayforge modifications and any unexpected entry point also rejects tracked Rayforge modifications and any unexpected
untracked source. It removes and recreates only the validated addon staging untracked source. It removes and recreates only the validated addon staging
@ -150,10 +161,12 @@ hardware, non-isolated app data, changed evidence, malformed review or any
stop condition. Final `passed` requires a non-empty reviewer name and an stop condition. Final `passed` requires a non-empty reviewer name and an
ISO-8601 review timestamp with timezone. No status authorizes hardware access. ISO-8601 review timestamp with timezone. No status authorizes hardware access.
## Remaining external gates ## Remaining release gates
- A known-working private `.lbrn2` is still required before implementing
project-level `QPulseWidth` mapping.
- Human interaction results from the packaged build must be recorded before - Human interaction results from the packaged build must be recorded before
ADR 0002 can be accepted. M1 can be called a packaged desktop checkpoint.
- The three known-working private projects establish serialized MOPA fields.
Promotion from disabled staging still requires the explicit unit contract,
absent-pulse-width policy and exact machine-context validation already
enforced by preflight.
- Physical controller behavior remains outside M1. - Physical controller behavior remains outside M1.

View file

@ -14,7 +14,7 @@ optics:
active_field_mm: active_field_mm:
width: 110 width: 110
height: 110 height: 110
status: assumed-pending-physical-verification status: operator-confirmed-on-control-machine
other_configurations_observed_mm: other_configurations_observed_mm:
- width: 300 - width: 300
height: 300 height: 300
@ -63,3 +63,4 @@ notes:
- USB revision 0005 is a descriptor value, not a proven board or firmware version. - USB revision 0005 is a descriptor value, not a proven board or firmware version.
- LightBurn bundle manifests establish version 2.1.03 but do not expose a separate build identifier. - LightBurn bundle manifests establish version 2.1.03 but do not expose a separate build identifier.
- Exported recipe values are observations, not certified hardware limits or safe operating parameters. - Exported recipe values are observations, not certified hardware limits or safe operating parameters.
- The operator confirmed the installed 110 mm lens during the disconnected control-machine inspection on 2026-10-24.

View file

@ -1,6 +1,6 @@
# Product and research roadmap # Product and research roadmap
Last updated: 2026-10-21 Last updated: 2026-10-24
## Destination ## Destination
@ -65,8 +65,7 @@ It does **not** yet establish:
- which values are controller settings versus laser-source settings; - which values are controller settings versus laser-source settings;
- status, interlock, error, abort, and recovery behavior; - status, interlock, error, abort, and recovery behavior;
- whether an apparently harmless query can move a galvo or arm emission; - whether an apparently harmless query can move a galvo or arm emission;
- how a known-working `.lbrn2` project serializes JPT frequency and - the effective pulse-width default for settings that omit `QPulseWidth`.
`QPulseWidth` settings.
The later LightBurn export collection now establishes version `2.1.03`, a The later LightBurn export collection now establishes version `2.1.03`, a
110 x 110 mm `JCZFiber`/`LibUSB` JPT profile, and the `QPulseWidth` device 110 x 110 mm `JCZFiber`/`LibUSB` JPT profile, and the `QPulseWidth` device
@ -78,7 +77,7 @@ would be premature and would make the safety boundary impossible to audit.
## Milestones ## Milestones
### M0 — Evidence baseline (current) ### M0 — Evidence baseline (complete)
Preserve and hash the installer snapshot, publish sanitized findings, and keep Preserve and hash the installer snapshot, publish sanitized findings, and keep
public/private provenance verifiable. Complete M0 with one local collection public/private provenance verifiable. Complete M0 with one local collection
@ -90,7 +89,7 @@ Exit criteria:
- exact LightBurn version, bundle, preferences, screenshots, and working - exact LightBurn version, bundle, preferences, screenshots, and working
project examples are present in PrivateStash; project examples are present in PrivateStash;
- mounted lens, startup, interlock, and USB device identity are confirmed; - mounted lens and USB device identity are confirmed;
- sanitized metadata and new analysis are committed publicly; - sanitized metadata and new analysis are committed publicly;
- both repositories pass their clean-clone validation. - both repositories pass their clean-clone validation.
@ -100,7 +99,7 @@ Ecosystem research found that Rayforge already implements most of the desired
application and MeerK40t BalorMK already implements the relevant controller. application and MeerK40t BalorMK already implements the relevant controller.
Validate those assets before defining competing application architecture. See Validate those assets before defining competing application architecture. See
the [open-source landscape](research/open-source-landscape.md) and the [open-source landscape](research/open-source-landscape.md) and
[proposed ADR 0002](adr/0002-reuse-rayforge-and-balormk.md). [accepted ADR 0002](adr/0002-reuse-rayforge-and-balormk.md).
The first work is entirely offline: The first work is entirely offline:
@ -150,24 +149,21 @@ width, and unsupported Ops. Hardware-specific upper limits remain unknown and
are deliberately not guessed. are deliberately not guessed.
[The transport-free JCZ record spike](research/rayforge-jcz-packet-spike.md) [The transport-free JCZ record spike](research/rayforge-jcz-packet-spike.md)
packs the same operation stream into deterministic little-endian 12-byte now extends the deterministic 12-byte records with fixed-size list buffering
records whose opcodes, values, coordinates and distances match the pinned and explicit state-before-motion ordering. Upload, execution and physical
BalorMK mock evidence. It deliberately omits list buffering and all transport; transport remain outside M1.
controller-safe state ordering remains an explicit evidence gap.
[The synthetic LightBurn MOPA import spike](research/rayforge-lightburn-mopa-import-spike.md) [The synthetic LightBurn MOPA import spike](research/rayforge-lightburn-mopa-import-spike.md)
confirms that Rayforge preserves geometry, power, speed and passes and retains confirms that Rayforge preserves geometry, power, speed and passes. Three later
an observed LightBurn frequency value in raw source metadata. Frequency is not operator-verified projects establish project-level `frequency` and
mapped into workflow settings. A later real device export identifies `QPulseWidth` fields. The addon now stages complete vector settings into
`QPulseWidth` and its nanosecond UI contract, but project serialization remains disabled Rayforge steps and rejects them at preflight until units, defaults and
unknown, so the job still deliberately fails the JCZ encoding gate. A private machine context are explicitly approved.
known-working `.lbrn2` fixture is required before implementing the missing
MOPA mapping.
The architecture decision remains proposed until the packaged Windows ADR 0002 accepts the Rayforge-addon architecture with BalorMK as the pinned
interaction and known-working private-project gates produce evidence. An behavioral oracle. Packaged Windows interaction is a release gate, not an open
independent app requires a documented gap analysis showing why neither architecture question. An independent app now requires a later ADR with
existing platform can be extended responsibly. concrete evidence that this accepted route cannot meet product needs.
Exit criteria: a representative JPT job survives import/planning, MOPA Exit criteria: a representative JPT job survives import/planning, MOPA
parameters and units, validation and deterministic mock encoding; the chosen parameters and units, validation and deterministic mock encoding; the chosen
@ -266,26 +262,24 @@ Bodor BCL503MU. Each controller family gets its own evidence, protocol,
capability, and hardware-validation track. A successful JPT backend does not capability, and hardware-validation track. A successful JPT backend does not
imply compatible safety or control behavior on either CO2 machine. imply compatible safety or control behavior on either CO2 machine.
## Immediate next work package ## Current completion track
The next work package should be small enough to review in a few commits: The exploratory work package is complete: verified projects, device evidence,
profile import, list buffering, cancellation/recovery, workflow modeling and
the M3 protocol draft are all present. Work now converges on two deliverables:
1. collect two or three known-working `.lbrn2` projects plus the remaining 1. produce the pinned Windows Rayforge checkpoint on a reviewed external build
device screenshots, preferences folder, photos and operator notes; runner, verify the transferred artifact set and complete the no-device
2. add a sanitized synthetic JCZ `.lbdev` fixture and characterize the exact desktop interaction checklist;
gaps in Rayforge's device importer; 2. promote complete vector MOPA imports only after recording the explicit unit
3. implement and test the smallest evidence-backed JCZ profile-import seam, contract, absent-pulse-width policy and exact machine-context validation.
keeping project `QPulseWidth` mapping gated until a real project confirms
its serialization;
4. extend the offline JCZ encoder from semantic records to controller-list
buffering and explicit safe state ordering, still without transport;
5. exercise cancellation/recovery and the graphical workflow against mock
backends, then accept, amend or reject ADR 0002;
6. complete and independently review the written M3 hardware-research protocol
before seeking separate approval for any USB endpoint access or controller
command.
The draft, template, validator and regression tests for item 6 are present. Exterior photographs, additional screenshots and speculative protocol research
are not M1 blockers. Hardware work remains separately gated by M3.
### Completed implementation record
The M3 draft, template, validator and regression tests are present.
Hardware work still cannot proceed until the exact read-only operation evidence, Hardware work still cannot proceed until the exact read-only operation evidence,
driver/rollback plan, physical controls, distinct assigned roles and separate driver/rollback plan, physical controls, distinct assigned roles and separate
human approval are all in place. human approval are all in place.
@ -299,8 +293,8 @@ profile model. The fixture and regression test remain transport-free.
Item 3 now has a minimal external-addon seam: it recognizes only the synthetic Item 3 now has a minimal external-addon seam: it recognizes only the synthetic
`JCZFiber` fixture with explicit provenance and frame-before-start, binds it to `JCZFiber` fixture with explicit provenance and frame-before-start, binds it to
the no-device JCZ driver, preserves process values as raw observations with the no-device JCZ driver, preserves process values as raw observations with
unit uncertainty, and keeps encoding disabled until the remaining profile and unit uncertainty, and keeps encoding disabled until the unit/default and exact
project evidence exists. machine-context review passes.
Item 4 now covers transport-free fixed-size list buffering and an explicit Item 4 now covers transport-free fixed-size list buffering and an explicit
state-before-motion order derived from pinned BalorMK. Port commands, upload, state-before-motion order derived from pinned BalorMK. Port commands, upload,
@ -318,7 +312,8 @@ generated settings UI exposes frequency and pulse width with Hz/ns contracts,
the values survive step serialization, and the preview artifact is populated the values survive step serialization, and the preview artifact is populated
without machine code or transport. This is a headless UI-contract and pipeline without machine code or transport. This is a headless UI-contract and pipeline
proof, not a completed human GTK test. Packaged Windows interaction with the proof, not a completed human GTK test. Packaged Windows interaction with the
external addon remains open before item 5 or ADR 0002 can close. external addon remains the final desktop release gate; ADR 0002 has accepted
the architecture independently of it.
The first Windows launch preflight is recorded separately. It fails closed The first Windows launch preflight is recorded separately. It fails closed
because the available spike venv has no PyGObject `gi` module and this machine because the available spike venv has no PyGObject `gi` module and this machine

View file

@ -1,4 +1,4 @@
# Phase 0 status # Phase 0 status — complete
Last updated: 2026-10-24 Last updated: 2026-10-24
@ -43,9 +43,10 @@ Last updated: 2026-10-24
and preserved privately. They confirm the 110 x 110 mm field, Q-pulse-width and preserved privately. They confirm the 110 x 110 mm field, Q-pulse-width
enablement, visible 1-4000 kHz range and require-frame-before-start setting. enablement, visible 1-4000 kHz range and require-frame-before-start setting.
## Pending external acquisition ## Deferred hardware-readiness evidence
These items remain pending from the actual control machine: These items are still useful before hardware work, but no longer block the
completed Phase 0 evidence baseline or the offline M1 product checkpoint:
- safe exterior hardware/label photographs and startup/interlock notes; - safe exterior hardware/label photographs and startup/interlock notes;
- stock-driver identity and version from the actual LightBurn control computer. - stock-driver identity and version from the actual LightBurn control computer.
@ -58,13 +59,8 @@ Collect them directly into a dated directory under PrivateStash by following
the [LightBurn checklist](../collection/lightburn.md). Do not use Google Drive the [LightBurn checklist](../collection/lightburn.md). Do not use Google Drive
as an intermediate project workspace and do not generate Support Data. as an intermediate project workspace and do not generate Support Data.
Phase 0 remains open until the safety- and interoperability-relevant remainder Collect them when preparing an approved M3 hardware session. Do not expand the
is hashed, committed, pushed, and represented by sanitized public evidence. offline product scope merely to keep Phase 0 open.
The work that does not depend on this acquisition can proceed in parallel.
The [product and research roadmap](../roadmap.md) defines the offline application
core, configuration analysis, protocol research, gated hardware experiments,
and workshop release as separate milestones.
## Offline M1 status ## Offline M1 status
@ -112,17 +108,20 @@ evidence for:
commit, reviewer, bounded session and explicit safety statements while still commit, reviewer, bounded session and explicit safety statements while still
requiring live preflight and returning no automatic hardware authorization. requiring live preflight and returning no automatic hardware authorization.
The remaining M1 gates are deliberately explicit: The remaining M1 release gates are deliberately narrow:
- a private known-working `.lbrn2` project is still required before mapping
project-level `QPulseWidth` into workflow settings;
- packaged Windows/GTK interaction is blocked until a reviewed Rayforge - packaged Windows/GTK interaction is blocked until a reviewed Rayforge
runtime with PyGObject and MSYS2/UCRT64 is available; the repository build runtime with PyGObject and MSYS2/UCRT64 is available; the repository build
entry point fetches pinned source but never installs that runtime; entry point fetches pinned source but never installs that runtime;
- graphical workflow, import, lens correction and controller protocol - three operator-verified projects establish project-level `frequency` and
behavior remain unproven at the physical boundary; `QPulseWidth` serialization, and the offline bridge stages complete vector
- ADR 0002 remains **Proposed** until those gaps are resolved or rejected by settings with output disabled; promotion still requires an explicit unit
evidence. contract, absent-pulse-width policy and exact machine-context validation;
- graphical workflow, lens correction and controller behavior remain unproven
at the physical boundary and belong to later gated milestones.
ADR 0002 is accepted for the offline product architecture. These release gates
do not reopen that decision.
No item in this section authorizes USB access, controller commands or laser No item in this section authorizes USB access, controller commands or laser
output. output.

View file

@ -19,6 +19,22 @@ if (-not $ArtifactRoot) {
$ArtifactRoot = Join-Path $repositoryRoot ".build\artifacts" $ArtifactRoot = Join-Path $repositoryRoot ".build\artifacts"
} }
$repositoryStatus = @(
& git -C $repositoryRoot status --porcelain=v1 --untracked-files=all
)
if ($LASTEXITCODE -ne 0) {
throw "Could not inspect The Pirate Ray checkout state"
}
if ($repositoryStatus.Count -gt 0) {
throw "The Pirate Ray checkout must be clean before an external checkpoint build"
}
if (
(Test-Path -LiteralPath $ArtifactRoot) -and
@(Get-ChildItem -LiteralPath $ArtifactRoot -Force).Count -gt 0
) {
throw "Artifact root must be absent or empty: $ArtifactRoot"
}
& (Join-Path $PSScriptRoot "prepare_rayforge_build.ps1") ` & (Join-Path $PSScriptRoot "prepare_rayforge_build.ps1") `
-CacheRoot $CacheRoot ` -CacheRoot $CacheRoot `
-AllowStagedPirateRayAddon -AllowStagedPirateRayAddon
@ -87,6 +103,10 @@ if (-not (Test-Path -LiteralPath $installer)) {
} }
New-Item -ItemType Directory -Force -Path $ArtifactRoot | Out-Null New-Item -ItemType Directory -Force -Path $ArtifactRoot | Out-Null
$artifactInstaller = Join-Path $ArtifactRoot (Split-Path -Leaf $installer)
if ($PSCmdlet.ShouldProcess($artifactInstaller, "Copy installer into checkpoint")) {
Copy-Item -LiteralPath $installer -Destination $artifactInstaller -Force
}
$licenseDirectory = Join-Path $bundleDirectory "THIRD_PARTY_LICENSES" $licenseDirectory = Join-Path $bundleDirectory "THIRD_PARTY_LICENSES"
$rayforgeLicense = Join-Path $checkout "LICENSE" $rayforgeLicense = Join-Path $checkout "LICENSE"
$normalizedLicense = [IO.File]::ReadAllText($rayforgeLicense).Replace("`r`n", "`n").Replace("`r", "`n") $normalizedLicense = [IO.File]::ReadAllText($rayforgeLicense).Replace("`r`n", "`n").Replace("`r", "`n")
@ -134,9 +154,9 @@ $result = [ordered]@{
bytes = (Get-Item -LiteralPath $archive).Length bytes = (Get-Item -LiteralPath $archive).Length
} }
installer = [ordered]@{ installer = [ordered]@{
path = Split-Path -Leaf $installer path = Split-Path -Leaf $artifactInstaller
sha256 = (Get-FileHash -Algorithm SHA256 -LiteralPath $installer).Hash.ToLowerInvariant() sha256 = (Get-FileHash -Algorithm SHA256 -LiteralPath $artifactInstaller).Hash.ToLowerInvariant()
bytes = (Get-Item -LiteralPath $installer).Length bytes = (Get-Item -LiteralPath $artifactInstaller).Length
} }
license_files = @( license_files = @(
[ordered]@{ [ordered]@{
@ -152,3 +172,4 @@ $result = [ordered]@{
$manifestPath = Join-Path $ArtifactRoot "rayforge-windows-checkpoint.json" $manifestPath = Join-Path $ArtifactRoot "rayforge-windows-checkpoint.json"
$result | ConvertTo-Json -Depth 8 | Set-Content -LiteralPath $manifestPath -Encoding utf8 $result | ConvertTo-Json -Depth 8 | Set-Content -LiteralPath $manifestPath -Encoding utf8
Write-Output "Checkpoint manifest: $manifestPath" Write-Output "Checkpoint manifest: $manifestPath"
Write-Output "Transfer the complete artifact directory: $ArtifactRoot"

View file

@ -131,7 +131,7 @@ def run(source: Path) -> dict:
"evidence": evidence, "evidence": evidence,
"remaining_manual_gates": [ "remaining_manual_gates": [
"packaged Windows GUI interaction", "packaged Windows GUI interaction",
"known-working private LightBurn project import", "reviewed promotion of imported MOPA settings",
], ],
} }

View file

@ -17,7 +17,7 @@ EXPECTED_TARGET = {
"usb_pid": "9899", "usb_pid": "9899",
"device_description": "USBLMCV4", "device_description": "USBLMCV4",
"lens_field_mm": 110, "lens_field_mm": 110,
"lens_status": "assumed-pending-physical-verification", "lens_status": "operator-confirmed-on-control-machine",
} }
PROHIBITED_CAPABILITIES = { PROHIBITED_CAPABILITIES = {
"persistent_write", "persistent_write",

View file

@ -9,7 +9,7 @@
"usb_pid": "9899", "usb_pid": "9899",
"device_description": "USBLMCV4", "device_description": "USBLMCV4",
"lens_field_mm": 110, "lens_field_mm": 110,
"lens_status": "assumed-pending-physical-verification" "lens_status": "operator-confirmed-on-control-machine"
}, },
"scope": { "scope": {
"milestone": "M3", "milestone": "M3",

View file

@ -32,7 +32,7 @@ class M1AcceptancePipelineTests(unittest.TestCase):
source = ACCEPTANCE.read_text(encoding="utf-8") source = ACCEPTANCE.read_text(encoding="utf-8")
self.assertIn(manifest["commit"], source) self.assertIn(manifest["commit"], source)
self.assertIn("packaged Windows GUI interaction", source) self.assertIn("packaged Windows GUI interaction", source)
self.assertIn("known-working private LightBurn project import", source) self.assertIn("reviewed promotion of imported MOPA settings", source)
self.assertIn('"-m", "unittest", "discover"', source) self.assertIn('"-m", "unittest", "discover"', source)
self.assertIn("scripts/check_public_repo.py", source) self.assertIn("scripts/check_public_repo.py", source)
self.assertIn("timeout=120", source) self.assertIn("timeout=120", source)
@ -50,6 +50,10 @@ class M1AcceptancePipelineTests(unittest.TestCase):
self.assertIn("GetFullPath", source) self.assertIn("GetFullPath", source)
self.assertIn("Remove-Item -LiteralPath $resolvedAddonDestination", source) self.assertIn("Remove-Item -LiteralPath $resolvedAddonDestination", source)
self.assertIn("AllowStagedPirateRayAddon", source) self.assertIn("AllowStagedPirateRayAddon", source)
self.assertIn("Copy installer into checkpoint", source)
self.assertIn("-LiteralPath $artifactInstaller", source)
self.assertIn("checkout must be clean", source)
self.assertIn("Artifact root must be absent or empty", source)
def test_acceptance_has_no_hardware_commands(self): def test_acceptance_has_no_hardware_commands(self):
source = ACCEPTANCE.read_text(encoding="utf-8").lower() source = ACCEPTANCE.read_text(encoding="utf-8").lower()