refactor: isolate Rayforge driver registry seam

This commit is contained in:
Codex 2026-10-21 15:18:50 +02:00
parent 415b00e157
commit fe2625e423
12 changed files with 292 additions and 28 deletions

View file

@ -21,6 +21,7 @@ The repository now provides:
synthetic inputs;
- a fail-closed manual desktop-acceptance report and validator;
- pinned upstream contract/license manifests and a drift checker;
- 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
it, so the repository remains unlicensed.

View file

@ -58,6 +58,10 @@ 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.
The compatibility-adapter part of item 5 is implemented for the offline addon
and fails closed outside Rayforge addon API 23. This implementation evidence
does not accept this ADR or select the project license.
See the
[upstream contract and license audit](../research/upstream-contract-and-license-audit.md)
for the evidence and API-stability assessment.

View file

@ -13,7 +13,7 @@ An external addon loaded through Rayforge's real `AddonManager` and registered
discovery specification or transport, and returns a recognizable binary test
payload from an `OpsEncoder`.
The following lifecycle completed twice with identical JSON results:
The following lifecycle completes against the pinned Rayforge checkout:
1. load the external addon;
2. resolve the driver by class name;
@ -21,12 +21,12 @@ The following lifecycle completed twice with identical JSON results:
4. disable the addon and verify driver removal;
5. re-enable it and verify re-registration;
6. reload it and verify there is exactly one registry entry;
7. package its three source/manifest files into a ZIP; and
7. package its source and manifest files into a ZIP; and
8. disable it cleanly.
The two result files had SHA-256
`610f8f191526afe4685f8659c1ef2b34fac3c68a8f8088d77ca5e1fdd6080bfc`.
The checked result is `tests/fixtures/rayforge-addon-spike.json`.
The current checked result is `tests/fixtures/rayforge-addon-spike.json` with
SHA-256
`c7a7ba7d561af58829c9c0fc271234e7600d50cca5f8125fc574fa3f0a1aa25f`.
## Registration seam finding
@ -37,10 +37,13 @@ a driver class. The source-level driver registry instead exposes
`rayforge.machine.driver.register_driver(driver)`.
The working spike therefore registers during import of the addon's worker
entry point and removes its class in `on_unload`. This survives load, disable,
enable and reload, but it relies on mutable module-level registry internals for
cleanup. Before a production addon, Rayforge should ideally expose a dedicated
addon-aware driver registry hook with supported unregister behavior.
entry point and removes its class in `on_unload`. All access to the mutable
module-level registry is now isolated in `rayforge_compat.py`. The adapter
accepts only addon API 23, checks the expected registry shape, refuses class
name collisions, is idempotent, and verifies symmetric cleanup. Contract drift
therefore fails closed instead of silently replacing another driver. Before a
production addon, Rayforge should still expose a dedicated addon-aware driver
registry hook with supported unregister behavior.
## Reproduce
@ -67,8 +70,9 @@ The script refuses any Rayforge revision other than the pinned commit.
- The ZIP check is addon packaging, not a proof that a complete PyInstaller or
MSIX Rayforge build discovers an externally installed addon.
- The current registration seam is functional but not a stable, addon-aware
API. Upstream feasibility should be assessed before relying on it.
- The compatibility adapter contains the unstable seam, but does not make it
a supported addon API. Upstream feasibility should be assessed before
relying on it in production.
- No JCZ command encoding, machine profile, UI, project reload, discovery,
transport, controller status or cancellation is implemented here.
- Nothing in this spike authorizes USB access, galvo movement or emission.

View file

@ -65,7 +65,10 @@ its manifest and sidecar; installer-only publication is not accepted.
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.
one compatibility adapter and guarded by the hashes/symbol tests. The current
addon now does this: its API-23 adapter rejects version drift, malformed
registries, duplicate entries and foreign class-name collisions, and its
load/disable/enable/reload lifecycle passes against the pinned checkout.
## Code-origin boundary

View file

@ -333,8 +333,9 @@ ignored build area, records their hashes and refuses to overwrite an existing
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.
registration still needs a supported symmetric registry API. The current
internal access is contained by a fail-closed API-23 compatibility adapter;
project license selection remains an explicit owner decision in ADR 0003.
## Decision rules

View file

@ -87,7 +87,9 @@ evidence for:
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.
and beside the installer manifest;
- a single fail-closed Rayforge API-23 compatibility adapter around the
internal driver registry, with collision, drift and symmetric-cleanup tests.
The remaining M1 gates are deliberately explicit:

View file

@ -113,7 +113,9 @@ def run(source: Path, addon_path: Path) -> dict[str, object]:
),
"lifecycle": ["load", "disable", "enable", "reload", "disable"],
"packaged_files": packaged_files,
"registration_seam": "worker import side effect + on_unload cleanup",
"registration_seam": (
"fail-closed API 23 compatibility adapter + on_unload cleanup"
),
}

View file

@ -0,0 +1,127 @@
"""Fail-closed compatibility boundary for Rayforge's driver registry.
Rayforge API 23 does not expose addon-aware driver registration and cleanup.
Keep the unavoidable internal registry access here so it can be removed when
the host provides a supported symmetric API.
"""
from typing import Any
SUPPORTED_PLUGIN_API_VERSION = 23
class RayforgeCompatibilityError(RuntimeError):
"""The pinned Rayforge registry contract is unavailable or has drifted."""
def _resolve_host() -> tuple[Any, int]:
from rayforge.core.hooks import PLUGIN_API_VERSION
from rayforge.machine import driver as registry
return registry, PLUGIN_API_VERSION
def _validate_host(registry: Any, plugin_api_version: int) -> None:
if plugin_api_version != SUPPORTED_PLUGIN_API_VERSION:
raise RayforgeCompatibilityError(
"unsupported Rayforge addon API: "
f"expected {SUPPORTED_PLUGIN_API_VERSION}, got {plugin_api_version}"
)
if not isinstance(getattr(registry, "driver_by_classname", None), dict):
raise RayforgeCompatibilityError(
"Rayforge driver_by_classname registry is unavailable"
)
if not isinstance(getattr(registry, "drivers", None), list):
raise RayforgeCompatibilityError("Rayforge drivers registry is unavailable")
if not callable(getattr(registry, "register_driver", None)):
raise RayforgeCompatibilityError("Rayforge register_driver is unavailable")
def _host(
registry: Any | None, plugin_api_version: int | None
) -> tuple[Any, int]:
if registry is None or plugin_api_version is None:
resolved_registry, resolved_version = _resolve_host()
registry = resolved_registry if registry is None else registry
plugin_api_version = (
resolved_version
if plugin_api_version is None
else plugin_api_version
)
_validate_host(registry, plugin_api_version)
return registry, plugin_api_version
def register_driver(
driver_cls: type,
*,
registry: Any | None = None,
plugin_api_version: int | None = None,
) -> bool:
"""Register exactly one driver class, or fail before replacing anything."""
registry, _version = _host(registry, plugin_api_version)
name = driver_cls.__name__
registered = registry.driver_by_classname.get(name)
named_entries = [item for item in registry.drivers if item.__name__ == name]
if registered is driver_cls:
if named_entries == [driver_cls]:
return False
raise RayforgeCompatibilityError(
f"inconsistent Rayforge driver registry for {name}"
)
if registered is not None or named_entries:
raise RayforgeCompatibilityError(
f"Rayforge driver name collision for {name}"
)
registry.register_driver(driver_cls)
if registry.driver_by_classname.get(name) is not driver_cls:
raise RayforgeCompatibilityError(
f"Rayforge did not index registered driver {name}"
)
if sum(item is driver_cls for item in registry.drivers) != 1:
raise RayforgeCompatibilityError(
f"Rayforge did not register exactly one {name} driver"
)
return True
def unregister_driver(
driver_cls: type,
*,
registry: Any | None = None,
plugin_api_version: int | None = None,
) -> bool:
"""Remove only the exact class previously registered by this addon."""
registry, _version = _host(registry, plugin_api_version)
name = driver_cls.__name__
registered = registry.driver_by_classname.get(name)
identity_entries = [item for item in registry.drivers if item is driver_cls]
named_foreign = [
item
for item in registry.drivers
if item.__name__ == name and item is not driver_cls
]
if registered is None and not identity_entries and not named_foreign:
return False
if registered is not driver_cls or named_foreign:
raise RayforgeCompatibilityError(
f"refusing to unregister foreign Rayforge driver {name}"
)
del registry.driver_by_classname[name]
registry.drivers[:] = [
item for item in registry.drivers if item is not driver_cls
]
if name in registry.driver_by_classname or any(
item is driver_cls for item in registry.drivers
):
raise RayforgeCompatibilityError(
f"Rayforge did not fully unregister driver {name}"
)
return True

View file

@ -12,7 +12,6 @@ import struct
from typing import Any
from rayforge.core.hooks import hookimpl
from rayforge.machine import driver as driver_registry
from rayforge.machine.driver import NoDeviceDriver
from rayforge.pipeline.encoder.base import (
EncodedOutput,
@ -24,6 +23,7 @@ from .offline_session import (
OfflineJCZSession,
OfflineSessionState,
)
from .rayforge_compat import register_driver, unregister_driver
class OfflineJCZEncoder(OpsEncoder):
@ -341,19 +341,11 @@ class PirateRayJCZNoDeviceDriver(NoDeviceDriver):
def _register() -> None:
name = PirateRayJCZNoDeviceDriver.__name__
if name not in driver_registry.driver_by_classname:
driver_registry.register_driver(PirateRayJCZNoDeviceDriver)
register_driver(PirateRayJCZNoDeviceDriver)
def _unregister() -> None:
name = PirateRayJCZNoDeviceDriver.__name__
registered = driver_registry.driver_by_classname.get(name)
if registered is PirateRayJCZNoDeviceDriver:
del driver_registry.driver_by_classname[name]
driver_registry.drivers[:] = [
item for item in driver_registry.drivers if item is not registered
]
unregister_driver(PirateRayJCZNoDeviceDriver)
# Rayforge currently has no driver registry in call_registration_hooks().

View file

@ -19,9 +19,10 @@
"pirate_ray_jcz/__init__.py",
"pirate_ray_jcz/offline_session.py",
"pirate_ray_jcz/profile_import.py",
"pirate_ray_jcz/rayforge_compat.py",
"pirate_ray_jcz/recipe_metadata.py",
"pirate_ray_jcz/worker.py",
"rayforge-addon.yaml"
],
"registration_seam": "worker import side effect + on_unload cleanup"
"registration_seam": "fail-closed API 23 compatibility adapter + on_unload cleanup"
}

View file

@ -28,6 +28,10 @@ class RayforgeAddonSpikeTests(unittest.TestCase):
result["lifecycle"],
)
self.assertEqual(23, result["rayforge_api_version"])
self.assertEqual(
"fail-closed API 23 compatibility adapter + on_unload cleanup",
result["registration_seam"],
)
def test_addon_contains_no_hardware_transport_import(self):
source = "\n".join(

View file

@ -0,0 +1,123 @@
import importlib.util
import unittest
from pathlib import Path
ROOT = Path(__file__).parents[1]
COMPAT_PATH = (
ROOT
/ "spikes"
/ "rayforge-jcz-no-device-addon"
/ "pirate_ray_jcz"
/ "rayforge_compat.py"
)
SPEC = importlib.util.spec_from_file_location("rayforge_compat_test", COMPAT_PATH)
COMPAT = importlib.util.module_from_spec(SPEC)
SPEC.loader.exec_module(COMPAT)
class FakeRegistry:
def __init__(self):
self.drivers = []
self.driver_by_classname = {}
def register_driver(self, driver_cls):
self.drivers.append(driver_cls)
self.driver_by_classname[driver_cls.__name__] = driver_cls
class ExampleDriver:
pass
class RayforgeCompatibilityTests(unittest.TestCase):
def test_registration_and_cleanup_are_symmetric_and_idempotent(self):
registry = FakeRegistry()
self.assertTrue(
COMPAT.register_driver(
ExampleDriver, registry=registry, plugin_api_version=23
)
)
self.assertFalse(
COMPAT.register_driver(
ExampleDriver, registry=registry, plugin_api_version=23
)
)
self.assertEqual([ExampleDriver], registry.drivers)
self.assertIs(
ExampleDriver, registry.driver_by_classname["ExampleDriver"]
)
self.assertTrue(
COMPAT.unregister_driver(
ExampleDriver, registry=registry, plugin_api_version=23
)
)
self.assertFalse(
COMPAT.unregister_driver(
ExampleDriver, registry=registry, plugin_api_version=23
)
)
self.assertEqual([], registry.drivers)
self.assertEqual({}, registry.driver_by_classname)
def test_api_version_drift_fails_before_mutation(self):
registry = FakeRegistry()
with self.assertRaisesRegex(
COMPAT.RayforgeCompatibilityError,
"expected 23, got 24",
):
COMPAT.register_driver(
ExampleDriver, registry=registry, plugin_api_version=24
)
self.assertEqual([], registry.drivers)
self.assertEqual({}, registry.driver_by_classname)
def test_name_collision_is_never_replaced_or_removed(self):
foreign_driver = type("ExampleDriver", (), {})
registry = FakeRegistry()
registry.register_driver(foreign_driver)
with self.assertRaisesRegex(
COMPAT.RayforgeCompatibilityError, "name collision"
):
COMPAT.register_driver(
ExampleDriver, registry=registry, plugin_api_version=23
)
with self.assertRaisesRegex(
COMPAT.RayforgeCompatibilityError, "foreign"
):
COMPAT.unregister_driver(
ExampleDriver, registry=registry, plugin_api_version=23
)
self.assertEqual([foreign_driver], registry.drivers)
self.assertIs(
foreign_driver, registry.driver_by_classname["ExampleDriver"]
)
def test_incomplete_registry_contract_fails_closed(self):
class MissingRegister:
drivers = []
driver_by_classname = {}
with self.assertRaisesRegex(
COMPAT.RayforgeCompatibilityError, "register_driver"
):
COMPAT.register_driver(
ExampleDriver,
registry=MissingRegister(),
plugin_api_version=23,
)
def test_adapter_has_no_transport_imports(self):
source = COMPAT_PATH.read_text(encoding="utf-8").lower()
for forbidden in ("import usb", "import serial", "import socket"):
self.assertNotIn(forbidden, source)
if __name__ == "__main__":
unittest.main()