evidence: analyze LightBurn device exports

This commit is contained in:
Codex 2026-10-19 10:14:45 +02:00
parent ceb70be30e
commit 202e756071
8 changed files with 239 additions and 60 deletions

View file

@ -25,15 +25,21 @@ Phase 0 has established a reproducible evidence and research baseline:
correction data, material libraries, and relevant configuration files; correction data, material libraries, and relevant configuration files;
- the physical controller has been passively identified by Windows as - the physical controller has been passively identified by Windows as
`USBLMCV4`, USB `9588:9899`, with device revision descriptor `0005`; `USBLMCV4`, USB `9588:9899`, with device revision descriptor `0005`;
- LightBurn `2.1.03` exports now confirm the JPT `JCZFiber`/`LibUSB` profile,
110 x 110 mm work area, Q-pulse-width support, and the exact serialized
setting name `QPulseWidth`;
- the Eduard X1550 export confirms a Ruida/Serial profile and 1500 x 1000 mm
work area;
- the development computer has no compatible controller driver installed; - the development computer has no compatible controller driver installed;
- no USB endpoint has been opened and no controller command, galvo movement, - no USB endpoint has been opened and no controller command, galvo movement,
framing, marking, or laser output has occurred; framing, marking, or laser output has occurred;
- 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 the LightBurn bundle, preferences, Phase 0 remains open while we collect known-working LightBurn projects, device
known-working projects, device screenshots, machine photos, and operator notes screenshots, the closed-application preferences folder, machine photos, and
directly from the actual control computer. operator notes directly from the actual control computer. The bundle and
exported preferences are already preserved and analyzed.
## The important discovery ## The important discovery
@ -55,16 +61,17 @@ BalorMK. This remains a proposed decision until the offline proofs in
## Next steps ## Next steps
The next work is deliberately offline and hardware-free: The initial offline architecture spikes have passed. The next work remains
hardware-free:
1. run a synthetic MOPA job through MeerK40t's mock controller; 1. collect known-working `.lbrn2` projects and the remaining screenshots,
2. prove that an external Rayforge addon can register a binary no-device photos, preferences folder and operator notes;
driver on Windows; 2. extend the synthetic import tests with the now-observed `QPulseWidth`
3. translate a small Rayforge operation stream into a deterministic JCZ trace evidence, without assuming its `.lbrn2` representation;
and compare it with BalorMK; 3. define the minimum Rayforge JCZ device-profile/import extension and finish
4. test Rayforge's existing LightBurn import against galvo/MOPA fixtures; controller-list buffering and safe state ordering offline;
5. accept, revise, or reject the proposed reuse architecture from evidence; 4. accept, revise, or reject the proposed reuse architecture from evidence;
6. write and review a separate hardware protocol before any live controller 5. write and review a separate hardware protocol before any live controller
communication. communication.
An independent desktop application is the fallback only if both existing An independent desktop application is the fallback only if both existing
@ -86,6 +93,7 @@ project workspace or development destination.
- [Open-source reuse landscape](docs/research/open-source-landscape.md) - [Open-source reuse landscape](docs/research/open-source-landscape.md)
- [Proposed Rayforge/BalorMK reuse decision](docs/adr/0002-reuse-rayforge-and-balormk.md) - [Proposed Rayforge/BalorMK reuse decision](docs/adr/0002-reuse-rayforge-and-balormk.md)
- [Live USB enumeration](docs/research/live-usb-enumeration-2026-10-17.md) - [Live USB enumeration](docs/research/live-usb-enumeration-2026-10-17.md)
- [LightBurn device-export analysis](docs/research/lightburn-device-exports-2026-10-19.md)
- [Sanitized machine profile](docs/research/machine-profile.yaml) - [Sanitized machine profile](docs/research/machine-profile.yaml)
- [110/300 configuration comparison](docs/research/config-comparison.md) - [110/300 configuration comparison](docs/research/config-comparison.md)
- [Data handling policy](docs/data-handling.md) - [Data handling policy](docs/data-handling.md)

View file

@ -0,0 +1,114 @@
# LightBurn device-export analysis
- Date: 2026-10-19
- Scope: four user-provided LightBurn exports, analyzed offline
- LightBurn version recorded by all three bundle manifests: `2.1.03`
- Safety boundary: no executable, driver, USB endpoint or machine command was
used
## Evidence
Raw exports remain in PrivateStash. This public record contains hashes,
structure and independently derived findings only.
| Artifact ID | Size | Role |
| --- | ---: | --- |
| `sha256-e16fdb0aa7c374c4c94d` | 20,309 bytes | Complete bundle containing three device profiles, preferences and libraries |
| `sha256-fc6cf52f4ef815dd5037` | 5,720 bytes | Eduard device bundle |
| `sha256-88c762adcb8e9693c913` | 97,356 bytes | Exported LightBurn preferences |
| `sha256-d422e9886e29ed666c07` | 8,798 bytes | JPT MOPA device bundle |
Every digest and size is recorded in
[`evidence/public-lightburn-exports.jsonl`](../../evidence/public-lightburn-exports.jsonl).
Private paths, device GUIDs and local account data are intentionally omitted.
## Bundle structure
The complete bundle has eight entries: preferences, material-test presets,
three LightBurn device profiles, two material libraries and a bundle manifest.
Each device-specific bundle contains preferences, one device profile and a
manifest. All manifests declare bundle format `1.0` and LightBurn `2.1.03`.
## Device findings
| Display name | LightBurn backend | Connection | Work area | Attribution |
| --- | --- | --- | ---: | --- |
| `100 JPT E2 MOPA` | `JCZFiber` | `LibUSB` | 110 × 110 mm | Focus machine |
| `Eduard X1550` | `Ruida` | `Serial` | 1500 × 1000 mm | Makerspace CO2 machine |
| `Ruida 644XS` | `Ruida` | `CDM` | 500 × 300 mm | Unassigned; physical machine unknown |
The preferences contain four device records because the unassigned Ruida
profile occurs twice. This is not evidence for a fourth physical machine, and
the 500 × 300 mm profile must not be attributed to the Bodor or any other
machine without independent confirmation.
The JPT profile additionally records:
- a numeric laser type of `1` and fiber type of `3`;
- enabled Q-pulse-width control;
- profile frequency bounds stored as `1` and `4000`;
- `RequireFrameBeforeStart` enabled;
- 60 cut-list entries.
Numeric enums are preserved as observations. Their semantic labels are not
guessed from the numbers alone.
## MOPA process-setting evidence
Across the 60 exported cut-list entries:
- 35 contain `frequency`;
- 25 contain `QPulseWidth`;
- all 25 pulse-width entries also contain frequency;
- observed raw frequency values range from 30,000 to 1,000,000;
- observed `QPulseWidth` values range from 2 to 180.
LightBurn's official galvo cut-setting documentation presents frequency in
kHz and Q-Pulse Width in nanoseconds. The larger integers stored by the export
show that the serialized representation cannot simply be copied into a UI
field without an explicit conversion contract. The exact field name and unit
are now evidenced for a device-library export, but the representation inside
a `.lbrn2` project remains unconfirmed.
These observed recipe values and profile fields are **not certified hardware
limits or safe operating parameters**. They must not be used to probe the
machine. LightBurn likewise warns that incorrect galvo source settings can
damage a laser.
Sources:
- [LightBurn galvo-specific cut settings](https://docs.lightburnsoftware.com/2.2/Reference/CutSettingsEditor/GalvoSpecificCutSettings/)
- [LightBurn galvo source settings](https://docs.lightburnsoftware.com/latest/Reference/DeviceSettings/GalvoPorts/)
## Reuse impact
Rayforge's current LightBurn **project** importer retains raw frequency
metadata but maps neither frequency nor pulse width into workflow settings.
Its separate LightBurn **device** importer recognizes common profile fields,
but its driver map does not cover this observed `LibUSB`/`JCZFiber` pairing
and it does not map the JPT-specific settings or cut list.
The narrowest useful implementation is therefore an extension rather than a
new general parser:
1. add evidence-backed JCZ device-profile recognition;
2. preserve source units and raw values at the import boundary;
3. map frequency and `QPulseWidth` only after a real `.lbrn2` fixture confirms
project serialization;
4. validate imported values against a separately verified machine profile;
5. fail closed when units, profile identity or ranges are unknown.
## Remaining collection gaps
The exports settle the exact LightBurn release and the two named device
profiles. Phase 0 still needs:
- two or three known-working `.lbrn2` projects, especially a MOPA/hatch job;
- Device Settings and device-list screenshots;
- the closed-application LightBurn preferences directory;
- exterior machine, source, controller and installed-lens photographs;
- mounted-lens, connection, startup and interlock notes;
- the stock driver's identity and version on the control computer.
An About screenshot is still useful as visual corroboration, but the bundle
manifests already establish version `2.1.03`.

View file

@ -19,9 +19,14 @@ optics:
- width: 300 - width: 300
height: 300 height: 300
lightburn: lightburn:
version: null version: 2.1.03
build: null build: null
status: pending-collection device_name: 100 JPT E2 MOPA
backend: JCZFiber
connection: LibUSB
q_pulse_width_enabled: true
require_frame_before_start: true
status: bundle-export-observed
usb: usb:
vid: "9588" vid: "9588"
pid: "9899" pid: "9899"
@ -45,8 +50,16 @@ evidence:
- artifact_id: sha256-ebfe631958b9522533ff - artifact_id: sha256-ebfe631958b9522533ff
sha256: ebfe631958b9522533ffb1e78c8961073e3549039053ea0d56e4c82cfe8bff83 sha256: ebfe631958b9522533ffb1e78c8961073e3549039053ea0d56e4c82cfe8bff83
role: jcz-windows-driver-metadata role: jcz-windows-driver-metadata
- artifact_id: sha256-d422e9886e29ed666c07
sha256: d422e9886e29ed666c077a0e7bab9a6c3d53fc938365b7b773d79c529b55c0f0
role: lightburn-jpt-device-bundle
- artifact_id: sha256-88c762adcb8e9693c913
sha256: 88c762adcb8e9693c9132473a9d90c69eaf768b96f12e8af71e89adfb10b4cfa
role: lightburn-preferences-export
notes: notes:
- No complete serial numbers or private source locators are stored here. - No complete serial numbers or private source locators are stored here.
- Incorrect galvo or source configuration can damage hardware; values are not inferred by trial and error. - Incorrect galvo or source configuration can damage hardware; values are not inferred by trial and error.
- USB identity was confirmed by passive Windows PnP enumeration on 2026-10-17; no device endpoint was opened. - USB identity was confirmed by passive Windows PnP enumeration on 2026-10-17; no device endpoint was opened.
- 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.
- Exported recipe values are observations, not certified hardware limits or safe operating parameters.

View file

@ -14,9 +14,12 @@ speed and one pass. LightBurn's observed `frequency` and
However, the importer does not map frequency into the workflow layer settings. However, the importer does not map frequency into the workflow layer settings.
It maps only power, speed and passes from this fixture. The inspected Rayforge It maps only power, speed and passes from this fixture. The inspected Rayforge
LightBurn fixtures and importer contain no evidence-backed mapping for a MOPA LightBurn project fixtures and importer contain no evidence-backed mapping for
pulse-width field. Consequently, the imported job is explicitly classified as a MOPA pulse-width field. A later private LightBurn device-library export
unsafe for JCZ encoding rather than being completed with a guessed value. establishes the field name `QPulseWidth` and its nanosecond UI contract, but it
does not establish how that value is serialized in a `.lbrn2` project.
Consequently, the imported job is explicitly classified as unsafe for JCZ
encoding rather than being completed with a guessed mapping.
Two runs produced identical output with SHA-256 Two runs produced identical output with SHA-256
`9ebce7b5b3388abfeed024e25f8f46001260bf814da19808bfc83fb6554fc26e`. `9ebce7b5b3388abfeed024e25f8f46001260bf814da19808bfc83fb6554fc26e`.
@ -28,8 +31,12 @@ The golden result is `tests/fixtures/rayforge-lightburn-mopa-import.json`.
`overrideFrequency` elements. `overrideFrequency` elements.
- **Observed:** generic parsing retains these elements in source metadata. - **Observed:** generic parsing retains these elements in source metadata.
- **Observed:** `_build_step_config` does not map frequency or pulse width. - **Observed:** `_build_step_config` does not map frequency or pulse width.
- **Unknown:** the exact LightBurn field and unit for JPT MOPA pulse width in a - **Observed after this spike:** a LightBurn `2.1.03` device-library export
known-working galvo project. uses `QPulseWidth`; official LightBurn documentation defines Q-pulse width
in nanoseconds. See the
[device-export analysis](lightburn-device-exports-2026-10-19.md).
- **Unknown:** the exact `QPulseWidth` representation and omission/default
behavior in a known-working galvo `.lbrn2` project.
- **Not inferred:** no pulse-width tag, unit, default or machine limit was - **Not inferred:** no pulse-width tag, unit, default or machine limit was
invented for the public fixture. invented for the public fixture.
@ -38,9 +45,8 @@ The golden result is `tests/fixtures/rayforge-lightburn-mopa-import.json`.
Rayforge's existing LightBurn importer is useful for ordinary vector geometry Rayforge's existing LightBurn importer is useful for ordinary vector geometry
and common process settings, but it does not yet satisfy M1's MOPA parameter and common process settings, but it does not yet satisfy M1's MOPA parameter
survival requirement. The narrowest likely extension is an importer mapping survival requirement. The narrowest likely extension is an importer mapping
for frequency plus a separately evidenced pulse-width field, followed by an for frequency plus `QPulseWidth`, followed by an explicit unit conversion and
explicit validation gate. A private known-working LightBurn project is still validation gate. A private known-working LightBurn project is still needed to
needed to establish the pulse-width representation before implementing that establish project serialization before implementing that mapping.
mapping.
No USB enumeration, controller command, motion or emission was attempted. No USB enumeration, controller command, motion or emission was attempted.

View file

@ -1,6 +1,6 @@
# Product and research roadmap # Product and research roadmap
Last updated: 2026-10-18 Last updated: 2026-10-19
## Destination ## Destination
@ -46,7 +46,8 @@ evidence.
The collected installer snapshot establishes: The collected installer snapshot establishes:
- the JCZ/EZCAD2 controller and `Lmcv2u` driver family; - the JCZ/EZCAD2 controller and `Lmcv2u` driver family;
- vendor-declared USB VID/PID values, pending live confirmation; - vendor-declared USB VID/PID values, subsequently confirmed by passive live
Windows enumeration;
- known-good 110 mm and 300 mm configuration trees; - known-good 110 mm and 300 mm configuration trees;
- exact hashes for the opaque, lens-specific `markcfg7` profiles; - exact hashes for the opaque, lens-specific `markcfg7` profiles;
- a shared correction file and observable workspace differences; - a shared correction file and observable workspace differences;
@ -60,7 +61,13 @@ 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;
- the exact LightBurn version and known-working LightBurn device profile. - how a known-working `.lbrn2` project serializes JPT frequency and
`QPulseWidth` settings.
The later LightBurn export collection now establishes version `2.1.03`, a
110 x 110 mm `JCZFiber`/`LibUSB` JPT profile, and the `QPulseWidth` device
library field. See the
[device-export analysis](research/lightburn-device-exports-2026-10-19.md).
Consequently, treating a vendor DLL or opaque profile as a callable black box Consequently, treating a vendor DLL or opaque profile as a callable black box
would be premature and would make the safety boundary impossible to audit. would be premature and would make the safety boundary impossible to audit.
@ -147,10 +154,11 @@ 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 and retains
an observed LightBurn frequency value in raw source metadata. Frequency is not an observed LightBurn frequency value in raw source metadata. Frequency is not
mapped into workflow settings, and no evidence-backed pulse-width field was mapped into workflow settings. A later real device export identifies
found, so the job deliberately fails the JCZ encoding gate. A private `QPulseWidth` and its nanosecond UI contract, but project serialization remains
known-working galvo fixture is required before implementing the missing MOPA unknown, so the job still deliberately fails the JCZ encoding gate. A private
mapping. known-working `.lbrn2` fixture is required before implementing the missing
MOPA mapping.
The architecture decision remains proposed until these spikes produce The architecture decision remains proposed until these spikes produce
evidence. An independent app requires a documented gap analysis showing why evidence. An independent app requires a documented gap analysis showing why
@ -251,17 +259,22 @@ imply compatible safety or control behavior on either CO2 machine.
The next work package should be small enough to review in a few commits: The next work package should be small enough to review in a few commits:
1. collect the pending LightBurn data from the control PC as one local archive; 1. collect two or three known-working `.lbrn2` projects plus the remaining
2. run the stock MeerK40t mock-controller evaluation; device screenshots, preferences folder, photos and operator notes;
3. prove the Rayforge external-addon and binary-driver registration seam; 2. add a sanitized synthetic JCZ `.lbdev` fixture and characterize the exact
4. build one no-I/O Rayforge-to-JCZ encoder spike and golden trace comparison; gaps in Rayforge's device importer;
5. test Rayforge's existing LightBurn import against galvo/MOPA fixtures; 3. implement and test the smallest evidence-backed JCZ profile-import seam,
6. accept, amend or reject ADR 0002 from the results; keeping project `QPulseWidth` mapping gated until a real project confirms
7. separately produce a written M3 hardware-research protocol before any USB its serialization;
access or controller command. 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. separately produce a written M3 hardware-research protocol before any USB
endpoint access or controller command.
Items 2–6 can proceed while item 1 is pending. Hardware work cannot proceed Hardware work cannot proceed until item 6 is reviewed and the physical setup
until item 7 is reviewed and the physical setup is ready. is ready.
## Decision rules ## Decision rules

View file

@ -1,6 +1,6 @@
# Phase 0 status # Phase 0 status
Last updated: 2026-10-17 Last updated: 2026-10-19
## Completed ## Completed
@ -22,29 +22,36 @@ Last updated: 2026-10-17
description `USBLMCV4`, USB VID/PID `9588:9899`, vendor-specific class, and description `USBLMCV4`, USB VID/PID `9588:9899`, vendor-specific class, and
USB revision descriptor `0005`. No compatible function driver is installed USB revision descriptor `0005`. No compatible function driver is installed
on the development computer; no endpoint was opened. on the development computer; no endpoint was opened.
- Four LightBurn exports are preserved privately with SHA-256 manifests and
Git LFS. Their public evidence records contain no private source locators.
- All three bundle manifests identify LightBurn `2.1.03`.
- The JPT export confirms a `JCZFiber`/`LibUSB` device profile, 110 x 110 mm
work area, enabled Q-pulse-width control, `QPulseWidth` cut-list fields and
a require-frame-before-start setting.
- The Eduard export confirms a Ruida/Serial profile with a 1500 x 1000 mm work
area. A separate 500 x 300 mm Ruida profile remains physically unassigned.
- No vendor executable, plugin, DLL, or driver was run. No USB capture, framing, - No vendor executable, plugin, DLL, or driver was run. No USB capture, framing,
machine command, or laser output was performed. machine command, or laser output was performed.
## Pending external acquisition ## Pending external acquisition
The current development computer is not the LightBurn control computer: no These items remain pending from the actual control machine:
LightBurn executable or local LightBurn preferences directory was present.
Consequently, these items remain pending from the actual control machine:
- Help → About screenshot;
- full `.lbzip` bundle and `.lbprefs` export;
- closed-application preferences-folder archive; - closed-application preferences-folder archive;
- Device Settings screenshots; - Device Settings screenshots;
- known-working `.lbrn2` examples; - known-working `.lbrn2` examples;
- 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.
An About screenshot is useful corroboration, but the imported bundle manifests
already establish version `2.1.03`.
Collect them directly into a dated directory under PrivateStash by following 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 these control-machine artifacts are hashed, Phase 0 remains open until the safety- and interoperability-relevant remainder
committed, pushed, and represented by sanitized public evidence. is hashed, committed, pushed, and represented by sanitized public evidence.
The work that does not depend on this acquisition can proceed in parallel. The work that does not depend on this acquisition can proceed in parallel.
The [product and research roadmap](../roadmap.md) defines the offline application The [product and research roadmap](../roadmap.md) defines the offline application

View file

@ -0,0 +1,4 @@
{"analysis_refs":["docs/research/lightburn-device-exports-2026-10-19.md"],"artifact_id":"sha256-e16fdb0aa7c374c4c94d","classification":"private-sensitive","media_type":"application/zip","observed_at_utc":"2026-10-19T08:06:14Z","origin":"user-provided-local-lightburn-export","sha256":"e16fdb0aa7c374c4c94de9b4ecf4f0be1e1167f91c8f51517b060dde900880de","size_bytes":20309}
{"analysis_refs":["docs/research/lightburn-device-exports-2026-10-19.md"],"artifact_id":"sha256-fc6cf52f4ef815dd5037","classification":"private-sensitive","media_type":"application/zip","observed_at_utc":"2026-10-19T08:06:14Z","origin":"user-provided-local-lightburn-export","sha256":"fc6cf52f4ef815dd5037e11a893cefce4009b4965c11776d931e501ccf10a713","size_bytes":5720}
{"analysis_refs":["docs/research/lightburn-device-exports-2026-10-19.md"],"artifact_id":"sha256-88c762adcb8e9693c913","classification":"private-sensitive","media_type":"application/octet-stream","observed_at_utc":"2026-10-19T08:06:14Z","origin":"user-provided-local-lightburn-export","sha256":"88c762adcb8e9693c9132473a9d90c69eaf768b96f12e8af71e89adfb10b4cfa","size_bytes":97356}
{"analysis_refs":["docs/research/lightburn-device-exports-2026-10-19.md"],"artifact_id":"sha256-d422e9886e29ed666c07","classification":"private-sensitive","media_type":"application/zip","observed_at_utc":"2026-10-19T08:06:14Z","origin":"user-provided-local-lightburn-export","sha256":"d422e9886e29ed666c077a0e7bab9a6c3d53fc938365b7b773d79c529b55c0f0","size_bytes":8798}

View file

@ -8,6 +8,17 @@ ROOT = Path(__file__).resolve().parents[1]
class PublicEvidenceTests(unittest.TestCase): class PublicEvidenceTests(unittest.TestCase):
def _manifest_paths(self) -> list[Path]:
return sorted((ROOT / "evidence").glob("public-*.jsonl"))
def _entries(self) -> list[dict]:
return [
json.loads(line)
for path in self._manifest_paths()
for line in path.read_text(encoding="utf-8").splitlines()
if line
]
def test_public_evidence_matches_manifest_contract(self) -> None: def test_public_evidence_matches_manifest_contract(self) -> None:
schema = json.loads( schema = json.loads(
(ROOT / "schemas" / "artifact-manifest.schema.json").read_text(encoding="utf-8") (ROOT / "schemas" / "artifact-manifest.schema.json").read_text(encoding="utf-8")
@ -18,13 +29,10 @@ class PublicEvidenceTests(unittest.TestCase):
digest_pattern = re.compile(schema["properties"]["sha256"]["pattern"]) digest_pattern = re.compile(schema["properties"]["sha256"]["pattern"])
classifications = set(schema["properties"]["classification"]["enum"]) classifications = set(schema["properties"]["classification"]["enum"])
lines = (ROOT / "evidence" / "public-installer-configs.jsonl").read_text( paths = self._manifest_paths()
encoding="utf-8" self.assertGreaterEqual(len(paths), 2)
).splitlines()
self.assertEqual(len(lines), 9)
for line in lines: for entry in self._entries():
entry = json.loads(line)
self.assertFalse(required - set(entry)) self.assertFalse(required - set(entry))
self.assertFalse(set(entry) - allowed) self.assertFalse(set(entry) - allowed)
self.assertRegex(entry["artifact_id"], artifact_pattern) self.assertRegex(entry["artifact_id"], artifact_pattern)
@ -35,15 +43,21 @@ class PublicEvidenceTests(unittest.TestCase):
self.assertNotIn("source_locator", entry) self.assertNotIn("source_locator", entry)
def test_public_evidence_has_unique_content_ids(self) -> None: def test_public_evidence_has_unique_content_ids(self) -> None:
entries = [ entries = self._entries()
json.loads(line)
for line in (ROOT / "evidence" / "public-installer-configs.jsonl")
.read_text(encoding="utf-8")
.splitlines()
]
ids = [entry["artifact_id"] for entry in entries] ids = [entry["artifact_id"] for entry in entries]
self.assertEqual(len(ids), len(set(ids))) self.assertEqual(len(ids), len(set(ids)))
def test_expected_manifest_entry_counts(self) -> None:
expected = {
"public-installer-configs.jsonl": 9,
"public-lightburn-exports.jsonl": 4,
}
actual = {
path.name: len(path.read_text(encoding="utf-8").splitlines())
for path in self._manifest_paths()
}
self.assertEqual(actual, expected)
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()