build: pin upstream contracts and license notices
This commit is contained in:
parent
ffbff9805c
commit
415b00e157
19 changed files with 598 additions and 14 deletions
|
|
@ -107,6 +107,8 @@ project workspace or development destination.
|
||||||
- [Rayforge material-recipe model spike](docs/research/rayforge-recipe-model-spike.md)
|
- [Rayforge material-recipe model spike](docs/research/rayforge-recipe-model-spike.md)
|
||||||
- [M1 no-device build and acceptance checkpoint](docs/research/m1-no-device-acceptance.md)
|
- [M1 no-device build and acceptance checkpoint](docs/research/m1-no-device-acceptance.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)
|
||||||
|
- [Proposed project license and upstream boundary](docs/adr/0003-project-license-and-upstream-boundary.md)
|
||||||
|
- [Upstream contract and license audit](docs/research/upstream-contract-and-license-audit.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)
|
- [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)
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,10 @@ The repository now provides:
|
||||||
- a non-executing verifier for externally produced Windows artifacts;
|
- a non-executing verifier for externally produced Windows artifacts;
|
||||||
- a checksummed acceptance-kit generator containing only allowlisted public
|
- a checksummed acceptance-kit generator containing only allowlisted public
|
||||||
synthetic inputs;
|
synthetic inputs;
|
||||||
- a fail-closed manual desktop-acceptance report and validator.
|
- a fail-closed manual desktop-acceptance report and validator;
|
||||||
|
- pinned upstream contract/license manifests and a drift checker;
|
||||||
|
- a proposed MIT project-license decision in ADR 0003; Kaz has not accepted
|
||||||
|
it, so the repository remains unlicensed.
|
||||||
|
|
||||||
Run the ordinary checkpoint with the bundled Codex Python or another supported
|
Run the ordinary checkpoint with the bundled Codex Python or another supported
|
||||||
Python interpreter and a writable `TEMP`/`TMP`:
|
Python interpreter and a writable `TEMP`/`TMP`:
|
||||||
|
|
@ -55,6 +58,7 @@ order:
|
||||||
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, do not install a build environment here. The next
|
||||||
useful input is the private known-working `.lbrn2` collection. Until either
|
useful input is the private known-working `.lbrn2` collection or Kaz's explicit
|
||||||
external gate arrives, ADR 0002 remains Proposed and no controller backend,
|
decision on ADR 0003. Until either external gate arrives, ADR 0002 remains
|
||||||
USB discovery, physical framing, galvo motion or laser output is authorized.
|
Proposed and no controller backend, USB discovery, physical framing, galvo
|
||||||
|
motion or laser output is authorized.
|
||||||
|
|
|
||||||
|
|
@ -123,6 +123,10 @@ Subsequent offline evidence:
|
||||||
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. The packaged Windows GUI and a
|
||||||
known-working private project remain external gates.
|
known-working private project remain external gates.
|
||||||
|
- [Upstream contract and license audit](../research/upstream-contract-and-license-audit.md):
|
||||||
|
both pins and license notices are now machine-verifiable; the documented
|
||||||
|
addon lifecycle is usable, but driver registration and cleanup still rely
|
||||||
|
on an internal registry seam that should be replaced upstream.
|
||||||
|
|
||||||
These results satisfy the current model, buffering and no-device integration
|
These results satisfy the current model, buffering and no-device integration
|
||||||
proofs. They do not yet satisfy the packaged human-interaction or real-project
|
proofs. They do not yet satisfy the packaged human-interaction or real-project
|
||||||
|
|
|
||||||
63
docs/adr/0003-project-license-and-upstream-boundary.md
Normal file
63
docs/adr/0003-project-license-and-upstream-boundary.md
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
# ADR 0003: Project license and upstream attribution boundary
|
||||||
|
|
||||||
|
- Status: Proposed
|
||||||
|
- Date: 2026-10-21
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
The Pirate Ray is intended to become open source, but the repository currently
|
||||||
|
has no project license. Rayforge and MeerK40t are both pinned under MIT terms,
|
||||||
|
and the leading architecture is a Rayforge addon informed by BalorMK behavior.
|
||||||
|
ADR 0002 forbids copied upstream source until license compatibility and exact
|
||||||
|
attribution are recorded.
|
||||||
|
|
||||||
|
## Proposed decision
|
||||||
|
|
||||||
|
1. License original The Pirate Ray code under MIT.
|
||||||
|
2. Keep exact upstream repositories, revisions, license notices and contract
|
||||||
|
hashes in the public repository.
|
||||||
|
3. Include applicable notices in every source or binary distribution set.
|
||||||
|
4. Treat Rayforge's documented addon API as the preferred host boundary.
|
||||||
|
5. Isolate any unavoidable internal Rayforge registry access behind one
|
||||||
|
compatibility adapter and remove it when a supported upstream API exists.
|
||||||
|
6. Use BalorMK as a pinned behavioral oracle until a separately reviewed
|
||||||
|
decision authorizes adaptation of specific MIT-licensed implementation
|
||||||
|
files.
|
||||||
|
|
||||||
|
## Why MIT is proposed
|
||||||
|
|
||||||
|
MIT matches both principal upstreams, is compatible with contributing the
|
||||||
|
addon upstream, and imposes a small, auditable notice obligation. It does not
|
||||||
|
solve safety, warranty, trademark or third-party-data questions; those remain
|
||||||
|
separate project responsibilities.
|
||||||
|
|
||||||
|
## Consequences
|
||||||
|
|
||||||
|
- A root project `LICENSE` can be added only after Kaz accepts this ADR.
|
||||||
|
- Source provenance becomes part of code review and release acceptance.
|
||||||
|
- A Windows installer may not be published alone; its verified license
|
||||||
|
sidecar and checkpoint manifest are part of the distribution set.
|
||||||
|
- BalorMK code adaptation must identify source files and preserve the
|
||||||
|
MeerK40t notice.
|
||||||
|
- A Rayforge upgrade fails closed when a pinned license, source hash or
|
||||||
|
required symbol changes.
|
||||||
|
|
||||||
|
## Alternatives considered
|
||||||
|
|
||||||
|
- **Remain unlicensed:** acceptable for research snapshots but blocks the
|
||||||
|
intended open-source product and upstream code adaptation.
|
||||||
|
- **Apache-2.0:** provides an explicit patent grant but adds notice and license
|
||||||
|
complexity without a current upstream requirement.
|
||||||
|
- **GPL-family license:** may be appropriate for stronger reciprocity, but it
|
||||||
|
is a larger product-policy decision and is not required by the current MIT
|
||||||
|
dependencies.
|
||||||
|
|
||||||
|
## Acceptance
|
||||||
|
|
||||||
|
Kaz must explicitly choose the project license. Until then this ADR remains
|
||||||
|
Proposed, the README's no-license statement remains authoritative, and no root
|
||||||
|
`LICENSE` file is created.
|
||||||
|
|
||||||
|
See the
|
||||||
|
[upstream contract and license audit](../research/upstream-contract-and-license-audit.md)
|
||||||
|
for the evidence and API-stability assessment.
|
||||||
|
|
@ -51,11 +51,13 @@ python scripts/verify_rayforge_windows_checkpoint.py `
|
||||||
--output .build/artifacts/rayforge-windows-verification.json
|
--output .build/artifacts/rayforge-windows-verification.json
|
||||||
```
|
```
|
||||||
|
|
||||||
The verifier requires portable artifact names, checks sizes and SHA-256
|
The verifier requires checkpoint schema 2 and portable artifact names, checks
|
||||||
digests, rejects unsafe ZIP paths, confirms the pinned Rayforge commit and
|
sizes and SHA-256 digests, rejects unsafe ZIP paths, confirms the pinned
|
||||||
compares every packaged addon file with its recorded digest. It never executes
|
Rayforge commit, compares every packaged addon file with its recorded digest,
|
||||||
the application or installer, and a passing result retains the manual GUI
|
and requires the pinned Rayforge license both inside the ZIP and as a sidecar.
|
||||||
interaction gate.
|
It never executes the application or installer, and a passing result retains
|
||||||
|
the manual GUI interaction gate. The installer, manifest and license sidecar
|
||||||
|
form one distribution set.
|
||||||
|
|
||||||
## Automated acceptance
|
## Automated acceptance
|
||||||
|
|
||||||
|
|
|
||||||
91
docs/research/upstream-contract-and-license-audit.md
Normal file
91
docs/research/upstream-contract-and-license-audit.md
Normal file
|
|
@ -0,0 +1,91 @@
|
||||||
|
# Upstream contract and license audit
|
||||||
|
|
||||||
|
- Date: 2026-10-21
|
||||||
|
- Scope: pinned Rayforge and MeerK40t revisions
|
||||||
|
- Hardware access: none
|
||||||
|
- Legal status: engineering record, not legal advice
|
||||||
|
|
||||||
|
## Result
|
||||||
|
|
||||||
|
Both pinned checkouts declare MIT licensing and pass the machine-readable
|
||||||
|
contract check. Their exact repositories, commits, normalized license hashes,
|
||||||
|
source-file hashes and required symbols are recorded in
|
||||||
|
`upstreams/rayforge.json` and `upstreams/meerk40t.json`.
|
||||||
|
|
||||||
|
Run the check against separately obtained source trees:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
python scripts/check_upstream_contracts.py `
|
||||||
|
--rayforge <rayforge-checkout> `
|
||||||
|
--meerk40t <meerk40t-checkout> `
|
||||||
|
--output .build/artifacts/upstream-contracts.json
|
||||||
|
```
|
||||||
|
|
||||||
|
The current pinned trees pass. The command reads source and Git metadata only;
|
||||||
|
it imports neither project and cannot discover or open hardware.
|
||||||
|
|
||||||
|
## License observations
|
||||||
|
|
||||||
|
| Project | Pin | Declared license | Notice observation |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| Rayforge | `c28918c87f8273dd1d244f5a057addc192807fa5` | MIT | `pyproject.toml` names Samuel Abels as author; the pinned `LICENSE` text contains the MIT grant and warranty disclaimer but no explicit copyright line |
|
||||||
|
| MeerK40t | `d511944e08788e88077727f682957759884ff5ca` | MIT | The pinned notice states `Copyright (c) 2021 meerk40t` |
|
||||||
|
|
||||||
|
Exact notice copies live under `third_party/licenses/`. They are notices, not
|
||||||
|
a license grant for The Pirate Ray's own code. No upstream source is vendored
|
||||||
|
by adding them.
|
||||||
|
|
||||||
|
MIT permits use, modification and redistribution, but requires the copyright
|
||||||
|
and permission notice to accompany copies or substantial portions. Therefore:
|
||||||
|
|
||||||
|
- every Rayforge-based ZIP and installer distribution set must include the
|
||||||
|
pinned Rayforge notice;
|
||||||
|
- a distribution containing adapted or copied BalorMK/MeerK40t code must also
|
||||||
|
include the MeerK40t notice;
|
||||||
|
- attribution must name the upstream project and exact revision without
|
||||||
|
implying endorsement;
|
||||||
|
- The Pirate Ray still needs its own explicit license before original project
|
||||||
|
code is offered under open-source terms.
|
||||||
|
|
||||||
|
Checkpoint schema 2 now requires a verified Rayforge license sidecar and the
|
||||||
|
same notice inside the portable ZIP. The installer must be distributed with
|
||||||
|
its manifest and sidecar; installer-only publication is not accepted.
|
||||||
|
|
||||||
|
## API stability assessment
|
||||||
|
|
||||||
|
| Seam | Classification | Assessment |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| Rayforge addon API version 23, `hookimpl`, `on_unload` | Documented addon API | Suitable compatibility boundary at the pinned revision |
|
||||||
|
| `register_machines` hook | Documented but unusable for this driver today | The hook exists, but the supplied manager does not expose driver-class registration |
|
||||||
|
| `NoDeviceDriver`, `OpsEncoder`, `EncodedOutput`, `MachineCodeOpMap` | Imported host interfaces | Required by the addon; pin and contract-test them before upgrades |
|
||||||
|
| `register_driver`, `drivers`, `driver_by_classname` | Internal registry | Functional but unsuitable as a permanent public contract; `register_driver` is not exported in `__all__` and no symmetric unregister function exists |
|
||||||
|
| Addon-manager registry cleanup | Host lifecycle implementation | Other registries support addon-owned cleanup, but the driver registry is not part of that mechanism |
|
||||||
|
| BalorMK `GalvoController` and `MockConnection` | Application-internal reference/test seam | Valid as a pinned behavioral oracle; not treated as a stable library API |
|
||||||
|
|
||||||
|
The immediate upstream contribution should be a narrow, addon-aware driver
|
||||||
|
registry contract with symmetric registration and cleanup. Until upstream
|
||||||
|
accepts such a contract, all internal registry access must remain isolated in
|
||||||
|
one compatibility adapter and guarded by the hashes/symbol tests.
|
||||||
|
|
||||||
|
## Code-origin boundary
|
||||||
|
|
||||||
|
The current PirateRay addon is a no-device clean-room spike. It imports
|
||||||
|
Rayforge host interfaces and compares independently generated traces with the
|
||||||
|
pinned BalorMK mock behavior. It does not vendor Rayforge or MeerK40t source.
|
||||||
|
|
||||||
|
Before adapting any BalorMK implementation code:
|
||||||
|
|
||||||
|
1. accept a project license compatible with MIT;
|
||||||
|
2. record the exact source files and revisions used;
|
||||||
|
3. preserve the MeerK40t notice in source and distributions;
|
||||||
|
4. keep transport code separate from the independently tested encoder; and
|
||||||
|
5. review the resulting diff for copied comments, identifiers and substantial
|
||||||
|
structure rather than assuming a rewrite has no attribution duty.
|
||||||
|
|
||||||
|
## Recommendation requiring owner decision
|
||||||
|
|
||||||
|
MIT is the simplest project-license candidate because both principal
|
||||||
|
upstreams use MIT and the intended addon is meant for upstream contribution.
|
||||||
|
This audit does not select it. Kaz must explicitly approve the project license
|
||||||
|
before a root `LICENSE` file is added or upstream implementation code is
|
||||||
|
adapted.
|
||||||
|
|
@ -331,6 +331,10 @@ hashed screenshots stay outside the public repository pending sanitization.
|
||||||
A reproducible kit generator copies only allowlisted synthetic inputs into the
|
A reproducible kit generator copies only allowlisted synthetic inputs into the
|
||||||
ignored build area, records their hashes and refuses to overwrite an existing
|
ignored build area, records their hashes and refuses to overwrite an existing
|
||||||
test run.
|
test run.
|
||||||
|
The pinned upstream contracts and license notices are now machine-verifiable.
|
||||||
|
Rayforge's documented addon lifecycle is viable, but production driver
|
||||||
|
registration still needs a supported symmetric registry API; project license
|
||||||
|
selection remains an explicit owner decision in ADR 0003.
|
||||||
|
|
||||||
## Decision rules
|
## Decision rules
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,7 @@ evidence for:
|
||||||
the pinned BalorMK mock values;
|
the pinned BalorMK mock values;
|
||||||
- fail-closed profile and encoder negative cases;
|
- fail-closed profile and encoder negative cases;
|
||||||
- Rayforge workflow-model serialization and preview behavior for power,
|
- Rayforge workflow-model serialization and preview behavior for power,
|
||||||
frequency and pulse width.
|
frequency and pulse width;
|
||||||
- independent layer visibility/output state and ordered serialization;
|
- independent layer visibility/output state and ordered serialization;
|
||||||
- material-recipe matching plus a fail-closed PirateRay provenance and exact
|
- material-recipe matching plus a fail-closed PirateRay provenance and exact
|
||||||
machine/source/lens/correction compatibility gate;
|
machine/source/lens/correction compatibility gate;
|
||||||
|
|
@ -84,7 +84,10 @@ evidence for:
|
||||||
pass missing steps, connected hardware, changed evidence or stop conditions;
|
pass missing steps, connected hardware, changed evidence or stop conditions;
|
||||||
- a deterministic acceptance-kit generator that copies only four allowlisted
|
- a deterministic acceptance-kit generator that copies only four allowlisted
|
||||||
public synthetic inputs, hashes them and refuses to overwrite an existing
|
public synthetic inputs, hashes them and refuses to overwrite an existing
|
||||||
run directory.
|
run directory;
|
||||||
|
- pinned Rayforge and MeerK40t license/source contracts with drift tests, plus
|
||||||
|
checkpoint schema 2 requiring the Rayforge MIT notice in the portable ZIP
|
||||||
|
and beside the installer manifest.
|
||||||
|
|
||||||
The remaining M1 gates are deliberately explicit:
|
The remaining M1 gates are deliberately explicit:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -74,6 +74,25 @@ if (-not (Test-Path -LiteralPath $installer)) {
|
||||||
}
|
}
|
||||||
|
|
||||||
New-Item -ItemType Directory -Force -Path $ArtifactRoot | Out-Null
|
New-Item -ItemType Directory -Force -Path $ArtifactRoot | Out-Null
|
||||||
|
$licenseDirectory = Join-Path $bundleDirectory "THIRD_PARTY_LICENSES"
|
||||||
|
$rayforgeLicense = Join-Path $checkout "LICENSE"
|
||||||
|
$normalizedLicense = [IO.File]::ReadAllText($rayforgeLicense).Replace("`r`n", "`n").Replace("`r", "`n")
|
||||||
|
$normalizedLicenseBytes = [Text.Encoding]::UTF8.GetBytes($normalizedLicense)
|
||||||
|
$rayforgeLicenseHash = [Convert]::ToHexString(
|
||||||
|
[Security.Cryptography.SHA256]::HashData($normalizedLicenseBytes)
|
||||||
|
).ToLowerInvariant()
|
||||||
|
if ($rayforgeLicenseHash -ne $upstreamManifest.license_file.sha256_lf) {
|
||||||
|
throw "Pinned Rayforge license hash mismatch"
|
||||||
|
}
|
||||||
|
$rayforgeNotice = Join-Path $repositoryRoot "third_party\licenses\Rayforge-LICENSE.txt"
|
||||||
|
$bundledRayforgeLicense = Join-Path $licenseDirectory "Rayforge-LICENSE.txt"
|
||||||
|
$sidecarRayforgeLicense = Join-Path $ArtifactRoot "Rayforge-LICENSE.txt"
|
||||||
|
if ($PSCmdlet.ShouldProcess($licenseDirectory, "Stage Rayforge license notice")) {
|
||||||
|
New-Item -ItemType Directory -Force -Path $licenseDirectory | Out-Null
|
||||||
|
Copy-Item -LiteralPath $rayforgeNotice -Destination $bundledRayforgeLicense -Force
|
||||||
|
Copy-Item -LiteralPath $rayforgeNotice -Destination $sidecarRayforgeLicense -Force
|
||||||
|
}
|
||||||
|
|
||||||
$archive = Join-Path $ArtifactRoot "$bundleName.zip"
|
$archive = Join-Path $ArtifactRoot "$bundleName.zip"
|
||||||
if ($PSCmdlet.ShouldProcess($archive, "Create portable checkpoint archive")) {
|
if ($PSCmdlet.ShouldProcess($archive, "Create portable checkpoint archive")) {
|
||||||
Compress-Archive -LiteralPath $bundleDirectory -DestinationPath $archive -Force
|
Compress-Archive -LiteralPath $bundleDirectory -DestinationPath $archive -Force
|
||||||
|
|
@ -88,7 +107,7 @@ $addonFiles = Get-ChildItem -LiteralPath $addonSource -Recurse -File |
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$result = [ordered]@{
|
$result = [ordered]@{
|
||||||
schema_version = 1
|
schema_version = 2
|
||||||
checkpoint = "Rayforge Windows no-device bundle"
|
checkpoint = "Rayforge Windows no-device bundle"
|
||||||
rayforge_commit = $verifiedCommit
|
rayforge_commit = $verifiedCommit
|
||||||
addon = "the_pirate_ray_jcz"
|
addon = "the_pirate_ray_jcz"
|
||||||
|
|
@ -106,6 +125,15 @@ $result = [ordered]@{
|
||||||
sha256 = (Get-FileHash -Algorithm SHA256 -LiteralPath $installer).Hash.ToLowerInvariant()
|
sha256 = (Get-FileHash -Algorithm SHA256 -LiteralPath $installer).Hash.ToLowerInvariant()
|
||||||
bytes = (Get-Item -LiteralPath $installer).Length
|
bytes = (Get-Item -LiteralPath $installer).Length
|
||||||
}
|
}
|
||||||
|
license_files = @(
|
||||||
|
[ordered]@{
|
||||||
|
upstream = "Rayforge"
|
||||||
|
path = Split-Path -Leaf $sidecarRayforgeLicense
|
||||||
|
bundle_path = "THIRD_PARTY_LICENSES/Rayforge-LICENSE.txt"
|
||||||
|
sha256 = (Get-FileHash -Algorithm SHA256 -LiteralPath $sidecarRayforgeLicense).Hash.ToLowerInvariant()
|
||||||
|
bytes = (Get-Item -LiteralPath $sidecarRayforgeLicense).Length
|
||||||
|
}
|
||||||
|
)
|
||||||
manual_interaction_required = $true
|
manual_interaction_required = $true
|
||||||
}
|
}
|
||||||
$manifestPath = Join-Path $ArtifactRoot "rayforge-windows-checkpoint.json"
|
$manifestPath = Join-Path $ArtifactRoot "rayforge-windows-checkpoint.json"
|
||||||
|
|
|
||||||
127
scripts/check_upstream_contracts.py
Normal file
127
scripts/check_upstream_contracts.py
Normal file
|
|
@ -0,0 +1,127 @@
|
||||||
|
"""Verify pinned upstream licenses, source hashes, and required API symbols."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import ast
|
||||||
|
import hashlib
|
||||||
|
import json
|
||||||
|
import subprocess
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
ROOT = Path(__file__).resolve().parents[1]
|
||||||
|
|
||||||
|
|
||||||
|
def _sha256(path: Path) -> str:
|
||||||
|
return hashlib.sha256(path.read_bytes()).hexdigest()
|
||||||
|
|
||||||
|
|
||||||
|
def _text_sha256_lf(path: Path) -> str:
|
||||||
|
normalized = path.read_text(encoding="utf-8").replace("\r\n", "\n").replace("\r", "\n")
|
||||||
|
return hashlib.sha256(normalized.encode("utf-8")).hexdigest()
|
||||||
|
|
||||||
|
|
||||||
|
def _git_commit(path: Path) -> str:
|
||||||
|
return subprocess.run(
|
||||||
|
["git", "-C", str(path), "rev-parse", "HEAD"],
|
||||||
|
check=True,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
).stdout.strip()
|
||||||
|
|
||||||
|
|
||||||
|
def _symbols(path: Path) -> set[str]:
|
||||||
|
tree = ast.parse(path.read_text(encoding="utf-8"), filename=str(path))
|
||||||
|
symbols: set[str] = set()
|
||||||
|
for node in tree.body:
|
||||||
|
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)):
|
||||||
|
symbols.add(node.name)
|
||||||
|
if isinstance(node, ast.ClassDef):
|
||||||
|
for child in node.body:
|
||||||
|
if isinstance(child, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
||||||
|
symbols.add(f"{node.name}.{child.name}")
|
||||||
|
if isinstance(node, (ast.Assign, ast.AnnAssign)):
|
||||||
|
targets = node.targets if isinstance(node, ast.Assign) else [node.target]
|
||||||
|
for target in targets:
|
||||||
|
if isinstance(target, ast.Name):
|
||||||
|
symbols.add(target.id)
|
||||||
|
return symbols
|
||||||
|
|
||||||
|
|
||||||
|
def inspect_tree(manifest: dict, checkout: Path, observed_commit: str) -> dict:
|
||||||
|
failures = []
|
||||||
|
if observed_commit != manifest["commit"]:
|
||||||
|
failures.append(
|
||||||
|
f"commit mismatch: expected {manifest['commit']}, found {observed_commit}"
|
||||||
|
)
|
||||||
|
|
||||||
|
license_record = manifest["license_file"]
|
||||||
|
license_path = checkout / license_record["path"]
|
||||||
|
if not license_path.is_file():
|
||||||
|
failures.append(f"missing license file: {license_record['path']}")
|
||||||
|
elif _text_sha256_lf(license_path) != license_record["sha256_lf"]:
|
||||||
|
failures.append(f"license hash mismatch: {license_record['path']}")
|
||||||
|
|
||||||
|
contracts = []
|
||||||
|
for record in manifest["contract_files"]:
|
||||||
|
path = checkout / record["path"]
|
||||||
|
missing_symbols = []
|
||||||
|
if not path.is_file():
|
||||||
|
failures.append(f"missing contract file: {record['path']}")
|
||||||
|
else:
|
||||||
|
if _sha256(path) != record["sha256"]:
|
||||||
|
failures.append(f"contract hash mismatch: {record['path']}")
|
||||||
|
available = _symbols(path)
|
||||||
|
missing_symbols = sorted(set(record["required_symbols"]) - available)
|
||||||
|
if missing_symbols:
|
||||||
|
failures.append(
|
||||||
|
f"missing symbols in {record['path']}: {', '.join(missing_symbols)}"
|
||||||
|
)
|
||||||
|
contracts.append({
|
||||||
|
"path": record["path"],
|
||||||
|
"stability": record["stability"],
|
||||||
|
"required_symbols": record["required_symbols"],
|
||||||
|
"missing_symbols": missing_symbols,
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
"name": manifest["name"],
|
||||||
|
"repository": manifest["repository"],
|
||||||
|
"commit": observed_commit,
|
||||||
|
"license": manifest["license"],
|
||||||
|
"license_sha256_lf": license_record["sha256_lf"],
|
||||||
|
"status": "passed" if not failures else "failed",
|
||||||
|
"failures": failures,
|
||||||
|
"contracts": contracts,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument("--rayforge", type=Path, required=True)
|
||||||
|
parser.add_argument("--meerk40t", type=Path, required=True)
|
||||||
|
parser.add_argument("--output", type=Path, required=True)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
results = []
|
||||||
|
for name, checkout in (("rayforge", args.rayforge), ("meerk40t", args.meerk40t)):
|
||||||
|
manifest = json.loads(
|
||||||
|
(ROOT / "upstreams" / f"{name}.json").read_text(encoding="utf-8")
|
||||||
|
)
|
||||||
|
resolved = checkout.resolve()
|
||||||
|
results.append(inspect_tree(manifest, resolved, _git_commit(resolved)))
|
||||||
|
report = {
|
||||||
|
"status": "passed" if all(item["status"] == "passed" for item in results) else "failed",
|
||||||
|
"hardware_access": False,
|
||||||
|
"upstreams": results,
|
||||||
|
}
|
||||||
|
rendered = json.dumps(report, indent=2, sort_keys=True) + "\n"
|
||||||
|
args.output.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
args.output.write_text(rendered, encoding="utf-8", newline="\n")
|
||||||
|
print(f"status={report['status']}")
|
||||||
|
return 0 if report["status"] == "passed" else 1
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
|
|
@ -21,6 +21,11 @@ def _digest(data: bytes) -> str:
|
||||||
return hashlib.sha256(data).hexdigest()
|
return hashlib.sha256(data).hexdigest()
|
||||||
|
|
||||||
|
|
||||||
|
def _text_digest_lf(data: bytes) -> str:
|
||||||
|
normalized = data.decode("utf-8").replace("\r\n", "\n").replace("\r", "\n")
|
||||||
|
return hashlib.sha256(normalized.encode("utf-8")).hexdigest()
|
||||||
|
|
||||||
|
|
||||||
def _artifact_path(root: Path, value: str) -> Path:
|
def _artifact_path(root: Path, value: str) -> Path:
|
||||||
relative = PurePosixPath(value)
|
relative = PurePosixPath(value)
|
||||||
if relative.is_absolute() or ".." in relative.parts or len(relative.parts) != 1:
|
if relative.is_absolute() or ".." in relative.parts or len(relative.parts) != 1:
|
||||||
|
|
@ -42,7 +47,7 @@ def _check_file(root: Path, record: dict, label: str) -> Path:
|
||||||
def verify(manifest_path: Path) -> dict:
|
def verify(manifest_path: Path) -> dict:
|
||||||
manifest_path = manifest_path.resolve()
|
manifest_path = manifest_path.resolve()
|
||||||
manifest = json.loads(manifest_path.read_text(encoding="utf-8-sig"))
|
manifest = json.loads(manifest_path.read_text(encoding="utf-8-sig"))
|
||||||
if manifest.get("schema_version") != 1:
|
if manifest.get("schema_version") != 2:
|
||||||
raise ValueError("unsupported checkpoint schema")
|
raise ValueError("unsupported checkpoint schema")
|
||||||
if manifest.get("rayforge_commit") != UPSTREAM["commit"]:
|
if manifest.get("rayforge_commit") != UPSTREAM["commit"]:
|
||||||
raise ValueError("Rayforge commit does not match the repository pin")
|
raise ValueError("Rayforge commit does not match the repository pin")
|
||||||
|
|
@ -54,6 +59,13 @@ def verify(manifest_path: Path) -> dict:
|
||||||
artifact_root = manifest_path.parent
|
artifact_root = manifest_path.parent
|
||||||
bundle = _check_file(artifact_root, manifest["bundle"], "bundle")
|
bundle = _check_file(artifact_root, manifest["bundle"], "bundle")
|
||||||
installer = _check_file(artifact_root, manifest["installer"], "installer")
|
installer = _check_file(artifact_root, manifest["installer"], "installer")
|
||||||
|
license_records = manifest.get("license_files", [])
|
||||||
|
if len(license_records) != 1 or license_records[0].get("upstream") != "Rayforge":
|
||||||
|
raise ValueError("checkpoint must include the Rayforge license sidecar")
|
||||||
|
license_path = _check_file(artifact_root, license_records[0], "Rayforge license")
|
||||||
|
expected_license_hash = UPSTREAM["license_file"]["sha256_lf"]
|
||||||
|
if _text_digest_lf(license_path.read_bytes()) != expected_license_hash:
|
||||||
|
raise ValueError("Rayforge license does not match the pinned upstream")
|
||||||
packaged_root = PurePosixPath(manifest["addon_packaged_root"])
|
packaged_root = PurePosixPath(manifest["addon_packaged_root"])
|
||||||
if packaged_root.is_absolute() or ".." in packaged_root.parts:
|
if packaged_root.is_absolute() or ".." in packaged_root.parts:
|
||||||
raise ValueError("unsafe packaged addon root")
|
raise ValueError("unsafe packaged addon root")
|
||||||
|
|
@ -70,6 +82,15 @@ def verify(manifest_path: Path) -> dict:
|
||||||
executable = manifest["bundle"]["path"].removesuffix(".zip") + ".exe"
|
executable = manifest["bundle"]["path"].removesuffix(".zip") + ".exe"
|
||||||
if not any(path.name == executable for path in files):
|
if not any(path.name == executable for path in files):
|
||||||
raise ValueError("bundle application executable is missing")
|
raise ValueError("bundle application executable is missing")
|
||||||
|
bundle_license = PurePosixPath(license_records[0]["bundle_path"])
|
||||||
|
license_matches = [
|
||||||
|
path for path in files
|
||||||
|
if tuple(path.parts[-len(bundle_license.parts):]) == bundle_license.parts
|
||||||
|
]
|
||||||
|
if len(license_matches) != 1:
|
||||||
|
raise ValueError("Rayforge license is missing from the portable bundle")
|
||||||
|
if _text_digest_lf(archive.read(files[license_matches[0]])) != expected_license_hash:
|
||||||
|
raise ValueError("bundled Rayforge license does not match the pinned upstream")
|
||||||
|
|
||||||
verified_addon_files = 0
|
verified_addon_files = 0
|
||||||
for record in manifest["addon_source_files"]:
|
for record in manifest["addon_source_files"]:
|
||||||
|
|
@ -100,6 +121,7 @@ def verify(manifest_path: Path) -> dict:
|
||||||
"verified_addon_files": verified_addon_files,
|
"verified_addon_files": verified_addon_files,
|
||||||
"manual_interaction_required": True,
|
"manual_interaction_required": True,
|
||||||
"installer_name": installer.name,
|
"installer_name": installer.name,
|
||||||
|
"license_sidecar": license_path.name,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,9 @@ class M1AcceptancePipelineTests(unittest.TestCase):
|
||||||
self.assertIn("rayforge-addon-the-pirate-ray-jcz", source)
|
self.assertIn("rayforge-addon-the-pirate-ray-jcz", source)
|
||||||
self.assertIn("Compress-Archive", source)
|
self.assertIn("Compress-Archive", source)
|
||||||
self.assertIn("Get-FileHash -Algorithm SHA256", source)
|
self.assertIn("Get-FileHash -Algorithm SHA256", source)
|
||||||
|
self.assertIn("schema_version = 2", source)
|
||||||
|
self.assertIn("Rayforge-LICENSE.txt", source)
|
||||||
|
self.assertIn("license_files", 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()
|
||||||
|
|
@ -60,6 +63,7 @@ class M1AcceptancePipelineTests(unittest.TestCase):
|
||||||
self.assertIn("zipfile.ZipFile", source)
|
self.assertIn("zipfile.ZipFile", source)
|
||||||
self.assertIn("Rayforge commit does not match", source)
|
self.assertIn("Rayforge commit does not match", source)
|
||||||
self.assertIn('"executed_binaries": False', source)
|
self.assertIn('"executed_binaries": False', source)
|
||||||
|
self.assertIn("license sidecar", source)
|
||||||
self.assertNotIn("subprocess", source)
|
self.assertNotIn("subprocess", source)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
87
tests/test_upstream_contracts.py
Normal file
87
tests/test_upstream_contracts.py
Normal file
|
|
@ -0,0 +1,87 @@
|
||||||
|
import copy
|
||||||
|
import hashlib
|
||||||
|
import json
|
||||||
|
import tempfile
|
||||||
|
import unittest
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from scripts.check_upstream_contracts import ROOT, inspect_tree
|
||||||
|
|
||||||
|
|
||||||
|
class UpstreamContractTests(unittest.TestCase):
|
||||||
|
def _tree(self, root: Path) -> tuple[dict, str]:
|
||||||
|
license_path = root / "LICENSE"
|
||||||
|
api_path = root / "package" / "api.py"
|
||||||
|
api_path.parent.mkdir(parents=True)
|
||||||
|
license_path.write_text("synthetic MIT text\n", encoding="utf-8")
|
||||||
|
api_path.write_text(
|
||||||
|
"VERSION = 3\nclass Registry:\n def unregister(self):\n pass\n",
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
commit = "a" * 40
|
||||||
|
manifest = {
|
||||||
|
"name": "Synthetic upstream",
|
||||||
|
"repository": "https://example.invalid/upstream.git",
|
||||||
|
"commit": commit,
|
||||||
|
"license": "MIT",
|
||||||
|
"license_file": {
|
||||||
|
"path": "LICENSE",
|
||||||
|
"sha256_lf": hashlib.sha256(b"synthetic MIT text\n").hexdigest(),
|
||||||
|
},
|
||||||
|
"contract_files": [{
|
||||||
|
"path": "package/api.py",
|
||||||
|
"sha256": hashlib.sha256(api_path.read_bytes()).hexdigest(),
|
||||||
|
"stability": "documented-api",
|
||||||
|
"required_symbols": ["VERSION", "Registry", "Registry.unregister"],
|
||||||
|
}],
|
||||||
|
}
|
||||||
|
return manifest, commit
|
||||||
|
|
||||||
|
def test_matching_tree_passes(self):
|
||||||
|
with tempfile.TemporaryDirectory(dir=ROOT / ".build") as directory:
|
||||||
|
root = Path(directory)
|
||||||
|
manifest, commit = self._tree(root)
|
||||||
|
result = inspect_tree(manifest, root, commit)
|
||||||
|
self.assertEqual(result["status"], "passed")
|
||||||
|
self.assertEqual(result["failures"], [])
|
||||||
|
|
||||||
|
def test_commit_hash_and_symbol_drift_fail(self):
|
||||||
|
with tempfile.TemporaryDirectory(dir=ROOT / ".build") as directory:
|
||||||
|
root = Path(directory)
|
||||||
|
manifest, _ = self._tree(root)
|
||||||
|
changed = copy.deepcopy(manifest)
|
||||||
|
changed["contract_files"][0]["required_symbols"].append("Registry.missing")
|
||||||
|
result = inspect_tree(changed, root, "b" * 40)
|
||||||
|
self.assertEqual(result["status"], "failed")
|
||||||
|
self.assertTrue(any("commit mismatch" in item for item in result["failures"]))
|
||||||
|
self.assertTrue(any("Registry.missing" in item for item in result["failures"]))
|
||||||
|
|
||||||
|
def test_license_drift_fails(self):
|
||||||
|
with tempfile.TemporaryDirectory(dir=ROOT / ".build") as directory:
|
||||||
|
root = Path(directory)
|
||||||
|
manifest, commit = self._tree(root)
|
||||||
|
(root / "LICENSE").write_text("changed\n", encoding="utf-8")
|
||||||
|
result = inspect_tree(manifest, root, commit)
|
||||||
|
self.assertEqual(result["status"], "failed")
|
||||||
|
self.assertTrue(any("license hash mismatch" in item for item in result["failures"]))
|
||||||
|
|
||||||
|
def test_real_manifests_pin_mit_license_and_stability(self):
|
||||||
|
for name in ("rayforge", "meerk40t"):
|
||||||
|
manifest = json.loads(
|
||||||
|
(ROOT / "upstreams" / f"{name}.json").read_text(encoding="utf-8")
|
||||||
|
)
|
||||||
|
self.assertEqual(manifest["license"], "MIT")
|
||||||
|
self.assertEqual(len(manifest["commit"]), 40)
|
||||||
|
self.assertEqual(len(manifest["license_file"]["sha256_lf"]), 64)
|
||||||
|
self.assertTrue(manifest["contract_files"])
|
||||||
|
self.assertTrue(all(item["stability"] for item in manifest["contract_files"]))
|
||||||
|
notice = ROOT / "third_party" / "licenses" / f"{manifest['name']}-LICENSE.txt"
|
||||||
|
normalized = notice.read_text(encoding="utf-8").replace("\r\n", "\n").replace("\r", "\n")
|
||||||
|
self.assertEqual(
|
||||||
|
hashlib.sha256(normalized.encode("utf-8")).hexdigest(),
|
||||||
|
manifest["license_file"]["sha256_lf"],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
|
|
@ -19,17 +19,28 @@ class WindowsCheckpointVerifierTests(unittest.TestCase):
|
||||||
"rayforge-addon.yaml": b"name: the_pirate_ray_jcz\n",
|
"rayforge-addon.yaml": b"name: the_pirate_ray_jcz\n",
|
||||||
"pirate_ray_jcz/worker.py": b"TRANSPORT = None\n",
|
"pirate_ray_jcz/worker.py": b"TRANSPORT = None\n",
|
||||||
}
|
}
|
||||||
|
upstream = json.loads(
|
||||||
|
(ROOT / "upstreams" / "rayforge.json").read_text(encoding="utf-8")
|
||||||
|
)
|
||||||
|
license_data = (ROOT / "third_party" / "licenses" / "Rayforge-LICENSE.txt").read_bytes()
|
||||||
|
self.assertEqual(hashlib.sha256(license_data).hexdigest(), upstream["license_file"]["sha256_lf"])
|
||||||
bundle = root / bundle_name
|
bundle = root / bundle_name
|
||||||
with zipfile.ZipFile(bundle, "w") as archive:
|
with zipfile.ZipFile(bundle, "w") as archive:
|
||||||
archive.writestr(f"{bundle_name[:-4]}/{executable}", b"synthetic")
|
archive.writestr(f"{bundle_name[:-4]}/{executable}", b"synthetic")
|
||||||
for relative, data in addon_files.items():
|
for relative, data in addon_files.items():
|
||||||
archive.writestr(f"{bundle_name[:-4]}/_internal/{ADDON_ROOT}/{relative}", data)
|
archive.writestr(f"{bundle_name[:-4]}/_internal/{ADDON_ROOT}/{relative}", data)
|
||||||
|
archive.writestr(
|
||||||
|
f"{bundle_name[:-4]}/THIRD_PARTY_LICENSES/Rayforge-LICENSE.txt",
|
||||||
|
license_data,
|
||||||
|
)
|
||||||
if unsafe_member:
|
if unsafe_member:
|
||||||
archive.writestr(unsafe_member, b"unsafe")
|
archive.writestr(unsafe_member, b"unsafe")
|
||||||
installer = root / f"{bundle_name[:-4]}-installer.exe"
|
installer = root / f"{bundle_name[:-4]}-installer.exe"
|
||||||
installer.write_bytes(b"synthetic installer, never executed")
|
installer.write_bytes(b"synthetic installer, never executed")
|
||||||
|
license_sidecar = root / "Rayforge-LICENSE.txt"
|
||||||
|
license_sidecar.write_bytes(license_data)
|
||||||
manifest = {
|
manifest = {
|
||||||
"schema_version": 1,
|
"schema_version": 2,
|
||||||
"checkpoint": "Rayforge Windows no-device bundle",
|
"checkpoint": "Rayforge Windows no-device bundle",
|
||||||
"rayforge_commit": UPSTREAM["commit"],
|
"rayforge_commit": UPSTREAM["commit"],
|
||||||
"addon": "the_pirate_ray_jcz",
|
"addon": "the_pirate_ray_jcz",
|
||||||
|
|
@ -50,6 +61,13 @@ class WindowsCheckpointVerifierTests(unittest.TestCase):
|
||||||
"sha256": hashlib.sha256(installer.read_bytes()).hexdigest(),
|
"sha256": hashlib.sha256(installer.read_bytes()).hexdigest(),
|
||||||
"bytes": installer.stat().st_size,
|
"bytes": installer.stat().st_size,
|
||||||
},
|
},
|
||||||
|
"license_files": [{
|
||||||
|
"upstream": "Rayforge",
|
||||||
|
"path": license_sidecar.name,
|
||||||
|
"bundle_path": "THIRD_PARTY_LICENSES/Rayforge-LICENSE.txt",
|
||||||
|
"sha256": hashlib.sha256(license_data).hexdigest(),
|
||||||
|
"bytes": len(license_data),
|
||||||
|
}],
|
||||||
"manual_interaction_required": True,
|
"manual_interaction_required": True,
|
||||||
}
|
}
|
||||||
path = root / "rayforge-windows-checkpoint.json"
|
path = root / "rayforge-windows-checkpoint.json"
|
||||||
|
|
@ -86,6 +104,14 @@ class WindowsCheckpointVerifierTests(unittest.TestCase):
|
||||||
with self.assertRaisesRegex(ValueError, "one filename"):
|
with self.assertRaisesRegex(ValueError, "one filename"):
|
||||||
verify(manifest)
|
verify(manifest)
|
||||||
|
|
||||||
|
def test_missing_license_sidecar_is_rejected(self):
|
||||||
|
with tempfile.TemporaryDirectory(dir=ROOT / ".build") as directory:
|
||||||
|
manifest = self._checkpoint(Path(directory))
|
||||||
|
data = json.loads(manifest.read_text(encoding="utf-8"))
|
||||||
|
(manifest.parent / data["license_files"][0]["path"]).unlink()
|
||||||
|
with self.assertRaises(FileNotFoundError):
|
||||||
|
verify(manifest)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
||||||
15
third_party/README.md
vendored
Normal file
15
third_party/README.md
vendored
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
# Third-party notices
|
||||||
|
|
||||||
|
This directory contains exact license notices for pinned upstream projects
|
||||||
|
used by the offline research and integration work. It does not vendor their
|
||||||
|
source code.
|
||||||
|
|
||||||
|
| Project | Pinned revision | License notice |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| Rayforge | `c28918c87f8273dd1d244f5a057addc192807fa5` | `licenses/Rayforge-LICENSE.txt` |
|
||||||
|
| MeerK40t | `d511944e08788e88077727f682957759884ff5ca` | `licenses/MeerK40t-LICENSE.txt` |
|
||||||
|
|
||||||
|
The expected UTF-8/LF-normalized hashes and source repositories are recorded in
|
||||||
|
`upstreams/rayforge.json` and `upstreams/meerk40t.json`. Inclusion of a notice
|
||||||
|
does not mean upstream source is included in The Pirate Ray, nor does it select
|
||||||
|
a license for The Pirate Ray's own code.
|
||||||
21
third_party/licenses/MeerK40t-LICENSE.txt
vendored
Normal file
21
third_party/licenses/MeerK40t-LICENSE.txt
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2021 meerk40t
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
17
third_party/licenses/Rayforge-LICENSE.txt
vendored
Normal file
17
third_party/licenses/Rayforge-LICENSE.txt
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
27
upstreams/meerk40t.json
Normal file
27
upstreams/meerk40t.json
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
{
|
||||||
|
"name": "MeerK40t",
|
||||||
|
"repository": "https://github.com/meerk40t/meerk40t.git",
|
||||||
|
"commit": "d511944e08788e88077727f682957759884ff5ca",
|
||||||
|
"license": "MIT",
|
||||||
|
"license_file": {
|
||||||
|
"path": "LICENSE",
|
||||||
|
"sha256_lf": "b0cb1df6963e70b7dc3735244360ef6686c4d2aa6288b326a0f7c93014f39cd6",
|
||||||
|
"copyright_notice_present": true,
|
||||||
|
"notice": "Copyright (c) 2021 meerk40t"
|
||||||
|
},
|
||||||
|
"contract_files": [
|
||||||
|
{
|
||||||
|
"path": "meerk40t/balormk/controller.py",
|
||||||
|
"sha256": "257f42e1b48b7cff45df63e2ad6db4becb87140c61a1e04f1168a5edabc11fcf",
|
||||||
|
"stability": "application-internal-reference",
|
||||||
|
"required_symbols": ["GalvoController", "GalvoController.connect_if_needed", "GalvoController.send", "listEndOfList", "listMarkTo", "listQSwitchPeriod"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "meerk40t/balormk/mock_connection.py",
|
||||||
|
"sha256": "6c5f361a2474bc5da2660809029ee01f6f8723fe87e7cceb79ce69e80872aad2",
|
||||||
|
"stability": "application-internal-test-seam",
|
||||||
|
"required_symbols": ["MockConnection", "MockConnection.set_implied_response"]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"purpose": "Pinned BalorMK reference and forced-mock oracle for offline JCZ validation"
|
||||||
|
}
|
||||||
|
|
@ -3,5 +3,42 @@
|
||||||
"repository": "https://github.com/barebaric/rayforge.git",
|
"repository": "https://github.com/barebaric/rayforge.git",
|
||||||
"commit": "c28918c87f8273dd1d244f5a057addc192807fa5",
|
"commit": "c28918c87f8273dd1d244f5a057addc192807fa5",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"license_file": {
|
||||||
|
"path": "LICENSE",
|
||||||
|
"sha256_lf": "f9c4c77baa3828004ee54b8a4f2db2e88ed44a6237a493965bf551fac0fcb62d",
|
||||||
|
"copyright_notice_present": false
|
||||||
|
},
|
||||||
|
"contract_files": [
|
||||||
|
{
|
||||||
|
"path": "rayforge/core/hooks.py",
|
||||||
|
"sha256": "e9f488361f120a848e89dd25eb122f9aedfb2313bfce8e60f1d252d92df7cfdc",
|
||||||
|
"stability": "documented-addon-api",
|
||||||
|
"required_symbols": ["MINIMUM_API_VERSION", "PLUGIN_API_VERSION", "RayforgeSpecs.on_unload", "RayforgeSpecs.register_machines"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "rayforge/machine/driver/__init__.py",
|
||||||
|
"sha256": "06e733b8814118e3b3bfda28a6b2d6d157c5562802bd5a2294574f3b305488ef",
|
||||||
|
"stability": "internal-registry",
|
||||||
|
"required_symbols": ["drivers", "driver_by_classname", "register_driver", "get_driver_cls"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "rayforge/machine/driver/driver.py",
|
||||||
|
"sha256": "8114ec5d2cf63a4eb98eea3db68d612fb00f1ca7add89f433c8f90758f103a5e",
|
||||||
|
"stability": "imported-host-interface",
|
||||||
|
"required_symbols": ["Driver"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "rayforge/pipeline/encoder/base.py",
|
||||||
|
"sha256": "b18f87dc57655379ebdbce4c64b0472b589c7182b3a20423fa93ef22c9393734",
|
||||||
|
"stability": "imported-host-interface",
|
||||||
|
"required_symbols": ["EncodedOutput", "MachineCodeOpMap", "OpsEncoder"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "rayforge/addon_mgr/addon_manager.py",
|
||||||
|
"sha256": "6f734a0246f35a7f18d8e4fa60b38f66193d9a5ece0982829cc269133e6ddd0b",
|
||||||
|
"stability": "host-lifecycle-implementation",
|
||||||
|
"required_symbols": ["AddonManager", "AddonManager._unregister_addon_items"]
|
||||||
|
}
|
||||||
|
],
|
||||||
"purpose": "Pinned host application for offline reuse and JCZ addon validation"
|
"purpose": "Pinned host application for offline reuse and JCZ addon validation"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue