From db4b96b007a793b77cb55d8be160e15d084b0183 Mon Sep 17 00:00:00 2001 From: Rohan Pandula Date: Sat, 18 Jul 2026 22:32:25 -0700 Subject: [PATCH 01/48] Add coolscan-roll optional dependency group coolscanpy is a standalone, SANE-free library for whole-roll scanning on a Nikon Coolscan LS-5000 with a roll feeder. Add it as a new dependency-group, following the same pattern as the existing "scanner" group for python-sane: opt-in, not a hard dependency of the rest of the application. --- pyproject.toml | 3 +++ uv.lock | 30 ++++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 368661ee..c61807a3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,6 +55,9 @@ camera = [ # Camera Scanning tab with them — are macOS and Linux only. "gphoto2>=2.5 ; sys_platform != 'win32'", ] +coolscan-roll = [ + "coolscanpy>=0.1.2", +] [project.urls] Homepage = "https://github.com/marcinz606/NegPy" diff --git a/uv.lock b/uv.lock index cfbb8304..400d686c 100644 --- a/uv.lock +++ b/uv.lock @@ -65,6 +65,23 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, ] +[[package]] +name = "coolscanpy" +version = "0.1.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "imagecodecs" }, + { name = "jinja2" }, + { name = "numpy" }, + { name = "opencv-python-headless" }, + { name = "pyusb" }, + { name = "tifffile" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/c6/d0/2ab023bcd243daa927f20e3cdaac0db36a8a4b5df02d3424640c7863e7bd/coolscanpy-0.1.2.tar.gz", hash = "sha256:1ff98ea9689b19ad765bb7109ac18ab9d71509d266e87cbdf8434fa14577808e", size = 289030, upload-time = "2026-07-19T02:58:42.101Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f9/66/18473737fcba32a57fbef3d2668b6f15d94c869810d1a7dbeb4fff57a7bd/coolscanpy-0.1.2-py3-none-any.whl", hash = "sha256:a780eb3613b30e1299913c3c5f33f6b3255382ac4c3309e46691a071ec477a24", size = 296086, upload-time = "2026-07-19T02:58:40.819Z" }, +] + [[package]] name = "coverage" version = "7.14.0" @@ -315,6 +332,9 @@ dependencies = [ camera = [ { name = "gphoto2", marker = "sys_platform != 'win32'" }, ] +coolscan-roll = [ + { name = "coolscanpy" }, +] dev = [ { name = "pyinstaller" }, { name = "pytest" }, @@ -347,6 +367,7 @@ requires-dist = [ [package.metadata.requires-dev] camera = [{ name = "gphoto2", marker = "sys_platform != 'win32'", specifier = ">=2.5" }] +coolscan-roll = [{ name = "coolscanpy", specifier = ">=0.1.2" }] dev = [ { name = "pyinstaller", specifier = ">=6.18.0" }, { name = "pytest", specifier = "==9.0.2" }, @@ -725,6 +746,15 @@ version = "2.9.2" source = { registry = "https://pypi.org/simple" } sdist = { url = "https://files.pythonhosted.org/packages/45/e9/e8baff69fc2347606c547201204d4b4843c7ad8ecb9164eceee42016eff6/python_sane-2.9.2.tar.gz", hash = "sha256:50ab8e0b033cececad26c7231a7254f80ad8fe9ec6b5c25add2493d7e2a07bbe", size = 22513, upload-time = "2025-07-21T21:20:21.735Z" } +[[package]] +name = "pyusb" +version = "1.3.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/00/6b/ce3727395e52b7b76dfcf0c665e37d223b680b9becc60710d4bc08b7b7cb/pyusb-1.3.1.tar.gz", hash = "sha256:3af070b607467c1c164f49d5b0caabe8ac78dbed9298d703a8dbf9df4052d17e", size = 77281, upload-time = "2025-01-08T23:45:01.866Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/28/b8/27e6312e86408a44fe16bd28ee12dd98608b39f7e7e57884a24e8f29b573/pyusb-1.3.1-py3-none-any.whl", hash = "sha256:bf9b754557af4717fe80c2b07cc2b923a9151f5c08d17bdb5345dac09d6a0430", size = 58465, upload-time = "2025-01-08T23:45:00.029Z" }, +] + [[package]] name = "pywin32-ctypes" version = "0.2.3" From 6937173a965614cc9575d4b2381069accda6df60 Mon Sep 17 00:00:00 2001 From: Rohan Pandula Date: Sat, 18 Jul 2026 22:32:32 -0700 Subject: [PATCH 02/48] Add coolscanpy roll-scanning backend adapter CoolscanRoll wraps coolscanpy's Device/Roll workflow (preview, spacing correction, approval, batch fine-scan, safe stop) behind a small RollHandle, translating coolscanpy's typed PyCoolscanError hierarchy to plain RuntimeErrors the same way SaneBackend already reports failures elsewhere in this package. available() is a cheap importlib.util.find_spec() check, mirroring ScanSidebar's existing _sane_available() gate, so the feature degrades to fully absent when coolscanpy isn't installed. open_roll() is the single place this adapter resolves a device handle, marked as the intended re-point target once the maintainer's own generic SANE-based coolscan route defines a real backend-selection seam on ScannerService. Tests inject a fake coolscanpy module (tests/scanners/conftest.py) so none of this requires real hardware or the real package installed. --- .../scanners/coolscanpy_roll.py | 175 ++++++++++++++++++ tests/scanners/conftest.py | 175 ++++++++++++++++++ tests/scanners/test_coolscanpy_roll.py | 166 +++++++++++++++++ 3 files changed, 516 insertions(+) create mode 100644 negpy/infrastructure/scanners/coolscanpy_roll.py create mode 100644 tests/scanners/conftest.py create mode 100644 tests/scanners/test_coolscanpy_roll.py diff --git a/negpy/infrastructure/scanners/coolscanpy_roll.py b/negpy/infrastructure/scanners/coolscanpy_roll.py new file mode 100644 index 00000000..bd466550 --- /dev/null +++ b/negpy/infrastructure/scanners/coolscanpy_roll.py @@ -0,0 +1,175 @@ +"""Optional coolscanpy-backed roll-scanning adapter for the Nikon Coolscan LS-5000. + +coolscanpy (https://github.com/rohanpandula/coolscanpy) is a standalone, +SANE-free library that talks to an LS-5000 plus SA-21/SA-30 roll feeder +directly over USB: whole-roll preview, per-slot spacing correction, and +batch fine-scanning with receipts. NegPy consumes it exactly like +python-sane or gphoto2 elsewhere in this package -- an optional dependency, +imported lazily, entirely absent by default. See `available()`. + +This module owns every place coolscanpy itself gets imported or driven for +the roll workflow: opening a device, opening its roll extension, and +translating coolscanpy's exceptions. `negpy.services.scanning.roll_service` +builds the file-writing workflow on top of it but never imports coolscanpy +directly. + +-------------------------------------------------------------------------- +INTEGRATION POINT for a future re-point +-------------------------------------------------------------------------- +`open_roll()` below is the one place a device handle gets resolved for the +roll workflow. It currently goes straight to `coolscanpy.open()`. NegPy's +maintainer has a generic SANE-based coolscan route planned upstream, which +is expected to add a real backend-selection seam to +`negpy.services.scanning.service.ScannerService` (today `_get_backend()` +just hardcodes `SaneBackend()`). When that seam lands, re-pointing this +adapter at it should only mean changing how `open_roll()` resolves a +device -- everything built on top of the returned `RollHandle` (this +module's exception translation, and all of +`negpy/services/scanning/roll_service.py`) is unaffected, since it never +talks to coolscanpy except through this one function and the `RollHandle` +it returns. +-------------------------------------------------------------------------- +""" + +from __future__ import annotations + +import importlib.util +from typing import TYPE_CHECKING, Iterable, Iterator + +if TYPE_CHECKING: + import coolscanpy + + +def available() -> bool: + """True if the optional `coolscanpy` dependency is importable. + + A cheap, side-effect-free presence check -- mirrors the + `ScanSidebar._sane_available()` check the plain Scan panel already uses + for python-sane, so callers (a future roll panel, a service, a test) can + degrade gracefully without ever importing coolscanpy just to ask whether + it exists. + """ + return importlib.util.find_spec("coolscanpy") is not None + + +class RollHandle: + """One open coolscanpy `Device` + `Roll`, released together on `close()`. + + Construct via `open_roll()`, not directly. Every method here is a thin + forward to the underlying `coolscanpy.Roll`, translating coolscanpy's + typed exceptions (rooted at `PyCoolscanError`) to plain `RuntimeError`s + -- matching how `SaneBackend.scan()` already reports failures elsewhere + in this package (a message string the scan worker forwards verbatim to + the GUI's status label). NegPy's scanner layer has no typed exception + vocabulary of its own for a caller to catch selectively; a caller that + wants coolscanpy's own richer hierarchy can still recover it from + `__cause__`, since every translation chains `from error`. + """ + + def __init__(self, device: "coolscanpy.Device", roll: "coolscanpy.Roll") -> None: + self._device = device + self._roll = roll + + def preview(self, slots: Iterable[int] | None = None, *, on_progress=None) -> list: + """One whole-roll transport read. See `coolscanpy.Roll.preview`.""" + try: + return self._roll.preview(slots, on_progress=on_progress) + except Exception as error: + raise _translate(error) from error + + def set_spacing_offset(self, slot: int, offset_rows: int) -> None: + try: + self._roll.set_spacing_offset(slot, offset_rows) + except Exception as error: + raise _translate(error) from error + + def approve(self, slot: int) -> None: + try: + self._roll.approve(slot) + except Exception as error: + raise _translate(error) from error + + def needs_approval(self, slot: int) -> bool: + try: + return self._roll.needs_approval(slot) + except Exception as error: + raise _translate(error) from error + + def scan_many(self, slots: Iterable[int], *, on_progress=None) -> Iterator["coolscanpy.Frame"]: + """Batch fine-scan `slots` in one transport reservation. + + Note for a future caller that wires this to a cancel affordance + (mirroring how `ScanWorker.run_scan` swallows a cancelled plain scan + rather than reporting it as an error): a `RuntimeError` raised here + whose `__cause__` is a `coolscanpy.SafeStopRequested` means + `safe_stop()` was already called deliberately and should be treated + the same way, not surfaced as a failure. + """ + try: + yield from self._roll.scan_many(slots, on_progress=on_progress) + except Exception as error: + raise _translate(error) from error + + def safe_stop(self) -> None: + """Request a graceful stop; the frame already in flight still finishes.""" + self._roll.safe_stop() + + def close(self) -> None: + """Idempotent. Releases the roll extension, then the device.""" + try: + self._roll.close() + finally: + self._device.close() + + def __enter__(self) -> "RollHandle": + return self + + def __exit__(self, *exc: object) -> None: + self.close() + + +def list_devices() -> "list[coolscanpy.DeviceInfo]": + """Enumerate attached LS-5000 units. Empty list, never raises, if none found.""" + import coolscanpy + + return coolscanpy.get_devices() + + +def open_roll(device_id: str | None = None, *, material: "coolscanpy.Material | None" = None) -> RollHandle: + """Open one LS-5000 and its roll-feeder extension. + + `device_id` follows `coolscanpy.open()`: omitted (`None`) picks "the one + attached unit", an explicit id (from `list_devices()`) disambiguates + when more than one is attached. `material` defaults to + `coolscanpy.Material.COLOR_NEGATIVE`, the only material coolscanpy's + roll engine fine-scans end to end today; black-and-white preview works + but its batch scan raises `NotImplementedError` (unchanged, surfaced as + a plain `RuntimeError` here like everything else). + """ + import coolscanpy + + resolved_material = material if material is not None else coolscanpy.Material.COLOR_NEGATIVE + try: + device = coolscanpy.open(device_id or "ls5000") + except Exception as error: + raise _translate(error) from error + + try: + roll = device.roll(material=resolved_material) + except Exception as error: + device.close() + raise _translate(error) from error + + return RollHandle(device, roll) + + +def _translate(error: BaseException) -> RuntimeError: + """Flatten any coolscanpy failure to a plain `RuntimeError`. + + Covers the typed `PyCoolscanError` hierarchy (`RollMismatch`, + `FingerprintRefused`, `ManualReviewRequired`, `SafeStopRequested`, ...) + and the handful of plain `ValueError`/`RuntimeError` coolscanpy itself + raises (e.g. "no roll adapter is attached"), so nothing coolscanpy-shaped + ever crosses out of this module. + """ + return RuntimeError(str(error)) diff --git a/tests/scanners/conftest.py b/tests/scanners/conftest.py new file mode 100644 index 00000000..5890566a --- /dev/null +++ b/tests/scanners/conftest.py @@ -0,0 +1,175 @@ +"""Shared fixtures for tests/scanners/: a fake `coolscanpy` module. + +`coolscanpy_roll.py` / `roll_service.py` treat coolscanpy as optional and +import it lazily, so these tests never need the real package installed -- +`fake_coolscanpy` injects a minimal stand-in module into `sys.modules` +instead, exercising the same `import coolscanpy` path the production code +takes. +""" + +from __future__ import annotations + +import dataclasses +import importlib.machinery +import sys +import types +from typing import Any + +import pytest + + +class FakePyCoolscanError(Exception): + """Stand-in for coolscanpy.PyCoolscanError.""" + + +class FakeDeviceNotFound(FakePyCoolscanError): + pass + + +class FakeManualReviewRequired(FakePyCoolscanError): + def __init__(self, message: str, *, slot: int) -> None: + super().__init__(message) + self.slot = slot + + +class FakeSafeStopRequested(FakePyCoolscanError): + pass + + +class FakeMaterial: + COLOR_NEGATIVE = "color-negative" + BLACK_AND_WHITE_NEGATIVE = "black-and-white-negative" + + +@dataclasses.dataclass(frozen=True) +class FakeThumbnail: + slot: int + image: Any + boundary_rows: tuple + spacing_offset: int + needs_approval: bool + warnings: tuple = () + + +@dataclasses.dataclass(frozen=True) +class FakeReceipt: + """Deliberately smaller than the real coolscanpy Receipt -- write_frame() + only needs *a* dataclass instance to round-trip through dataclasses.asdict().""" + + version: int + slot: int + dpi: int + depth: int + device_id: str + transport_smear_verdict: str + + +@dataclasses.dataclass(frozen=True) +class FakeFrame: + slot: int + rgb: Any + ir: Any + ir_validity: Any + receipt: Any + + +class FakeRoll: + """Scripted in-memory stand-in for `coolscanpy.Roll`.""" + + def __init__(self, thumbnails=None, frames=None, raise_on=None) -> None: + self._thumbnails = list(thumbnails or []) + self._frames = {frame.slot: frame for frame in (frames or [])} + self._raise_on = raise_on or {} + self.approved: list[int] = [] + self.spacing_offsets: dict[int, int] = {} + self.safe_stop_called = False + self.closed = False + + def preview(self, slots=None, *, on_progress=None): + if "preview" in self._raise_on: + raise self._raise_on["preview"] + wanted = None if slots is None else set(slots) + return [t for t in self._thumbnails if wanted is None or t.slot in wanted] + + def set_spacing_offset(self, slot, offset_rows) -> None: + self.spacing_offsets[slot] = offset_rows + + def approve(self, slot) -> None: + if "approve" in self._raise_on: + raise self._raise_on["approve"] + self.approved.append(slot) + + def needs_approval(self, slot) -> bool: + return slot in self._raise_on.get("needs_approval_slots", ()) + + def scan_many(self, slots, *, on_progress=None): + for slot in slots: + error = self._raise_on.get("scan_many_slots", {}).get(slot) + if error is not None: + raise error + yield self._frames[slot] + + def safe_stop(self) -> None: + self.safe_stop_called = True + + def close(self) -> None: + self.closed = True + + +class FakeDevice: + def __init__(self, roll) -> None: + self._roll = roll + self.closed = False + self.roll_called_with = None + + def roll(self, *, material=None): + self.roll_called_with = material + return self._roll + + def close(self) -> None: + self.closed = True + + +@pytest.fixture +def fake_coolscanpy(monkeypatch): + """Install a fake `coolscanpy` module; returns an object to script it. + + Usage:: + + fake_coolscanpy.state["open_device"] = fake_coolscanpy.Device(fake_coolscanpy.Roll()) + handle = coolscanpy_roll.open_roll() + """ + module = types.ModuleType("coolscanpy") + # importlib.util.find_spec() raises ValueError on a sys.modules entry + # with no __spec__, so give the fake one like a real imported module has. + module.__spec__ = importlib.machinery.ModuleSpec("coolscanpy", loader=None) + + state: dict[str, Any] = {"devices": [], "open_device": None, "open_error": None} + + def fake_get_devices(local_only: bool = False): + return state["devices"] + + def fake_open(devname: str): + if state["open_error"] is not None: + raise state["open_error"] + return state["open_device"] + + module.get_devices = fake_get_devices + module.open = fake_open + module.Material = FakeMaterial + module.PyCoolscanError = FakePyCoolscanError + module.DeviceNotFound = FakeDeviceNotFound + module.ManualReviewRequired = FakeManualReviewRequired + module.SafeStopRequested = FakeSafeStopRequested + + monkeypatch.setitem(sys.modules, "coolscanpy", module) + + return types.SimpleNamespace( + module=module, + state=state, + Device=FakeDevice, + Roll=FakeRoll, + Thumbnail=FakeThumbnail, + Frame=FakeFrame, + Receipt=FakeReceipt, + ) diff --git a/tests/scanners/test_coolscanpy_roll.py b/tests/scanners/test_coolscanpy_roll.py new file mode 100644 index 00000000..74009d7d --- /dev/null +++ b/tests/scanners/test_coolscanpy_roll.py @@ -0,0 +1,166 @@ +"""Tests for the optional coolscanpy-backed roll-scanning adapter. + +No real coolscanpy install is required or assumed: `fake_coolscanpy` (see +tests/scanners/conftest.py) injects a minimal stand-in module, exercising +the same lazy `import coolscanpy` path the production code takes. +""" + +from __future__ import annotations + +import importlib.util +import sys + +import numpy as np +import pytest + +from negpy.infrastructure.scanners import coolscanpy_roll + + +class TestAvailable: + def test_false_when_not_importable(self, monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.delitem(sys.modules, "coolscanpy", raising=False) + # This environment genuinely doesn't have coolscanpy installed (it's + # an optional dependency-group); if that ever changes, find_spec + # itself would need monkeypatching instead of just sys.modules. + assert importlib.util.find_spec("coolscanpy") is None + assert coolscanpy_roll.available() is False + + def test_true_when_fake_module_present(self, fake_coolscanpy) -> None: + assert coolscanpy_roll.available() is True + + +class TestListDevices: + def test_passthrough(self, fake_coolscanpy) -> None: + fake_coolscanpy.state["devices"] = ["device-a", "device-b"] + assert coolscanpy_roll.list_devices() == ["device-a", "device-b"] + + +class TestOpenRoll: + def test_opens_named_device_with_default_material(self, fake_coolscanpy) -> None: + roll = fake_coolscanpy.Roll() + device = fake_coolscanpy.Device(roll) + fake_coolscanpy.state["open_device"] = device + + handle = coolscanpy_roll.open_roll("ls5000-usb-001") + + assert device.roll_called_with == fake_coolscanpy.module.Material.COLOR_NEGATIVE + handle.close() + assert roll.closed is True + assert device.closed is True + + def test_explicit_material_forwarded(self, fake_coolscanpy) -> None: + roll = fake_coolscanpy.Roll() + device = fake_coolscanpy.Device(roll) + fake_coolscanpy.state["open_device"] = device + + coolscanpy_roll.open_roll(material=fake_coolscanpy.module.Material.BLACK_AND_WHITE_NEGATIVE) + + assert device.roll_called_with == fake_coolscanpy.module.Material.BLACK_AND_WHITE_NEGATIVE + + def test_device_not_found_translated_to_runtime_error(self, fake_coolscanpy) -> None: + fake_coolscanpy.state["open_error"] = fake_coolscanpy.module.DeviceNotFound("no Coolscan LS-5000 unit is attached") + + with pytest.raises(RuntimeError, match="no Coolscan LS-5000 unit is attached"): + coolscanpy_roll.open_roll() + + def test_roll_open_failure_closes_device_and_translates(self, fake_coolscanpy) -> None: + class ExplodingDevice(fake_coolscanpy.Device): + def roll(self, *, material=None): + raise ValueError("no roll adapter is attached/detected on this device") + + device = ExplodingDevice(None) + fake_coolscanpy.state["open_device"] = device + + with pytest.raises(RuntimeError, match="no roll adapter is attached"): + coolscanpy_roll.open_roll() + assert device.closed is True + + +class TestRollHandle: + @staticmethod + def _handle(fake_coolscanpy, roll=None): + roll = roll if roll is not None else fake_coolscanpy.Roll() + device = fake_coolscanpy.Device(roll) + fake_coolscanpy.state["open_device"] = device + return coolscanpy_roll.open_roll(), roll, device + + def test_preview_passthrough(self, fake_coolscanpy) -> None: + thumb = fake_coolscanpy.Thumbnail(slot=1, image=np.zeros((4, 4, 3)), boundary_rows=(0, 4), spacing_offset=0, needs_approval=False) + handle, _roll, _device = self._handle(fake_coolscanpy, fake_coolscanpy.Roll(thumbnails=[thumb])) + + assert handle.preview() == [thumb] + + def test_preview_filters_by_requested_slots(self, fake_coolscanpy) -> None: + thumbs = [ + fake_coolscanpy.Thumbnail(slot=i, image=np.zeros((2, 2, 3)), boundary_rows=(0, 2), spacing_offset=0, needs_approval=False) + for i in (1, 2, 3) + ] + handle, _roll, _device = self._handle(fake_coolscanpy, fake_coolscanpy.Roll(thumbnails=thumbs)) + + result = handle.preview([2]) + + assert [t.slot for t in result] == [2] + + def test_preview_exception_translated(self, fake_coolscanpy) -> None: + handle, _roll, _device = self._handle( + fake_coolscanpy, + fake_coolscanpy.Roll(raise_on={"preview": fake_coolscanpy.module.PyCoolscanError("fingerprint mismatch")}), + ) + with pytest.raises(RuntimeError, match="fingerprint mismatch"): + handle.preview() + + def test_approve_records_slot(self, fake_coolscanpy) -> None: + handle, roll, _device = self._handle(fake_coolscanpy) + handle.approve(3) + assert roll.approved == [3] + + def test_manual_review_required_translated(self, fake_coolscanpy) -> None: + handle, _roll, _device = self._handle( + fake_coolscanpy, + fake_coolscanpy.Roll(raise_on={"approve": fake_coolscanpy.module.ManualReviewRequired("slot 3 needs review", slot=3)}), + ) + with pytest.raises(RuntimeError, match="slot 3 needs review"): + handle.approve(3) + + def test_set_spacing_offset_records_value(self, fake_coolscanpy) -> None: + handle, roll, _device = self._handle(fake_coolscanpy) + handle.set_spacing_offset(5, -12) + assert roll.spacing_offsets[5] == -12 + + def test_scan_many_yields_frames(self, fake_coolscanpy) -> None: + frame = fake_coolscanpy.Frame(slot=2, rgb=np.zeros((4, 4, 3), dtype=np.uint16), ir=None, ir_validity=None, receipt=None) + handle, _roll, _device = self._handle(fake_coolscanpy, fake_coolscanpy.Roll(frames=[frame])) + + assert list(handle.scan_many([2])) == [frame] + + def test_scan_many_exception_translated(self, fake_coolscanpy) -> None: + error = fake_coolscanpy.module.SafeStopRequested("safe stop requested; 0 of 1 requested frames completed") + handle, _roll, _device = self._handle(fake_coolscanpy, fake_coolscanpy.Roll(raise_on={"scan_many_slots": {5: error}})) + + with pytest.raises(RuntimeError, match="safe stop requested") as excinfo: + list(handle.scan_many([5])) + # __cause__ preserves the original typed exception for a caller that + # wants to distinguish a deliberate stop from a genuine failure. + assert isinstance(excinfo.value.__cause__, fake_coolscanpy.module.SafeStopRequested) + + def test_safe_stop_forwarded(self, fake_coolscanpy) -> None: + handle, roll, _device = self._handle(fake_coolscanpy) + handle.safe_stop() + assert roll.safe_stop_called is True + + def test_close_releases_roll_then_device(self, fake_coolscanpy) -> None: + handle, roll, device = self._handle(fake_coolscanpy) + handle.close() + assert roll.closed is True + assert device.closed is True + + def test_context_manager_closes_on_exit(self, fake_coolscanpy) -> None: + roll = fake_coolscanpy.Roll() + device = fake_coolscanpy.Device(roll) + fake_coolscanpy.state["open_device"] = device + + with coolscanpy_roll.open_roll() as handle: + assert handle is not None + assert roll.closed is False + + assert roll.closed is True From fbb8f2a3f036a9366cae061f45c5c20e69977871 Mon Sep 17 00:00:00 2001 From: Rohan Pandula Date: Sat, 18 Jul 2026 22:32:38 -0700 Subject: [PATCH 03/48] Add roll-scanning service and output writer RollScanningService orchestrates the coolscanpy_roll adapter: open/ close a roll (one at a time, matching coolscanpy's own reservation lock), preview, approve, spacing correction, batch scan_many, and safe_stop. write_frame() writes each scanned Frame to disk in NegPy's existing scan-output shape: a 16-bit TIFF master, an "_IR" TIFF sidecar when the frame carries an infrared plane (matching writer.write_tiff_16bit's own suffix), and an "_receipt.json" sidecar with the full Receipt. The filename pattern is the same Jinja2 template the plain Scan panel uses, but seq is seeded from the frame's physical slot number rather than probed for a free name, so redoing one bad slot overwrites that slot's files instead of accumulating a second copy. ir_validity is intentionally not persisted; nothing downstream reads it yet. --- negpy/services/scanning/roll_service.py | 184 ++++++++++++++++++++++++ tests/scanners/test_roll_service.py | 163 +++++++++++++++++++++ 2 files changed, 347 insertions(+) create mode 100644 negpy/services/scanning/roll_service.py create mode 100644 tests/scanners/test_roll_service.py diff --git a/negpy/services/scanning/roll_service.py b/negpy/services/scanning/roll_service.py new file mode 100644 index 00000000..5855a8f8 --- /dev/null +++ b/negpy/services/scanning/roll_service.py @@ -0,0 +1,184 @@ +"""Roll-scanning service backed by the optional `coolscanpy` package. + +Mirrors `negpy.services.scanning.service.ScannerService`: one class that +orchestrates device discovery, the hardware workflow, and writing results to +disk in NegPy's conventional layout. Where `ScannerService` wraps a single +ad-hoc `Device.scan()`, this wraps coolscanpy's whole-roll workflow +(preview -> approve -> batch fine-scan) exposed by +`negpy.infrastructure.scanners.coolscanpy_roll`. + +Entirely inert if `coolscanpy` is not installed: `available()` is a cheap +presence check re-exported from that module, and every other method only +reaches coolscanpy (transitively, through `coolscanpy_roll`) once actually +called. +""" + +from __future__ import annotations + +import dataclasses +import json +import os +import tempfile +from datetime import date as _date +from typing import TYPE_CHECKING, Iterable, Iterator + +import numpy as np +import tifffile + +from negpy.infrastructure.scanners import coolscanpy_roll +from negpy.kernel.system.logging import get_logger +from negpy.services.scanning.templating import render_scan_filename + +if TYPE_CHECKING: + import coolscanpy + +logger = get_logger(__name__) + +# Re-exported so callers only need this module: `from negpy.services.scanning +# .roll_service import available`, matching how the plain Scan sidebar checks +# `_sane_available()` before showing its device combo. +available = coolscanpy_roll.available + + +@dataclasses.dataclass(frozen=True) +class RollFrameOutput: + """Where one scanned frame's files landed on disk.""" + + slot: int + rgb_path: str + ir_path: str | None + receipt_path: str + + +class RollScanningService: + """Orchestrates coolscanpy device/roll lifecycle and output writing. + + One roll open at a time, matching `coolscanpy.Device.roll()`'s own + single-reservation lock -- call `close()` (or use this as a context + manager) before opening another. + """ + + def __init__(self) -> None: + self._roll: coolscanpy_roll.RollHandle | None = None + + # -- device / roll lifecycle ----------------------------------------- + + def list_devices(self) -> "list[coolscanpy.DeviceInfo]": + return coolscanpy_roll.list_devices() + + def open_roll(self, device_id: str | None = None, *, material: "coolscanpy.Material | None" = None) -> None: + """Open a device and its roll extension. Call `close()` when done.""" + if self._roll is not None: + raise RuntimeError("a roll is already open on this service; call close() first") + self._roll = coolscanpy_roll.open_roll(device_id, material=material) + + def close(self) -> None: + """Idempotent. Ends the roll reservation and releases the device.""" + if self._roll is not None: + self._roll.close() + self._roll = None + + def __enter__(self) -> "RollScanningService": + return self + + def __exit__(self, *exc: object) -> None: + self.close() + + # -- preview / approval ------------------------------------------------ + + def preview(self, slots: Iterable[int] | None = None, *, on_progress=None) -> list: + return self._require_roll().preview(slots, on_progress=on_progress) + + def set_spacing_offset(self, slot: int, offset_rows: int) -> None: + self._require_roll().set_spacing_offset(slot, offset_rows) + + def approve(self, slot: int) -> None: + self._require_roll().approve(slot) + + def needs_approval(self, slot: int) -> bool: + return self._require_roll().needs_approval(slot) + + # -- scanning ---------------------------------------------------------- + + def scan_many(self, slots: Iterable[int], *, on_progress=None) -> Iterator["coolscanpy.Frame"]: + yield from self._require_roll().scan_many(slots, on_progress=on_progress) + + def safe_stop(self) -> None: + if self._roll is not None: + self._roll.safe_stop() + + # -- writing ------------------------------------------------------------- + + def write_frame(self, frame: "coolscanpy.Frame", output_folder: str, filename_pattern: str) -> RollFrameOutput: + """Write one scanned `Frame` to disk: a 16-bit TIFF master, an `_IR` + TIFF sidecar when the frame carries an infrared plane (matching + `writer.write_tiff_16bit`'s own `_IR.tif` convention), and + a `_receipt.json` sidecar holding the full `Receipt`. + + `filename_pattern` is the same Jinja2 template the plain scan path + uses (variables: `date`, `seq`), but `seq` is seeded from the frame's + physical slot number rather than probed for the next free name: a + roll slot is already a stable identity, so re-scanning slot 5 + replaces slot 5's old files instead of piling up a `..._002` beside + them. + + `frame.ir_validity` (the per-pixel IR confidence mask) is not + persisted here -- it isn't part of NegPy's existing scan-output + convention and nothing downstream reads it yet. + """ + os.makedirs(output_folder, exist_ok=True) + date_str = _date.today().strftime("%Y%m%d") + basename = render_scan_filename(filename_pattern, date_str, frame.slot) + base_path = os.path.join(output_folder, basename) + + rgb_path = _atomic_write_tiff(base_path + ".tif", frame.rgb, photometric="rgb") + + ir_path = None + if frame.ir is not None: + ir_path = _atomic_write_tiff(base_path + "_IR.tif", frame.ir, photometric="minisblack") + + receipt_path = base_path + "_receipt.json" + _atomic_write_json(receipt_path, dataclasses.asdict(frame.receipt)) + + return RollFrameOutput(slot=frame.slot, rgb_path=rgb_path, ir_path=ir_path, receipt_path=receipt_path) + + # -- internals ----------------------------------------------------------- + + def _require_roll(self) -> coolscanpy_roll.RollHandle: + if self._roll is None: + raise RuntimeError("no roll is open; call open_roll() first") + return self._roll + + +def _ensure_uint16(array: np.ndarray) -> np.ndarray: + return array if array.dtype == np.uint16 else array.astype(np.uint16) + + +def _atomic_write_tiff(path: str, array: np.ndarray, *, photometric: str) -> str: + """Write `array` to `path` via a temp file + rename, matching the + atomic-write convention `negpy.services.scanning.writer` already uses + for the plain scan path.""" + fd, tmp_path = tempfile.mkstemp(suffix=".tif", dir=os.path.dirname(path) or ".") + os.close(fd) + try: + tifffile.imwrite(tmp_path, _ensure_uint16(array), photometric=photometric, compression="lzw") + os.replace(tmp_path, path) + except Exception: + if os.path.exists(tmp_path): + os.unlink(tmp_path) + raise + return path + + +def _atomic_write_json(path: str, payload: dict) -> str: + tmp_path = None + try: + with tempfile.NamedTemporaryFile("w", dir=os.path.dirname(path) or ".", delete=False, suffix=".part", encoding="utf-8") as tmp: + tmp_path = tmp.name + json.dump(payload, tmp, default=str, indent=2) + os.replace(tmp_path, path) + except Exception: + if tmp_path is not None and os.path.exists(tmp_path): + os.unlink(tmp_path) + raise + return path diff --git a/tests/scanners/test_roll_service.py b/tests/scanners/test_roll_service.py new file mode 100644 index 00000000..2ac621ba --- /dev/null +++ b/tests/scanners/test_roll_service.py @@ -0,0 +1,163 @@ +"""Tests for RollScanningService: lifecycle orchestration and output writing. + +Lifecycle tests use `fake_coolscanpy` (see tests/scanners/conftest.py) the +same way test_coolscanpy_roll.py does. write_frame() tests construct a fake +Frame/Receipt directly -- writing to disk never touches coolscanpy itself, +so no module injection is needed for those. +""" + +from __future__ import annotations + +import json +import os + +import numpy as np +import pytest +import tifffile + +from negpy.services.scanning import roll_service +from negpy.services.scanning.roll_service import RollScanningService + + +class TestAvailable: + def test_reexports_backend_availability(self, fake_coolscanpy) -> None: + assert roll_service.available() is True + + +class TestRollLifecycle: + def _open_service(self, fake_coolscanpy, roll=None): + roll = roll if roll is not None else fake_coolscanpy.Roll() + device = fake_coolscanpy.Device(roll) + fake_coolscanpy.state["open_device"] = device + service = RollScanningService() + service.open_roll("ls5000-usb-001") + return service, roll, device + + def test_open_then_close(self, fake_coolscanpy) -> None: + service, roll, device = self._open_service(fake_coolscanpy) + service.close() + assert roll.closed is True + assert device.closed is True + + def test_double_open_raises(self, fake_coolscanpy) -> None: + service, _roll, _device = self._open_service(fake_coolscanpy) + with pytest.raises(RuntimeError, match="already open"): + service.open_roll() + + def test_methods_before_open_raise(self) -> None: + service = RollScanningService() + with pytest.raises(RuntimeError, match="no roll is open"): + service.preview() + + def test_close_without_open_is_a_no_op(self) -> None: + RollScanningService().close() # must not raise + + def test_preview_and_approve_delegate_to_handle(self, fake_coolscanpy) -> None: + thumb = fake_coolscanpy.Thumbnail(slot=1, image=np.zeros((2, 2, 3)), boundary_rows=(0, 2), spacing_offset=0, needs_approval=True) + service, roll, _device = self._open_service(fake_coolscanpy, fake_coolscanpy.Roll(thumbnails=[thumb])) + + assert service.preview() == [thumb] + service.approve(1) + assert roll.approved == [1] + + def test_scan_many_delegates_to_handle(self, fake_coolscanpy) -> None: + frame = fake_coolscanpy.Frame(slot=1, rgb=np.zeros((2, 2, 3), dtype=np.uint16), ir=None, ir_validity=None, receipt=None) + service, _roll, _device = self._open_service(fake_coolscanpy, fake_coolscanpy.Roll(frames=[frame])) + + assert list(service.scan_many([1])) == [frame] + + def test_safe_stop_before_open_is_a_no_op(self) -> None: + RollScanningService().safe_stop() # must not raise + + def test_context_manager_closes(self, fake_coolscanpy) -> None: + roll = fake_coolscanpy.Roll() + device = fake_coolscanpy.Device(roll) + fake_coolscanpy.state["open_device"] = device + + with RollScanningService() as service: + service.open_roll() + assert roll.closed is True + + +class TestWriteFrame: + def _frame(self, fake_coolscanpy, *, slot=7, ir=None): + rgb = np.random.randint(0, 65535, (40, 60, 3), dtype=np.uint16) + receipt = fake_coolscanpy.Receipt(version=1, slot=slot, dpi=4000, depth=16, device_id="usb:1:2", transport_smear_verdict="clean") + return fake_coolscanpy.Frame(slot=slot, rgb=rgb, ir=ir, ir_validity=None, receipt=receipt) + + def test_writes_rgb_tiff(self, fake_coolscanpy, tmp_path) -> None: + frame = self._frame(fake_coolscanpy) + service = RollScanningService() + + output = service.write_frame(frame, str(tmp_path), '{{ date }}_slot{{ "%02d" % seq }}') + + assert os.path.exists(output.rgb_path) + assert output.rgb_path.endswith(".tif") + readback = tifffile.imread(output.rgb_path) + assert readback.shape == (40, 60, 3) + assert readback.dtype == np.uint16 + np.testing.assert_array_equal(readback, frame.rgb) + + def test_seq_seeded_from_slot_number(self, fake_coolscanpy, tmp_path) -> None: + frame = self._frame(fake_coolscanpy, slot=23) + service = RollScanningService() + + output = service.write_frame(frame, str(tmp_path), '{{ "%03d" % seq }}') + + assert "023" in os.path.basename(output.rgb_path) + + def test_writes_ir_sidecar_when_present(self, fake_coolscanpy, tmp_path) -> None: + ir = np.random.randint(0, 65535, (40, 60), dtype=np.uint16) + frame = self._frame(fake_coolscanpy, ir=ir) + service = RollScanningService() + + output = service.write_frame(frame, str(tmp_path), '{{ "%03d" % seq }}') + + assert output.ir_path is not None + assert output.ir_path.endswith("_IR.tif") + assert os.path.exists(output.ir_path) + readback = tifffile.imread(output.ir_path) + np.testing.assert_array_equal(readback, ir) + + def test_no_ir_sidecar_when_absent(self, fake_coolscanpy, tmp_path) -> None: + frame = self._frame(fake_coolscanpy, ir=None) + service = RollScanningService() + + output = service.write_frame(frame, str(tmp_path), '{{ "%03d" % seq }}') + + assert output.ir_path is None + assert not any(name.endswith("_IR.tif") for name in os.listdir(tmp_path)) + + def test_writes_receipt_json_sidecar(self, fake_coolscanpy, tmp_path) -> None: + frame = self._frame(fake_coolscanpy, slot=9) + service = RollScanningService() + + output = service.write_frame(frame, str(tmp_path), '{{ "%03d" % seq }}') + + assert output.receipt_path.endswith("_receipt.json") + with open(output.receipt_path) as fh: + payload = json.load(fh) + assert payload["slot"] == 9 + assert payload["dpi"] == 4000 + assert payload["transport_smear_verdict"] == "clean" + + def test_rescanning_same_slot_overwrites(self, fake_coolscanpy, tmp_path) -> None: + service = RollScanningService() + pattern = '{{ "%03d" % seq }}' + + first = service.write_frame(self._frame(fake_coolscanpy, slot=4), str(tmp_path), pattern) + second_frame = self._frame(fake_coolscanpy, slot=4) + second = service.write_frame(second_frame, str(tmp_path), pattern) + + assert first.rgb_path == second.rgb_path + readback = tifffile.imread(second.rgb_path) + np.testing.assert_array_equal(readback, second_frame.rgb) + + def test_creates_output_folder(self, fake_coolscanpy, tmp_path) -> None: + nested = tmp_path / "does" / "not" / "exist" / "yet" + frame = self._frame(fake_coolscanpy) + service = RollScanningService() + + output = service.write_frame(frame, str(nested), '{{ "%03d" % seq }}') + + assert os.path.exists(output.rgb_path) From 1d5d75316cbba73c01082d67015a159b721fda18 Mon Sep 17 00:00:00 2001 From: Rohan Pandula Date: Sat, 18 Jul 2026 22:32:43 -0700 Subject: [PATCH 04/48] Document coolscanpy roll-scanning integration Explains what the integration adds, install steps, the LS-5000/SA-30 hardware caveat, the on-disk output convention, error handling, and where the single integration point lives for a future re-point onto the maintainer's own generic SANE-based coolscan route. Also documents the follow-up: no contact-sheet GUI panel yet, and where one would plug in. --- docs/COOLSCANPY_ROLL_SCANNING.md | 158 +++++++++++++++++++++++++++++++ 1 file changed, 158 insertions(+) create mode 100644 docs/COOLSCANPY_ROLL_SCANNING.md diff --git a/docs/COOLSCANPY_ROLL_SCANNING.md b/docs/COOLSCANPY_ROLL_SCANNING.md new file mode 100644 index 00000000..56cda623 --- /dev/null +++ b/docs/COOLSCANPY_ROLL_SCANNING.md @@ -0,0 +1,158 @@ +# Roll scanning with coolscanpy + +This page describes NegPy's optional integration with +[coolscanpy](https://github.com/rohanpandula/coolscanpy), a standalone +Python library for whole-roll scanning on a Nikon Coolscan LS-5000 with a +roll feeder. coolscanpy talks to the scanner directly over USB and does not +need SANE installed. NegPy consumes it the same way it consumes +python-sane or gphoto2: as an optional dependency that the rest of the +application never requires. + +## What it adds + +The plain Scan panel in NegPy scans one frame at a time through SANE. A +roll feeder changes the shape of the problem: you preview the whole roll in +one transport pass, check or nudge each frame's spacing, and then run a +batch fine scan across a list of slots without reloading the film. That +whole-roll workflow, preview, spacing correction, approval of any frame the +transport could not place automatically, and batch scanning with per-frame +receipts, is what coolscanpy implements and what this integration exposes +inside NegPy. + +Coverage is narrower than the plain Scan panel. Only color negative film +scans through coolscanpy's roll engine end to end today. Black and white +negative previews and approves normally, but batch fine scanning it raises +`NotImplementedError` inside coolscanpy itself; that limitation is +documented in the coolscanpy README and is unchanged by this integration. + +## Install + +Nothing here is required to run NegPy. The feature is entirely absent +until coolscanpy is importable, and every entry point checks for that +before doing anything else. + +If you use uv, sync the new dependency group the same way you already +sync the `scanner` group for python-sane: + +``` +uv sync --group coolscan-roll +``` + +If you manage your own environment instead, install the package directly: + +``` +pip install coolscanpy +``` + +coolscanpy has no SANE dependency for the roll workflow. See the +coolscanpy README if you also want its plain single-frame `scan()` path, +which does need SANE. + +## Hardware support + +coolscanpy, and by extension this integration, has been validated against +one specific setup: a Nikon Super Coolscan 5000 ED (LS-5000) running +firmware 1.03, with an SA-21 roll feeder wired for SA-30 compatibility. +Live hardware validation on 2026-07-18 confirmed USB enumeration, device +open, and a full roll preview with correct spacing and manual-review +flagging. Fine scanning and infrared capture through the roll engine have +not yet been re-run live since coolscanpy was extracted into its own +package; that is the remaining validation step on the coolscanpy side, not +something specific to this NegPy integration. + +Every other Coolscan model and every roll feeder other than the SA-21/SA-30 +combination above is untested. The transport protocol is not assumed to be +LS-5000-only where it does not have to be, but nothing beyond that one +combination has scanned real film. Windows has never run the roll engine +against hardware. + +## What this integration writes + +A batch scan produces, per slot, three files in the configured output +folder: + +- a 16-bit TIFF holding the scanner-linear RGB image +- a 16-bit TIFF holding the infrared plane, named with an `_IR` suffix + next to the RGB file, matching the suffix NegPy's plain scan writer + already uses for infrared +- a JSON file with an `_receipt` suffix, holding the full scan receipt + coolscanpy returns for that frame: exposure, clipping and focus + telemetry, transport-smear assessment, and the fingerprints the frame was + checked against + +The filename pattern is the same Jinja2 template the plain Scan panel +uses, with `date` and `seq` variables. For roll scanning, `seq` is the +frame's physical slot number rather than an incrementing counter. Slot +numbers are already a stable identity for a given roll, so re-scanning one +bad slot overwrites that slot's old files instead of accumulating a second +copy beside them. + +The infrared confidence mask coolscanpy also returns per frame is not +written to disk by this integration. Nothing downstream currently reads +it, and adding a file format for it before there is a consumer would be +guessing at a convention rather than following one. + +## Error handling + +coolscanpy raises a typed exception hierarchy rooted at +`PyCoolscanError`, covering things like a roll that no longer matches its +last preview, a slot that needs manual approval before it can be scanned, +and a requested safe stop. NegPy's existing scanner code has no typed +exception vocabulary of its own; `SaneBackend` reports every failure as a +plain `RuntimeError` with a human-readable message, and that message is +what ends up in the Scan panel's status label. This integration follows +the same convention: every coolscanpy exception is flattened to a +`RuntimeError` at the boundary, with the original exception preserved as +`__cause__` for any caller that wants to branch on it. + +One case is worth calling out for whoever builds the interactive panel +described below. `Roll.safe_stop()` lets the frame already in flight +finish and only refuses the next one, by raising `SafeStopRequested`. The +existing plain-scan worker treats a cancellation the same way, checking +its own cancel flag and returning quietly instead of reporting an error. +A roll-scanning worker should do the same for a `RuntimeError` whose +`__cause__` is `SafeStopRequested`: that is an expected stop, not a +failure. + +## The integration point + +Everything that touches coolscanpy itself lives in one file, +`negpy/infrastructure/scanners/coolscanpy_roll.py`. `open_roll()` in that +file is the single place a device handle gets resolved for the roll +workflow; it currently calls `coolscanpy.open()` directly. Everything else, +including `negpy/services/scanning/roll_service.py`, only ever talks to the +`RollHandle` that function returns. + +That module is marked in a comment as the intended target for a future +change. NegPy's maintainer has a generic SANE-based coolscan route planned +upstream, which is expected to add a real backend-selection seam to +`ScannerService` (today `ScannerService._get_backend()` simply hardcodes +`SaneBackend()`, with no selection mechanism at all). When that seam +lands, re-pointing this roll integration at it should only require changing +how `open_roll()` resolves a device. The roll workflow built on top of it, +the exception translation in the same file, and the whole of +`roll_service.py`, should not need to change. + +## What is not built yet + +This integration ships the backend and service layer: device discovery, +roll preview, spacing correction, approval, batch scanning, and file +writing, all covered by hardware-free tests. It does not ship a contact +sheet panel with rendered thumbnail images in the desktop GUI. Building +that panel from scratch, before the maintainer's own SANE-based route +defines how a Coolscan device is meant to surface in the UI, risked adding +exactly the kind of large, hard-to-review, foreign-feeling change this +integration is meant to avoid. + +A future panel can follow the existing Scan panel closely. Look at +`negpy/desktop/view/sidebar/scan.py` for the sidebar structure and its +`_sane_available()` availability gate, `negpy/desktop/workers/scan_worker.py` +for the background-thread pattern (a `QObject` moved to its own `QThread`, +progress and result reported back over Qt signals), and +`negpy/desktop/controller.py` for how that worker's signals get wired into +the rest of the application. A roll panel would follow the same shape, +calling `RollScanningService` instead of `ScannerService`, gated behind +`coolscanpy_roll.available()` the same way the plain panel gates itself +behind SANE. The simplest first version would not need thumbnail images at +all: a plain list of slots with their approval state, backed by +`Roll.preview()`, is enough to let someone pick which slots to scan. From da94eff57ad0531b39c9c0e97a82085ed20f90e4 Mon Sep 17 00:00:00 2001 From: Rohan Pandula Date: Sat, 18 Jul 2026 23:17:17 -0700 Subject: [PATCH 05/48] Move roll scanning into its own infrastructure/services package The camera capture route lives beside scanners/ and scanning/ as its own infrastructure/capture + services/capture pair, gated on an optional dependency, rather than inside the single-frame ScannerBackend protocol. Give roll scanning the same shape: infrastructure/roll/coolscanpy_roll.py and services/roll/service.py, instead of sitting inside the plain-SANE scanners/scanning packages where it doesn't implement that protocol and never will. Also adds a RollScanningError for this service's own lifecycle misuse (mirrors CaptureError), an is_safe_stop() helper next to the existing exception translation so a future worker can tell a deliberate safe_stop() outcome from a real failure, and precise on_progress/return typing sourced from coolscanpy's actual Progress/Thumbnail/Frame types. No behavior change to the preview/approve/scan/write workflow itself. --- negpy/infrastructure/roll/__init__.py | 8 +++ .../{scanners => roll}/coolscanpy_roll.py | 50 +++++++++++++------ negpy/infrastructure/roll/settings.py | 26 ++++++++++ negpy/services/roll/__init__.py | 15 ++++++ .../roll_service.py => roll/service.py} | 36 ++++++++----- tests/roll/__init__.py | 0 tests/{scanners => roll}/conftest.py | 12 ++--- .../test_coolscanpy_roll.py | 2 +- .../test_service.py} | 10 ++-- 9 files changed, 120 insertions(+), 39 deletions(-) create mode 100644 negpy/infrastructure/roll/__init__.py rename negpy/infrastructure/{scanners => roll}/coolscanpy_roll.py (77%) create mode 100644 negpy/infrastructure/roll/settings.py create mode 100644 negpy/services/roll/__init__.py rename negpy/services/{scanning/roll_service.py => roll/service.py} (82%) create mode 100644 tests/roll/__init__.py rename tests/{scanners => roll}/conftest.py (92%) rename tests/{scanners => roll}/test_coolscanpy_roll.py (99%) rename tests/{scanners/test_roll_service.py => roll/test_service.py} (94%) diff --git a/negpy/infrastructure/roll/__init__.py b/negpy/infrastructure/roll/__init__.py new file mode 100644 index 00000000..c390fe95 --- /dev/null +++ b/negpy/infrastructure/roll/__init__.py @@ -0,0 +1,8 @@ +"""Coolscan roll-feeder capture layer -- no Qt, no NegPy file model. + +Drives a Nikon Coolscan LS-5000 plus SA-21/SA-30 roll feeder through the +optional `coolscanpy` package: whole-roll preview, per-slot spacing +correction and approval, and batch fine-scanning with receipts. Entirely +absent when `coolscanpy` is not installed -- see +`coolscanpy_roll.available()`. +""" diff --git a/negpy/infrastructure/scanners/coolscanpy_roll.py b/negpy/infrastructure/roll/coolscanpy_roll.py similarity index 77% rename from negpy/infrastructure/scanners/coolscanpy_roll.py rename to negpy/infrastructure/roll/coolscanpy_roll.py index bd466550..a011f0f0 100644 --- a/negpy/infrastructure/scanners/coolscanpy_roll.py +++ b/negpy/infrastructure/roll/coolscanpy_roll.py @@ -9,9 +9,10 @@ This module owns every place coolscanpy itself gets imported or driven for the roll workflow: opening a device, opening its roll extension, and -translating coolscanpy's exceptions. `negpy.services.scanning.roll_service` -builds the file-writing workflow on top of it but never imports coolscanpy -directly. +translating coolscanpy's exceptions. `negpy.services.roll.service` builds +the file-writing workflow on top of it but never imports coolscanpy +directly, matching how `negpy.services.capture.service` never imports +`gphoto2` directly either. -------------------------------------------------------------------------- INTEGRATION POINT for a future re-point @@ -25,9 +26,9 @@ adapter at it should only mean changing how `open_roll()` resolves a device -- everything built on top of the returned `RollHandle` (this module's exception translation, and all of -`negpy/services/scanning/roll_service.py`) is unaffected, since it never -talks to coolscanpy except through this one function and the `RollHandle` -it returns. +`negpy/services/roll/service.py`) is unaffected, since it never talks to +coolscanpy except through this one function and the `RollHandle` it +returns. -------------------------------------------------------------------------- """ @@ -70,7 +71,9 @@ def __init__(self, device: "coolscanpy.Device", roll: "coolscanpy.Roll") -> None self._device = device self._roll = roll - def preview(self, slots: Iterable[int] | None = None, *, on_progress=None) -> list: + def preview( + self, slots: Iterable[int] | None = None, *, on_progress: "coolscanpy.ProgressCallback | None" = None + ) -> "list[coolscanpy.Thumbnail]": """One whole-roll transport read. See `coolscanpy.Roll.preview`.""" try: return self._roll.preview(slots, on_progress=on_progress) @@ -95,15 +98,15 @@ def needs_approval(self, slot: int) -> bool: except Exception as error: raise _translate(error) from error - def scan_many(self, slots: Iterable[int], *, on_progress=None) -> Iterator["coolscanpy.Frame"]: + def scan_many( + self, slots: Iterable[int], *, on_progress: "coolscanpy.ProgressCallback | None" = None + ) -> Iterator["coolscanpy.Frame"]: """Batch fine-scan `slots` in one transport reservation. - Note for a future caller that wires this to a cancel affordance - (mirroring how `ScanWorker.run_scan` swallows a cancelled plain scan - rather than reporting it as an error): a `RuntimeError` raised here - whose `__cause__` is a `coolscanpy.SafeStopRequested` means - `safe_stop()` was already called deliberately and should be treated - the same way, not surfaced as a failure. + A `RuntimeError` raised here whose `__cause__` is a + `coolscanpy.SafeStopRequested` means `safe_stop()` was already + called deliberately and should be treated as a clean stop, not + surfaced as a failure -- see `is_safe_stop()`. """ try: yield from self._roll.scan_many(slots, on_progress=on_progress) @@ -170,6 +173,23 @@ def _translate(error: BaseException) -> RuntimeError: `FingerprintRefused`, `ManualReviewRequired`, `SafeStopRequested`, ...) and the handful of plain `ValueError`/`RuntimeError` coolscanpy itself raises (e.g. "no roll adapter is attached"), so nothing coolscanpy-shaped - ever crosses out of this module. + ever crosses out of this module. `raise _translate(error) from error` + always preserves the original as `__cause__` -- see `is_safe_stop()`. """ return RuntimeError(str(error)) + + +def is_safe_stop(error: BaseException) -> bool: + """True if `error` is this module's translation of a deliberate + `RollHandle.safe_stop()` outcome, not a genuine failure. + + `error` is normally the `RuntimeError` a caller of `scan_many()` just + caught; every translation in this module chains `from error`, so the + original `coolscanpy.SafeStopRequested` (when that is what happened) + is always sitting in `__cause__`. Imports coolscanpy lazily like the + rest of this module -- safe here because an error this module raised + already means coolscanpy was reachable. + """ + import coolscanpy + + return isinstance(getattr(error, "__cause__", None), coolscanpy.SafeStopRequested) diff --git a/negpy/infrastructure/roll/settings.py b/negpy/infrastructure/roll/settings.py new file mode 100644 index 00000000..e27799dc --- /dev/null +++ b/negpy/infrastructure/roll/settings.py @@ -0,0 +1,26 @@ +"""Persisted Roll Scanning panel settings (stored as a global setting dict).""" + +from __future__ import annotations + +from dataclasses import dataclass + + +@dataclass(frozen=True) +class RollScanSettings: + """Sticky settings for the Roll Scanning sidebar. + + Persisted via the session repo under the `roll_scan_settings` key, + mirroring `ScannerSettings` and `ScanlightSettings`. `last_device_id` + empty means no device remembered yet; per-slot spacing offsets and + approvals are not persisted here at all -- they live on the open + coolscanpy `Roll` for as long as it stays open, and `preview()` resets + them on its own the moment it re-reads the transport. + """ + + last_device_id: str = "" + output_folder: str = "" + filename_pattern: str = '{{ date }}_{{ "%03d" % seq }}' + + @classmethod + def defaults(cls) -> "RollScanSettings": + return cls() diff --git a/negpy/services/roll/__init__.py b/negpy/services/roll/__init__.py new file mode 100644 index 00000000..810cf5b8 --- /dev/null +++ b/negpy/services/roll/__init__.py @@ -0,0 +1,15 @@ +"""Roll-scanning orchestration -- drives a coolscanpy Device + Roll extension +through preview, spacing correction, approval and batch fine-scanning. + +No Qt dependencies (mirrors `negpy.services.capture` and +`negpy.services.scanning`). +""" + +from negpy.services.roll.service import RollFrameOutput, RollScanningError, RollScanningService, available + +__all__ = [ + "RollFrameOutput", + "RollScanningError", + "RollScanningService", + "available", +] diff --git a/negpy/services/scanning/roll_service.py b/negpy/services/roll/service.py similarity index 82% rename from negpy/services/scanning/roll_service.py rename to negpy/services/roll/service.py index 5855a8f8..13d64a5c 100644 --- a/negpy/services/scanning/roll_service.py +++ b/negpy/services/roll/service.py @@ -1,11 +1,13 @@ """Roll-scanning service backed by the optional `coolscanpy` package. -Mirrors `negpy.services.scanning.service.ScannerService`: one class that -orchestrates device discovery, the hardware workflow, and writing results to -disk in NegPy's conventional layout. Where `ScannerService` wraps a single +Sibling of `negpy.services.capture.service.CaptureService`: one class that +orchestrates the hardware workflow and writes results to disk in NegPy's +conventional layout, on top of an infrastructure-layer adapter for an +optional dependency (`negpy.infrastructure.roll.coolscanpy_roll`, mirroring +`negpy.infrastructure.capture.gphoto`). It also mirrors the older, simpler +`negpy.services.scanning.service.ScannerService`: where that wraps a single ad-hoc `Device.scan()`, this wraps coolscanpy's whole-roll workflow -(preview -> approve -> batch fine-scan) exposed by -`negpy.infrastructure.scanners.coolscanpy_roll`. +(preview -> approve -> batch fine-scan). Entirely inert if `coolscanpy` is not installed: `available()` is a cheap presence check re-exported from that module, and every other method only @@ -25,7 +27,7 @@ import numpy as np import tifffile -from negpy.infrastructure.scanners import coolscanpy_roll +from negpy.infrastructure.roll import coolscanpy_roll from negpy.kernel.system.logging import get_logger from negpy.services.scanning.templating import render_scan_filename @@ -34,12 +36,18 @@ logger = get_logger(__name__) -# Re-exported so callers only need this module: `from negpy.services.scanning -# .roll_service import available`, matching how the plain Scan sidebar checks +# Re-exported so callers only need this module: `from negpy.services.roll +# .service import available`, matching how the plain Scan sidebar checks # `_sane_available()` before showing its device combo. available = coolscanpy_roll.available +class RollScanningError(RuntimeError): + """Raised for a roll-scanning failure that originates in this service's + own orchestration (lifecycle misuse), as opposed to one translated from + coolscanpy itself. Mirrors `negpy.services.capture.service.CaptureError`.""" + + @dataclasses.dataclass(frozen=True) class RollFrameOutput: """Where one scanned frame's files landed on disk.""" @@ -69,7 +77,7 @@ def list_devices(self) -> "list[coolscanpy.DeviceInfo]": def open_roll(self, device_id: str | None = None, *, material: "coolscanpy.Material | None" = None) -> None: """Open a device and its roll extension. Call `close()` when done.""" if self._roll is not None: - raise RuntimeError("a roll is already open on this service; call close() first") + raise RollScanningError("a roll is already open on this service; call close() first") self._roll = coolscanpy_roll.open_roll(device_id, material=material) def close(self) -> None: @@ -86,7 +94,9 @@ def __exit__(self, *exc: object) -> None: # -- preview / approval ------------------------------------------------ - def preview(self, slots: Iterable[int] | None = None, *, on_progress=None) -> list: + def preview( + self, slots: Iterable[int] | None = None, *, on_progress: "coolscanpy.ProgressCallback | None" = None + ) -> "list[coolscanpy.Thumbnail]": return self._require_roll().preview(slots, on_progress=on_progress) def set_spacing_offset(self, slot: int, offset_rows: int) -> None: @@ -100,7 +110,9 @@ def needs_approval(self, slot: int) -> bool: # -- scanning ---------------------------------------------------------- - def scan_many(self, slots: Iterable[int], *, on_progress=None) -> Iterator["coolscanpy.Frame"]: + def scan_many( + self, slots: Iterable[int], *, on_progress: "coolscanpy.ProgressCallback | None" = None + ) -> Iterator["coolscanpy.Frame"]: yield from self._require_roll().scan_many(slots, on_progress=on_progress) def safe_stop(self) -> None: @@ -146,7 +158,7 @@ def write_frame(self, frame: "coolscanpy.Frame", output_folder: str, filename_pa def _require_roll(self) -> coolscanpy_roll.RollHandle: if self._roll is None: - raise RuntimeError("no roll is open; call open_roll() first") + raise RollScanningError("no roll is open; call open_roll() first") return self._roll diff --git a/tests/roll/__init__.py b/tests/roll/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/scanners/conftest.py b/tests/roll/conftest.py similarity index 92% rename from tests/scanners/conftest.py rename to tests/roll/conftest.py index 5890566a..3cfc79ce 100644 --- a/tests/scanners/conftest.py +++ b/tests/roll/conftest.py @@ -1,10 +1,10 @@ -"""Shared fixtures for tests/scanners/: a fake `coolscanpy` module. +"""Shared fixtures for tests/roll/: a fake `coolscanpy` module. -`coolscanpy_roll.py` / `roll_service.py` treat coolscanpy as optional and -import it lazily, so these tests never need the real package installed -- -`fake_coolscanpy` injects a minimal stand-in module into `sys.modules` -instead, exercising the same `import coolscanpy` path the production code -takes. +`negpy.infrastructure.roll.coolscanpy_roll` and `negpy.services.roll.service` +treat coolscanpy as optional and import it lazily, so these tests never need +the real package installed -- `fake_coolscanpy` injects a minimal stand-in +module into `sys.modules` instead, exercising the same `import coolscanpy` +path the production code takes. """ from __future__ import annotations diff --git a/tests/scanners/test_coolscanpy_roll.py b/tests/roll/test_coolscanpy_roll.py similarity index 99% rename from tests/scanners/test_coolscanpy_roll.py rename to tests/roll/test_coolscanpy_roll.py index 74009d7d..b90cccda 100644 --- a/tests/scanners/test_coolscanpy_roll.py +++ b/tests/roll/test_coolscanpy_roll.py @@ -13,7 +13,7 @@ import numpy as np import pytest -from negpy.infrastructure.scanners import coolscanpy_roll +from negpy.infrastructure.roll import coolscanpy_roll class TestAvailable: diff --git a/tests/scanners/test_roll_service.py b/tests/roll/test_service.py similarity index 94% rename from tests/scanners/test_roll_service.py rename to tests/roll/test_service.py index 2ac621ba..df07168b 100644 --- a/tests/scanners/test_roll_service.py +++ b/tests/roll/test_service.py @@ -1,6 +1,6 @@ """Tests for RollScanningService: lifecycle orchestration and output writing. -Lifecycle tests use `fake_coolscanpy` (see tests/scanners/conftest.py) the +Lifecycle tests use `fake_coolscanpy` (see tests/roll/conftest.py) the same way test_coolscanpy_roll.py does. write_frame() tests construct a fake Frame/Receipt directly -- writing to disk never touches coolscanpy itself, so no module injection is needed for those. @@ -15,8 +15,8 @@ import pytest import tifffile -from negpy.services.scanning import roll_service -from negpy.services.scanning.roll_service import RollScanningService +from negpy.services.roll import service as roll_service +from negpy.services.roll.service import RollScanningError, RollScanningService class TestAvailable: @@ -41,12 +41,12 @@ def test_open_then_close(self, fake_coolscanpy) -> None: def test_double_open_raises(self, fake_coolscanpy) -> None: service, _roll, _device = self._open_service(fake_coolscanpy) - with pytest.raises(RuntimeError, match="already open"): + with pytest.raises(RollScanningError, match="already open"): service.open_roll() def test_methods_before_open_raise(self) -> None: service = RollScanningService() - with pytest.raises(RuntimeError, match="no roll is open"): + with pytest.raises(RollScanningError, match="no roll is open"): service.preview() def test_close_without_open_is_a_no_op(self) -> None: From dbc6013974dc48f7744fd52da31cac85c201b9f1 Mon Sep 17 00:00:00 2001 From: Rohan Pandula Date: Sat, 18 Jul 2026 23:22:26 -0700 Subject: [PATCH 06/48] Add RollWorker: background worker for coolscanpy roll scanning Mirrors CaptureWorker's shape: a QObject moved to its own QThread, request dataclasses for the operations with a progress/status lifecycle (RollPreviewRequest, RollBatchScanRequest), and one open hardware session held across calls rather than reopened per operation. _ensure_open() lazily opens the roll for whichever device a preview or batch-scan request names, closing a previous reservation on a device switch, mirroring _acquire_camera()'s idempotent-session pattern. Cancellation has no local threading.Event the way CaptureWorker's does: a fine-scan in flight can't be aborted mid-pull, so safe_stop() (thread- safe, called directly like CaptureWorker.cancel()) is the only stop, and it always lets the in-flight frame finish. run_batch_scan tells that clean stop apart from a genuine failure via coolscanpy_roll.is_safe_stop(). Extends the tests/roll/ fake_coolscanpy fixture's FakeRoll to actually call on_progress during preview()/scan_many(), matching coolscanpy's real Progress-object shape, so the worker's progress-forwarding has real coverage instead of being untestable. --- negpy/desktop/workers/roll_worker.py | 179 ++++++++++++++++ tests/roll/conftest.py | 24 ++- tests/roll/test_worker.py | 296 +++++++++++++++++++++++++++ 3 files changed, 497 insertions(+), 2 deletions(-) create mode 100644 negpy/desktop/workers/roll_worker.py create mode 100644 tests/roll/test_worker.py diff --git a/negpy/desktop/workers/roll_worker.py b/negpy/desktop/workers/roll_worker.py new file mode 100644 index 00000000..21b6d080 --- /dev/null +++ b/negpy/desktop/workers/roll_worker.py @@ -0,0 +1,179 @@ +"""Background worker for coolscanpy roll scanning. Mirrors CaptureWorker. + +Owns one open RollScanningService reservation (a coolscanpy `Device` plus +its `Roll` extension), held across preview/approve/scan calls and opened +lazily for whichever device the sidebar asks for -- mirrors how +CaptureWorker's `_acquire_camera()` opens and holds one libgphoto2 session +rather than reconnecting for every call. +""" + +from __future__ import annotations + +from dataclasses import dataclass + +from PyQt6.QtCore import QObject, pyqtSignal, pyqtSlot + +from negpy.infrastructure.roll import coolscanpy_roll +from negpy.kernel.system.logging import get_logger +from negpy.services.roll.service import RollFrameOutput, RollScanningService + +logger = get_logger(__name__) + + +@dataclass(frozen=True) +class RollPreviewRequest: + device_id: str + slots: tuple[int, ...] = () # empty = the whole roll + + +@dataclass(frozen=True) +class RollBatchScanRequest: + device_id: str + slots: tuple[int, ...] + output_folder: str + filename_pattern: str + + +class RollWorker(QObject): + """Drives one coolscanpy Device + Roll reservation off the UI thread.""" + + devices_ready = pyqtSignal(list) # list[coolscanpy.DeviceInfo] + opened = pyqtSignal(str) # device_id now open + closed = pyqtSignal() + preview_ready = pyqtSignal(list) # list[coolscanpy.Thumbnail] + spacing_offset_set = pyqtSignal(int, int) # slot, offset_rows actually applied + approved = pyqtSignal(int) # slot + progress = pyqtSignal(float, str) # 0.0..1.0, message + frame_written = pyqtSignal(object) # RollFrameOutput + finished = pyqtSignal(list) # list[RollFrameOutput] written this batch + cancelled = pyqtSignal() + error = pyqtSignal(str) + status = pyqtSignal(str) + + def __init__(self) -> None: + super().__init__() + self._service = RollScanningService() + self._open_device_id: str | None = None + + # ----- device / roll lifecycle ----- + + @pyqtSlot() + def list_devices(self) -> None: + try: + devices = self._service.list_devices() + self.devices_ready.emit(devices) + except Exception as e: + logger.exception("roll device listing failed") + self.error.emit(str(e)) + self.devices_ready.emit([]) + + def _ensure_open(self, device_id: str) -> None: + """Open `device_id`'s roll extension if it isn't already the one held. + + Switching to a different device closes the previous reservation + first -- coolscanpy allows only one `Roll` open per device, and a + stale reservation on the old device would otherwise sit open with + nothing left able to use it. + """ + if self._open_device_id == device_id: + return + if self._open_device_id is not None: + self._service.close() + self._open_device_id = None + self.status.emit("Opening roll…") + self._service.open_roll(device_id) + self._open_device_id = device_id + self.opened.emit(device_id) + + @pyqtSlot() + def close_roll(self) -> None: + try: + self._service.close() + except Exception: + logger.exception("error closing roll") + finally: + self._open_device_id = None + self.closed.emit() + + # ----- preview / approval ----- + + @pyqtSlot(RollPreviewRequest) + def run_preview(self, req: RollPreviewRequest) -> None: + try: + self._ensure_open(req.device_id) + self.status.emit("Reading roll transport…") + thumbnails = self._service.preview( + req.slots or None, + on_progress=lambda p: self.progress.emit(p.fraction, p.message), + ) + self.preview_ready.emit(thumbnails) + self.status.emit(f"Previewed {len(thumbnails)} slot(s).") + except Exception as e: + logger.exception("roll preview failed") + self.error.emit(f"Preview: {e}") + + @pyqtSlot(int, int) + def set_spacing_offset(self, slot: int, offset_rows: int) -> None: + try: + self._service.set_spacing_offset(slot, offset_rows) + self.spacing_offset_set.emit(slot, offset_rows) + except Exception as e: + logger.exception("set_spacing_offset failed") + self.error.emit(f"Spacing offset: {e}") + + @pyqtSlot(int) + def approve(self, slot: int) -> None: + try: + self._service.approve(slot) + self.approved.emit(slot) + except Exception as e: + logger.exception("approve failed") + self.error.emit(f"Approve: {e}") + + # ----- scanning ----- + + @pyqtSlot(RollBatchScanRequest) + def run_batch_scan(self, req: RollBatchScanRequest) -> None: + written: list[RollFrameOutput] = [] + try: + self._ensure_open(req.device_id) + self.status.emit("Scanning…") + for frame in self._service.scan_many( + req.slots, + on_progress=lambda p: self.progress.emit(p.fraction, p.message), + ): + output = self._service.write_frame(frame, req.output_folder, req.filename_pattern) + written.append(output) + self.frame_written.emit(output) + self.finished.emit(written) + except Exception as e: + if coolscanpy_roll.is_safe_stop(e): + # safe_stop() was requested deliberately (see `safe_stop` below): the frame + # already in flight always finishes -- and is already written, already on + # disk, already in `written` -- so this is a clean stop, not a failure. + self.cancelled.emit() + return + logger.exception("roll batch scan failed") + self.error.emit(str(e)) + + def safe_stop(self) -> None: + """Request a graceful stop of an in-progress batch scan. + + Not a `@pyqtSlot`, called directly cross-thread like `CaptureWorker + .cancel()`: `RollScanningService.safe_stop()` only sets a + `threading.Event`, which is thread-safe on its own and needs no Qt + queueing. The transport has no equivalent of the camera route's + instant `threading.Event`-gated cancel -- a fine-scan already in + flight cannot be aborted mid-pull, so unlike `CaptureWorker` there + is no separate local cancel flag here; safe_stop() is the only stop, + and it always lets that one frame finish first. + """ + self._service.safe_stop() + + def shutdown(self) -> None: + """Stop any scan and release the roll (called on app teardown).""" + self.safe_stop() + try: + self._service.close() + except Exception: + logger.exception("error closing roll on shutdown") diff --git a/tests/roll/conftest.py b/tests/roll/conftest.py index 3cfc79ce..47eb4659 100644 --- a/tests/roll/conftest.py +++ b/tests/roll/conftest.py @@ -73,6 +73,18 @@ class FakeFrame: receipt: Any +@dataclasses.dataclass(frozen=True) +class FakeProgress: + """Stand-in for coolscanpy.types.Progress, same field names.""" + + stage: str + slot: Any + index: int + total: int + fraction: float + message: str + + class FakeRoll: """Scripted in-memory stand-in for `coolscanpy.Roll`.""" @@ -88,8 +100,13 @@ def __init__(self, thumbnails=None, frames=None, raise_on=None) -> None: def preview(self, slots=None, *, on_progress=None): if "preview" in self._raise_on: raise self._raise_on["preview"] + if on_progress is not None: + on_progress(FakeProgress(stage="preview", slot=None, index=0, total=1, fraction=0.0, message="reading whole-roll transport index")) wanted = None if slots is None else set(slots) - return [t for t in self._thumbnails if wanted is None or t.slot in wanted] + result = [t for t in self._thumbnails if wanted is None or t.slot in wanted] + if on_progress is not None: + on_progress(FakeProgress(stage="preview", slot=None, index=1, total=1, fraction=1.0, message="preview complete")) + return result def set_spacing_offset(self, slot, offset_rows) -> None: self.spacing_offsets[slot] = offset_rows @@ -103,10 +120,13 @@ def needs_approval(self, slot) -> bool: return slot in self._raise_on.get("needs_approval_slots", ()) def scan_many(self, slots, *, on_progress=None): - for slot in slots: + ordered = list(slots) + for i, slot in enumerate(ordered): error = self._raise_on.get("scan_many_slots", {}).get(slot) if error is not None: raise error + if on_progress is not None: + on_progress(FakeProgress(stage="fine-scan", slot=slot, index=i, total=len(ordered), fraction=1.0, message=f"slot {slot} complete")) yield self._frames[slot] def safe_stop(self) -> None: diff --git a/tests/roll/test_worker.py b/tests/roll/test_worker.py new file mode 100644 index 00000000..c16178ab --- /dev/null +++ b/tests/roll/test_worker.py @@ -0,0 +1,296 @@ +"""Tests for RollWorker: signal emissions, device-session reuse, and the +safe-stop / cancellation distinction. Mirrors test_capture_worker.py's +shape: fakes wired directly into the worker, signals captured with plain +list-appending slots, no QThread or QApplication involved. +""" + +from __future__ import annotations + +import os + +import numpy as np + +from negpy.desktop.workers.roll_worker import RollBatchScanRequest, RollPreviewRequest, RollWorker + + +def _frame(fake_coolscanpy, *, slot, ir=None): + rgb = np.random.randint(0, 65535, (4, 6, 3), dtype=np.uint16) + receipt = fake_coolscanpy.Receipt(version=1, slot=slot, dpi=4000, depth=16, device_id="usb:1:2", transport_smear_verdict="clean") + return fake_coolscanpy.Frame(slot=slot, rgb=rgb, ir=ir, ir_validity=None, receipt=receipt) + + +def _open(fake_coolscanpy, roll=None): + roll = roll if roll is not None else fake_coolscanpy.Roll() + device = fake_coolscanpy.Device(roll) + fake_coolscanpy.state["open_device"] = device + return roll, device + + +class TestListDevices: + def test_success_emits_devices(self, fake_coolscanpy) -> None: + fake_coolscanpy.state["devices"] = ["device-a", "device-b"] + worker = RollWorker() + seen = [] + worker.devices_ready.connect(seen.append) + + worker.list_devices() + + assert seen == [["device-a", "device-b"]] + + def test_failure_emits_error_and_empty_list(self, fake_coolscanpy, monkeypatch) -> None: + worker = RollWorker() + + def boom(local_only: bool = False): + raise RuntimeError("usb enumeration failed") + + monkeypatch.setattr(fake_coolscanpy.module, "get_devices", boom) + errors, devices = [], [] + worker.error.connect(errors.append) + worker.devices_ready.connect(devices.append) + + worker.list_devices() + + assert errors and "usb enumeration failed" in errors[-1] + assert devices == [[]] + + +class TestOpenAndPreview: + def test_preview_opens_then_returns_thumbnails(self, fake_coolscanpy) -> None: + thumb = fake_coolscanpy.Thumbnail(slot=1, image=np.zeros((2, 2, 3)), boundary_rows=(0, 2), spacing_offset=0, needs_approval=False) + roll, device = _open(fake_coolscanpy, fake_coolscanpy.Roll(thumbnails=[thumb])) + worker = RollWorker() + opened, ready, progress = [], [], [] + worker.opened.connect(opened.append) + worker.preview_ready.connect(ready.append) + worker.progress.connect(lambda frac, msg: progress.append((frac, msg))) + + worker.run_preview(RollPreviewRequest(device_id="ls5000-usb-001")) + + assert opened == ["ls5000-usb-001"] + assert ready == [[thumb]] + assert device.roll_called_with == fake_coolscanpy.module.Material.COLOR_NEGATIVE + # the fake preview() reports 0.0 then 1.0, forwarded as (fraction, message) pairs + assert progress[0][0] == 0.0 + assert progress[-1] == (1.0, "preview complete") + + def test_preview_filters_to_requested_slots(self, fake_coolscanpy) -> None: + thumbs = [ + fake_coolscanpy.Thumbnail(slot=i, image=np.zeros((2, 2, 3)), boundary_rows=(0, 2), spacing_offset=0, needs_approval=False) + for i in (1, 2, 3) + ] + _open(fake_coolscanpy, fake_coolscanpy.Roll(thumbnails=thumbs)) + worker = RollWorker() + ready = [] + worker.preview_ready.connect(ready.append) + + worker.run_preview(RollPreviewRequest(device_id="ls5000-usb-001", slots=(2,))) + + assert [t.slot for t in ready[0]] == [2] + + def test_second_preview_of_the_same_device_does_not_reopen(self, fake_coolscanpy) -> None: + _open(fake_coolscanpy) + open_calls = [] + real_open = fake_coolscanpy.module.open + fake_coolscanpy.module.open = lambda devname: (open_calls.append(devname), real_open(devname))[1] + worker = RollWorker() + + worker.run_preview(RollPreviewRequest(device_id="ls5000-usb-001")) + worker.run_preview(RollPreviewRequest(device_id="ls5000-usb-001")) + + assert open_calls == ["ls5000-usb-001"] # opened once, reused the second time + + def test_switching_device_closes_the_previous_roll(self, fake_coolscanpy) -> None: + roll_a, device_a = _open(fake_coolscanpy) + worker = RollWorker() + worker.run_preview(RollPreviewRequest(device_id="device-a")) + assert roll_a.closed is False + + roll_b, device_b = _open(fake_coolscanpy) + worker.run_preview(RollPreviewRequest(device_id="device-b")) + + assert roll_a.closed is True + assert device_a.closed is True + assert roll_b.closed is False + + def test_preview_failure_emits_error(self, fake_coolscanpy) -> None: + _open(fake_coolscanpy, fake_coolscanpy.Roll(raise_on={"preview": fake_coolscanpy.module.PyCoolscanError("fingerprint mismatch")})) + worker = RollWorker() + errors = [] + worker.error.connect(errors.append) + + worker.run_preview(RollPreviewRequest(device_id="ls5000-usb-001")) + + assert errors and "fingerprint mismatch" in errors[-1] + + def test_close_roll_forgets_the_open_device(self, fake_coolscanpy) -> None: + roll, device = _open(fake_coolscanpy) + worker = RollWorker() + worker.run_preview(RollPreviewRequest(device_id="ls5000-usb-001")) + closed = [] + worker.closed.connect(lambda: closed.append(True)) + + worker.close_roll() + + assert closed == [True] + assert roll.closed is True + assert worker._open_device_id is None + + +class TestSpacingAndApproval: + def test_set_spacing_offset_echoes_the_applied_value(self, fake_coolscanpy) -> None: + roll, _device = _open(fake_coolscanpy) + worker = RollWorker() + worker.run_preview(RollPreviewRequest(device_id="ls5000-usb-001")) + seen = [] + worker.spacing_offset_set.connect(lambda slot, offset: seen.append((slot, offset))) + + worker.set_spacing_offset(3, -12) + + assert seen == [(3, -12)] + assert roll.spacing_offsets[3] == -12 + + def test_set_spacing_offset_before_open_emits_error(self, fake_coolscanpy) -> None: + worker = RollWorker() + errors = [] + worker.error.connect(errors.append) + + worker.set_spacing_offset(1, 5) + + assert errors and "no roll is open" in errors[-1] + + def test_approve_records_slot(self, fake_coolscanpy) -> None: + roll, _device = _open(fake_coolscanpy) + worker = RollWorker() + worker.run_preview(RollPreviewRequest(device_id="ls5000-usb-001")) + approved = [] + worker.approved.connect(approved.append) + + worker.approve(4) + + assert approved == [4] + assert roll.approved == [4] + + def test_approve_failure_emits_error(self, fake_coolscanpy) -> None: + _open(fake_coolscanpy, fake_coolscanpy.Roll(raise_on={"approve": fake_coolscanpy.module.PyCoolscanError("slot 3 needs review")})) + worker = RollWorker() + worker.run_preview(RollPreviewRequest(device_id="ls5000-usb-001")) + errors = [] + worker.error.connect(errors.append) + + worker.approve(3) + + assert errors and "slot 3 needs review" in errors[-1] + + +class TestBatchScan: + def test_writes_every_frame_and_reports_progress(self, fake_coolscanpy, tmp_path) -> None: + frames = [_frame(fake_coolscanpy, slot=s) for s in (1, 2, 3)] + _open(fake_coolscanpy, fake_coolscanpy.Roll(frames=frames)) + worker = RollWorker() + written, finished, progress = [], [], [] + worker.frame_written.connect(written.append) + worker.finished.connect(finished.append) + worker.progress.connect(lambda frac, msg: progress.append((frac, msg))) + + worker.run_batch_scan( + RollBatchScanRequest(device_id="ls5000-usb-001", slots=(1, 2, 3), output_folder=str(tmp_path), filename_pattern='{{ "%03d" % seq }}') + ) + + assert [w.slot for w in written] == [1, 2, 3] + assert all(os.path.exists(w.rgb_path) for w in written) + assert len(finished) == 1 and finished[0] == written + assert [msg for _frac, msg in progress] == ["slot 1 complete", "slot 2 complete", "slot 3 complete"] + + def test_batch_scan_opens_the_requested_device_first(self, fake_coolscanpy, tmp_path) -> None: + _open(fake_coolscanpy, fake_coolscanpy.Roll(frames=[_frame(fake_coolscanpy, slot=1)])) + worker = RollWorker() + opened = [] + worker.opened.connect(opened.append) + + worker.run_batch_scan( + RollBatchScanRequest(device_id="ls5000-usb-001", slots=(1,), output_folder=str(tmp_path), filename_pattern='{{ "%03d" % seq }}') + ) + + assert opened == ["ls5000-usb-001"] + + def test_safe_stop_mid_batch_is_reported_as_cancelled_not_error(self, fake_coolscanpy, tmp_path) -> None: + stop_error = fake_coolscanpy.module.SafeStopRequested("safe stop requested; 1 of 2 requested frames completed") + frames = [_frame(fake_coolscanpy, slot=1)] + roll = fake_coolscanpy.Roll(frames=frames, raise_on={"scan_many_slots": {2: stop_error}}) + _open(fake_coolscanpy, roll) + worker = RollWorker() + written, finished, cancelled, errors = [], [], [], [] + worker.frame_written.connect(written.append) + worker.finished.connect(finished.append) + worker.cancelled.connect(lambda: cancelled.append(True)) + worker.error.connect(errors.append) + + worker.run_batch_scan( + RollBatchScanRequest(device_id="ls5000-usb-001", slots=(1, 2), output_folder=str(tmp_path), filename_pattern='{{ "%03d" % seq }}') + ) + + assert len(written) == 1 # slot 1 finished and was written before the stop landed + assert cancelled == [True] + assert finished == [] # a stop is not a completion + assert errors == [] + + def test_genuine_failure_mid_batch_is_reported_as_error_not_cancelled(self, fake_coolscanpy, tmp_path) -> None: + boom = fake_coolscanpy.module.PyCoolscanError("transport jam") + frames = [_frame(fake_coolscanpy, slot=1)] + roll = fake_coolscanpy.Roll(frames=frames, raise_on={"scan_many_slots": {2: boom}}) + _open(fake_coolscanpy, roll) + worker = RollWorker() + written, cancelled, errors = [], [], [] + worker.frame_written.connect(written.append) + worker.cancelled.connect(lambda: cancelled.append(True)) + worker.error.connect(errors.append) + + worker.run_batch_scan( + RollBatchScanRequest(device_id="ls5000-usb-001", slots=(1, 2), output_folder=str(tmp_path), filename_pattern='{{ "%03d" % seq }}') + ) + + assert len(written) == 1 + assert cancelled == [] + assert errors and "transport jam" in errors[-1] + + def test_writes_ir_sidecar_when_frame_carries_one(self, fake_coolscanpy, tmp_path) -> None: + ir = np.random.randint(0, 65535, (4, 6), dtype=np.uint16) + _open(fake_coolscanpy, fake_coolscanpy.Roll(frames=[_frame(fake_coolscanpy, slot=1, ir=ir)])) + worker = RollWorker() + written = [] + worker.frame_written.connect(written.append) + + worker.run_batch_scan( + RollBatchScanRequest(device_id="ls5000-usb-001", slots=(1,), output_folder=str(tmp_path), filename_pattern='{{ "%03d" % seq }}') + ) + + assert written[0].ir_path is not None + assert written[0].ir_path.endswith("_IR.tif") + assert os.path.exists(written[0].ir_path) + + +class TestSafeStopAndShutdown: + def test_safe_stop_before_any_roll_is_open_is_a_no_op(self, fake_coolscanpy) -> None: + RollWorker().safe_stop() # must not raise + + def test_safe_stop_forwards_to_the_open_roll(self, fake_coolscanpy) -> None: + roll, _device = _open(fake_coolscanpy) + worker = RollWorker() + worker.run_preview(RollPreviewRequest(device_id="ls5000-usb-001")) + + worker.safe_stop() + + assert roll.safe_stop_called is True + + def test_shutdown_stops_and_closes(self, fake_coolscanpy) -> None: + roll, _device = _open(fake_coolscanpy) + worker = RollWorker() + worker.run_preview(RollPreviewRequest(device_id="ls5000-usb-001")) + + worker.shutdown() + + assert roll.safe_stop_called is True + assert roll.closed is True + + def test_shutdown_without_any_roll_open_does_not_raise(self) -> None: + RollWorker().shutdown() From d7dfbaf8db51452fb4d1042ffce922616f10f0e4 Mon Sep 17 00:00:00 2001 From: Rohan Pandula Date: Sat, 18 Jul 2026 23:29:29 -0700 Subject: [PATCH 07/48] Add CoolscanRollSidebar: device select, contact sheet, batch scan Structured like ScanlightSidebar: a pinned primary action, a gate hint that names what's still missing, sections for device and output settings, one status line plus progress bar, and settings persisted the same way (RollScanSettings under a global-setting key, loaded/saved like ScanlightSettings). _apply_gating() mirrors _missing_requirements() / _apply_gating() there, split into a narrower preview gate and a fuller scan gate. The contact sheet, per-slot spacing/approval controls and the safe-stop button have no camera-route analogue -- coolscanpy's preview/approve/ batch-scan workflow doesn't exist on that side. Built the simplest way that fits the surrounding code: a plain QListWidget in icon mode (not the file browser's heavier ThumbnailGridView, which assumes NegPy's own asset model) with a small local numpy-to-QPixmap conversion, since a scanner preview frame isn't a color-managed pipeline buffer. Safe Stop is its own control, not a Scan/Stop button toggle: unlike a camera exposure, a fine-scan in flight can't be aborted mid-pull, so the button explicitly says what it does -- finish the current frame, then stop -- rather than implying an instant cancel. --- negpy/desktop/view/sidebar/coolscan_roll.py | 521 ++++++++++++++++++++ tests/roll/test_sidebar.py | 315 ++++++++++++ 2 files changed, 836 insertions(+) create mode 100644 negpy/desktop/view/sidebar/coolscan_roll.py create mode 100644 tests/roll/test_sidebar.py diff --git a/negpy/desktop/view/sidebar/coolscan_roll.py b/negpy/desktop/view/sidebar/coolscan_roll.py new file mode 100644 index 00000000..733a20b9 --- /dev/null +++ b/negpy/desktop/view/sidebar/coolscan_roll.py @@ -0,0 +1,521 @@ +"""Coolscan roll-scanning sidebar. + +Device selection, a whole-roll preview rendered as a thumbnail contact +sheet, per-slot spacing-offset nudge and approval, and one-button batch +fine-scan with progress and a safe-stop control. Mirrors the structure of +`ScanlightSidebar` (gating via `_apply_gating()`, settings persisted the +same way, one status line + progress bar) as closely as the workflow +allows -- preview/approve/batch-scan has no camera-route analogue, so the +contact sheet and per-slot controls below are new, built the simplest way +that fits the surrounding code. +""" + +from __future__ import annotations + +import dataclasses + +import numpy as np +import qtawesome as qta +from PyQt6.QtCore import QSize, Qt, pyqtSlot +from PyQt6.QtGui import QColor, QIcon, QImage, QPixmap +from PyQt6.QtWidgets import ( + QAbstractItemView, + QComboBox, + QFileDialog, + QFormLayout, + QHBoxLayout, + QLabel, + QLineEdit, + QListWidget, + QListWidgetItem, + QProgressBar, + QPushButton, + QSpinBox, + QVBoxLayout, + QWidget, +) + +from negpy.desktop.view.styles.templates import section_subheader +from negpy.desktop.view.styles.theme import THEME +from negpy.infrastructure.roll import coolscanpy_roll +from negpy.infrastructure.roll.settings import RollScanSettings + +_SLOT_ROLE = Qt.ItemDataRole.UserRole +_WARN_COLOR = "#C8922E" # matches ScanlightSidebar's advisory tone +_THUMBNAIL_SIZE = QSize(96, 96) + + +def _thumbnail_pixmap(image) -> QPixmap: + """A `coolscanpy.Thumbnail.image` array as a displayable QPixmap. + + Deliberately dumb: this is a raw scanner preview frame for framing and + review, not a NegPy pipeline buffer, so it does not go through + `ImageConverter.to_qimage`'s color management -- that helper assumes a + working-space buffer this array isn't. Normalizes whatever numeric + range/dtype/channel-count arrives to 8-bit RGB. + """ + arr = np.asarray(image) + if arr.ndim == 2: + arr = np.stack([arr] * 3, axis=-1) + if arr.dtype != np.uint8: + peak = float(arr.max()) if arr.size else 1.0 + scale = 255.0 if peak <= 1.0 else (255.0 / peak if peak > 255.0 else 1.0) + arr = np.clip(arr.astype(np.float64) * scale, 0, 255).astype(np.uint8) + arr = np.ascontiguousarray(arr[..., :3]) + h, w = arr.shape[:2] + qimg = QImage(arr.data, w, h, w * 3, QImage.Format.Format_RGB888).copy() # copy: detach from arr's buffer + return QPixmap.fromImage(qimg) + + +class CoolscanRollSidebar(QWidget): + """Whole-roll preview, spacing/approval and batch fine-scan panel.""" + + def __init__(self, controller) -> None: + super().__init__() + self.controller = controller + self._settings: RollScanSettings = self._load_settings() + self._devices: list = [] + self._devices_loaded = False + self._thumbnails: dict[int, object] = {} + self._scanning = False + self._stopping = False # safe-stop acknowledged, waiting for the in-flight frame + + self._init_ui() + self._connect_signals() + + # ── settings persistence ────────────────────────────────────────── + + def _load_settings(self) -> RollScanSettings: + data = self.controller.session.repo.get_global_setting("roll_scan_settings", default={}) + if isinstance(data, dict) and data: + try: + known = {f.name for f in dataclasses.fields(RollScanSettings)} + return RollScanSettings(**{k: v for k, v in data.items() if k in known}) + except Exception: + pass + return RollScanSettings.defaults() + + def _save_settings(self) -> None: + self.controller.session.repo.save_global_setting("roll_scan_settings", dataclasses.asdict(self._settings)) + + def _update_settings_from_ui(self) -> None: + updated = dataclasses.replace( + self._settings, + last_device_id=self._current_device_id() or self._settings.last_device_id, + output_folder=self.folder_edit.text().strip(), + filename_pattern=self.pattern_edit.text().strip() or RollScanSettings.defaults().filename_pattern, + ) + if updated == self._settings: + return + self._settings = updated + self._save_settings() + self._apply_gating() + + # ── UI construction ─────────────────────────────────────────────── + + def _init_ui(self) -> None: + layout = QVBoxLayout(self) + layout.setContentsMargins(5, 0, 5, 5) + layout.setSpacing(10) + + self.preview_btn = QPushButton(qta.icon("fa5s.images", color=THEME.text_primary), " Preview Roll") + self.preview_btn.setObjectName("scan_btn") + self.preview_btn.setFixedHeight(40) + layout.addWidget(self.preview_btn) + + self.gate_hint = QLabel("") + self.gate_hint.setStyleSheet(f"color: {_WARN_COLOR}; font-size: {THEME.font_size_small}px;") + self.gate_hint.setWordWrap(True) + layout.addWidget(self.gate_hint) + + # ── DEVICE ────────────────────────────────────────── + layout.addWidget(section_subheader("DEVICE")) + self._setup_hint = QLabel( + "Roll scanning needs coolscanpy, an optional dependency: `pip install coolscanpy`. See docs/COOLSCANPY_ROLL_SCANNING.md." + ) + self._setup_hint.setWordWrap(True) + self._setup_hint.setStyleSheet(f"color: {_WARN_COLOR}; font-size: {THEME.font_size_small}px;") + self._setup_hint.setVisible(not coolscanpy_roll.available()) + layout.addWidget(self._setup_hint) + + device_row = QHBoxLayout() + self.device_combo = QComboBox() + self.device_combo.setToolTip("Select a Coolscan device") + self.device_combo.addItem("Detecting devices…", None) + self.refresh_btn = QPushButton() + self.refresh_btn.setIcon(qta.icon("fa5s.redo", color=THEME.text_secondary)) + self.refresh_btn.setToolTip("Refresh device list") + self.refresh_btn.setFixedWidth(32) + device_row.addWidget(self.device_combo, 1) + device_row.addWidget(self.refresh_btn) + layout.addLayout(device_row) + + # ── OUTPUT ────────────────────────────────────────── + layout.addWidget(section_subheader("OUTPUT")) + out_form = QFormLayout() + out_form.setSpacing(6) + folder_row = QHBoxLayout() + self.folder_edit = QLineEdit(self._settings.output_folder) + self.folder_edit.setPlaceholderText("Output folder…") + self.folder_browse = QPushButton("…") + self.folder_browse.setFixedWidth(32) + folder_row.addWidget(self.folder_edit) + folder_row.addWidget(self.folder_browse) + out_form.addRow("Folder", folder_row) + self.pattern_edit = QLineEdit(self._settings.filename_pattern) + self.pattern_edit.setToolTip('Jinja2 template. Variables: {{ date }}, {{ seq }} (the slot number).\nExample: {{ date }}_{{ "%03d" % seq }}') + out_form.addRow("Filename", self.pattern_edit) + layout.addLayout(out_form) + + # ── PROGRESS / STATUS ──────────────────────────────── + self.progress_bar = QProgressBar() + self.progress_bar.setVisible(False) + self.progress_bar.setRange(0, 100) + layout.addWidget(self.progress_bar) + self.status_label = QLabel("") + self.status_label.setStyleSheet(f"color: {THEME.text_muted}; font-size: {THEME.font_size_small}px;") + self.status_label.setWordWrap(True) + self.status_label.setVisible(False) + layout.addWidget(self.status_label) + + # ── CONTACT SHEET ───────────────────────────────────── + layout.addWidget(section_subheader("CONTACT SHEET")) + self.contact_sheet = QListWidget() + self.contact_sheet.setViewMode(QListWidget.ViewMode.IconMode) + self.contact_sheet.setResizeMode(QListWidget.ResizeMode.Adjust) + self.contact_sheet.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection) + self.contact_sheet.setIconSize(_THUMBNAIL_SIZE) + self.contact_sheet.setMinimumHeight(160) + self.contact_sheet.setToolTip("Click a slot to nudge its spacing or approve it; select several, then Scan Selected.") + layout.addWidget(self.contact_sheet) + + slot_row = QFormLayout() + slot_row.setSpacing(6) + self.slot_label = QLabel("—") + slot_row.addRow("Slot", self.slot_label) + offset_row = QHBoxLayout() + self.offset_spin = QSpinBox() + self.offset_spin.setRange(-500, 500) + self.offset_spin.setToolTip("Nudge this slot's transport boundary, in native rows at preview resolution") + self.offset_apply_btn = QPushButton("Apply") + offset_row.addWidget(self.offset_spin, 1) + offset_row.addWidget(self.offset_apply_btn) + slot_row.addRow("Spacing offset", offset_row) + layout.addLayout(slot_row) + + self.approve_btn = QPushButton(qta.icon("fa5s.check", color=THEME.text_primary), " Approve Slot") + self.approve_btn.setToolTip("This slot's transport origin was not confidently automatic; approve it before it can be scanned.") + layout.addWidget(self.approve_btn) + + # ── SCAN ──────────────────────────────────────────── + self.scan_btn = QPushButton(qta.icon("fa5s.camera-retro", color=THEME.text_primary), " Scan Selected") + self.scan_btn.setObjectName("scan_btn") + self.scan_btn.setFixedHeight(40) + layout.addWidget(self.scan_btn) + + self.safe_stop_btn = QPushButton(qta.icon("fa5s.stop", color=THEME.text_secondary), " Safe Stop") + self.safe_stop_btn.setToolTip("Finish the frame in flight, then stop before the next one -- the transport can't be aborted mid-pull.") + self.safe_stop_btn.setEnabled(False) + layout.addWidget(self.safe_stop_btn) + + self._show_slot_detail(None) + self._apply_gating() + layout.addStretch() + + def _connect_signals(self) -> None: + self.refresh_btn.clicked.connect(self._on_refresh) + self.device_combo.currentIndexChanged.connect(self._on_device_changed) + self.folder_browse.clicked.connect(self._on_browse_folder) + for w in (self.folder_edit, self.pattern_edit): + w.editingFinished.connect(self._update_settings_from_ui) + self.preview_btn.clicked.connect(self._on_preview_clicked) + self.contact_sheet.itemSelectionChanged.connect(self._on_selection_changed) + self.offset_apply_btn.clicked.connect(self._on_apply_offset) + self.approve_btn.clicked.connect(self._on_approve) + self.scan_btn.clicked.connect(self._on_scan_clicked) + self.safe_stop_btn.clicked.connect(self._on_safe_stop_clicked) + + self.controller.roll_devices_ready.connect(self._on_devices_ready) + self.controller.roll_opened.connect(self._on_opened) + self.controller.roll_preview_ready.connect(self._on_preview_ready) + self.controller.roll_spacing_offset_set.connect(self._on_spacing_offset_set) + self.controller.roll_approved.connect(self._on_approved) + self.controller.roll_progress.connect(self._on_progress) + self.controller.roll_frame_written.connect(self._on_frame_written) + self.controller.roll_finished.connect(self._on_finished) + self.controller.roll_cancelled.connect(self._on_cancelled) + self.controller.roll_error.connect(self._on_error) + self.controller.roll_status.connect(self._on_status) + + # ── activation hook ─────────────────────────────────────────────── + + def on_activated(self) -> None: + """Called when the Scan tab is switched to.""" + self._setup_hint.setVisible(not coolscanpy_roll.available()) + self._apply_gating() + if not self._devices_loaded: + self._request_devices() + + # ── devices ─────────────────────────────────────────────────────── + + def _request_devices(self) -> None: + if not coolscanpy_roll.available(): + return + self.device_combo.clear() + self.device_combo.addItem("Detecting devices…", None) + self.device_combo.setEnabled(False) + self.controller.request_roll_devices() + + def _on_refresh(self) -> None: + self._devices_loaded = False + self._request_devices() + + @pyqtSlot(list) + def _on_devices_ready(self, devices: list) -> None: + self._devices = devices + self._devices_loaded = True + self.device_combo.clear() + self.device_combo.setEnabled(True) + + if not devices: + self.device_combo.addItem("No Coolscan devices detected", None) + self.device_combo.setEnabled(False) + self._apply_gating() + return + + for d in devices: + label = f"{d.vendor} {d.model}" if getattr(d, "vendor", "") else d.model + self.device_combo.addItem(label, d.id) + + if self._settings.last_device_id: + idx = self.device_combo.findData(self._settings.last_device_id) + if idx >= 0: + self.device_combo.setCurrentIndex(idx) + + self._apply_gating() + + def _on_device_changed(self, _index: int) -> None: + self._clear_contact_sheet() + self._update_settings_from_ui() + self._apply_gating() + + def _current_device_id(self) -> str | None: + return self.device_combo.currentData() + + @pyqtSlot(str) + def _on_opened(self, device_id: str) -> None: + self._set_status(f"Roll open on {device_id}.") + + # ── preview / contact sheet ───────────────────────────────────── + + def _clear_contact_sheet(self) -> None: + self.contact_sheet.clear() + self._thumbnails = {} + self._show_slot_detail(None) + + def _on_preview_clicked(self) -> None: + device_id = self._current_device_id() + if not device_id: + return + from negpy.desktop.workers.roll_worker import RollPreviewRequest + + self._clear_contact_sheet() + self._set_status("Reading roll transport…") + self.progress_bar.setVisible(True) + self.progress_bar.setValue(0) + self.controller.start_roll_preview(RollPreviewRequest(device_id=device_id)) + + @pyqtSlot(list) + def _on_preview_ready(self, thumbnails: list) -> None: + self.progress_bar.setVisible(False) + self._thumbnails = {t.slot: t for t in thumbnails} + self.contact_sheet.clear() + for t in sorted(self._thumbnails): + self._add_slot_item(self._thumbnails[t]) + self._show_slot_detail(None) + self._apply_gating() + + def _add_slot_item(self, thumb) -> None: + pixmap = _thumbnail_pixmap(thumb.image).scaled( + _THUMBNAIL_SIZE, Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation + ) + item = QListWidgetItem(QIcon(pixmap), f"Slot {thumb.slot}" + (" ⚠" if thumb.needs_approval else "")) + item.setData(_SLOT_ROLE, thumb.slot) + if thumb.needs_approval: + item.setForeground(QColor(_WARN_COLOR)) + self.contact_sheet.addItem(item) + + def _on_selection_changed(self) -> None: + current = self.contact_sheet.currentItem() + slot = current.data(_SLOT_ROLE) if current is not None else None + self._show_slot_detail(self._thumbnails.get(slot) if slot is not None else None) + self._apply_gating() + + def _show_slot_detail(self, thumb) -> None: + if thumb is None: + self.slot_label.setText("—") + self.offset_spin.setEnabled(False) + self.offset_apply_btn.setEnabled(False) + self.approve_btn.setVisible(False) + return + self.slot_label.setText(str(thumb.slot)) + self.offset_spin.setEnabled(True) + self.offset_apply_btn.setEnabled(True) + self.offset_spin.blockSignals(True) + self.offset_spin.setValue(int(thumb.spacing_offset)) + self.offset_spin.blockSignals(False) + self.approve_btn.setVisible(bool(thumb.needs_approval)) + + def _selected_slots(self) -> list[int]: + return sorted({item.data(_SLOT_ROLE) for item in self.contact_sheet.selectedItems()}) + + # ── spacing offset / approval ──────────────────────────────────── + + def _on_apply_offset(self) -> None: + current = self.contact_sheet.currentItem() + if current is None: + return + slot = current.data(_SLOT_ROLE) + self.controller.set_roll_spacing_offset(slot, self.offset_spin.value()) + + @pyqtSlot(int, int) + def _on_spacing_offset_set(self, slot: int, offset_rows: int) -> None: + thumb = self._thumbnails.get(slot) + if thumb is not None: + self._thumbnails[slot] = dataclasses.replace(thumb, spacing_offset=offset_rows) + self._set_status(f"Slot {slot}: spacing offset set to {offset_rows}.") + + def _on_approve(self) -> None: + current = self.contact_sheet.currentItem() + if current is None: + return + self.controller.approve_roll_slot(current.data(_SLOT_ROLE)) + + @pyqtSlot(int) + def _on_approved(self, slot: int) -> None: + thumb = self._thumbnails.get(slot) + if thumb is not None: + self._thumbnails[slot] = dataclasses.replace(thumb, needs_approval=False) + for i in range(self.contact_sheet.count()): + item = self.contact_sheet.item(i) + if item.data(_SLOT_ROLE) == slot: + item.setText(f"Slot {slot}") + item.setData(Qt.ItemDataRole.ForegroundRole, None) # back to the theme's default text color + break + if self.contact_sheet.currentItem() is not None and self.contact_sheet.currentItem().data(_SLOT_ROLE) == slot: + self.approve_btn.setVisible(False) + self._set_status(f"Slot {slot} approved.") + self._apply_gating() + + # ── scan ────────────────────────────────────────────────────────── + + def _on_scan_clicked(self) -> None: + device_id = self._current_device_id() + slots = self._selected_slots() + output_folder = self.folder_edit.text().strip() + if not device_id or not slots or not output_folder: + return + from negpy.desktop.workers.roll_worker import RollBatchScanRequest + + self._update_settings_from_ui() + self._save_settings() + req = RollBatchScanRequest( + device_id=device_id, + slots=tuple(slots), + output_folder=output_folder, + filename_pattern=self.pattern_edit.text().strip() or RollScanSettings.defaults().filename_pattern, + ) + self.set_scanning(True) + self.controller.start_roll_scan(req) + + def _on_safe_stop_clicked(self) -> None: + self._stopping = True + self.safe_stop_btn.setEnabled(False) + self._set_status("Stopping after the current frame…") + self.controller.roll_safe_stop() + + @pyqtSlot(float, str) + def _on_progress(self, fraction: float, message: str) -> None: + self.progress_bar.setVisible(True) + self.progress_bar.setValue(int(fraction * 100)) + if not self._stopping: + self._set_status(message) + + @pyqtSlot(object) + def _on_frame_written(self, output) -> None: + if not self._stopping: + self._set_status(f"Wrote slot {output.slot}.") + + @pyqtSlot(list) + def _on_finished(self, outputs: list) -> None: + self.set_scanning(False) + self._set_status(f"Scanned {len(outputs)} frame(s).") + + @pyqtSlot() + def _on_cancelled(self) -> None: + self.set_scanning(False) + self._set_status("Stopped.") + + @pyqtSlot(str) + def _on_error(self, msg: str) -> None: + self.set_scanning(False) + self.progress_bar.setVisible(False) + self._set_status(f"Error: {msg}") + + @pyqtSlot(str) + def _on_status(self, msg: str) -> None: + if not self._stopping: + self._set_status(msg) + + def _set_status(self, text: str) -> None: + self.status_label.setText(text) + self.status_label.setVisible(bool(text)) + + # ── browse ──────────────────────────────────────────────────────── + + def _on_browse_folder(self) -> None: + folder = QFileDialog.getExistingDirectory(self, "Select Output Folder") + if folder: + self.folder_edit.setText(folder) + self._update_settings_from_ui() + + # ── gating ──────────────────────────────────────────────────────── + + def _missing_for_preview(self) -> list[str]: + m = [] + if not coolscanpy_roll.available(): + m.append("install coolscanpy") + if not self._current_device_id(): + m.append("select a device") + return m + + def _missing_for_scan(self) -> list[str]: + m = list(self._missing_for_preview()) + if not self._selected_slots(): + m.append("select at least one slot") + if not self.folder_edit.text().strip(): + m.append("choose an output folder") + return m + + def _apply_gating(self) -> None: + missing_preview = self._missing_for_preview() + missing_scan = self._missing_for_scan() + self.preview_btn.setEnabled(not missing_preview and not self._scanning) + self.scan_btn.setEnabled(not missing_scan and not self._scanning) + self.safe_stop_btn.setEnabled(self._scanning) + if missing_scan: + self.gate_hint.setText("To scan: " + ", ".join(missing_scan) + ".") + self.gate_hint.setVisible(True) + else: + self.gate_hint.setText("") + self.gate_hint.setVisible(False) + + # ── state helpers ───────────────────────────────────────────────── + + def set_scanning(self, active: bool) -> None: + self._scanning = active + if active: + self._stopping = False + self.progress_bar.setVisible(True) + self.progress_bar.setValue(0) + self._apply_gating() diff --git a/tests/roll/test_sidebar.py b/tests/roll/test_sidebar.py new file mode 100644 index 00000000..45772153 --- /dev/null +++ b/tests/roll/test_sidebar.py @@ -0,0 +1,315 @@ +"""Smoke and behavior tests for CoolscanRollSidebar. Mirrors +test_scanlight_sidebar.py: build the widget against a mock controller, then +drive it either through real widgets (clicks, combo selection) or by +calling its private `_on_*` handlers directly to simulate what the (mocked, +never-really-running) controller/worker would deliver. +""" + +from __future__ import annotations + +import sys + +import numpy as np +from PyQt6.QtWidgets import QApplication +from unittest.mock import MagicMock + +from negpy.desktop.view.sidebar.coolscan_roll import CoolscanRollSidebar + +if not QApplication.instance(): + _app = QApplication(sys.argv) + + +def _sidebar(): + ctrl = MagicMock() + ctrl.session.repo.get_global_setting.return_value = {} + return CoolscanRollSidebar(ctrl) + + +def _thumb(fake_coolscanpy, slot, *, needs_approval=False, spacing_offset=0): + return fake_coolscanpy.Thumbnail( + slot=slot, + image=np.zeros((4, 4, 3), dtype=np.uint8), + boundary_rows=(0, 4), + spacing_offset=spacing_offset, + needs_approval=needs_approval, + ) + + +def _pick_device(w, device_id="ls5000-usb-001", label="Nikon LS-5000"): + w.device_combo.clear() + w.device_combo.addItem(label, device_id) + w.device_combo.setCurrentIndex(0) + + +class TestBuildsAndGating: + def test_sidebar_builds_with_all_controls(self) -> None: + w = _sidebar() + for attr in ( + "preview_btn", + "scan_btn", + "safe_stop_btn", + "device_combo", + "refresh_btn", + "folder_edit", + "pattern_edit", + "contact_sheet", + "offset_spin", + "offset_apply_btn", + "approve_btn", + "gate_hint", + "status_label", + ): + assert hasattr(w, attr), attr + + def test_missing_coolscanpy_disables_preview_and_shows_hint(self) -> None: + w = _sidebar() # no fake_coolscanpy fixture -- coolscanpy genuinely absent here + assert w.preview_btn.isEnabled() is False + assert "install coolscanpy" in w.gate_hint.text() + assert not w._setup_hint.isHidden() + + def test_device_selected_still_blocks_scan_until_folder_and_slots(self, fake_coolscanpy) -> None: + w = _sidebar() + _pick_device(w) + assert w.preview_btn.isEnabled() is True # coolscanpy available (faked) + device picked + assert w.scan_btn.isEnabled() is False + missing = w._missing_for_scan() + assert "select at least one slot" in missing + assert "choose an output folder" in missing + + def test_scan_enabled_once_folder_and_slot_selected(self, fake_coolscanpy, tmp_path) -> None: + w = _sidebar() + _pick_device(w) + w.folder_edit.setText(str(tmp_path)) + w._on_preview_ready([_thumb(fake_coolscanpy, 1)]) + w.contact_sheet.item(0).setSelected(True) + + assert w._missing_for_scan() == [] + assert w.scan_btn.isEnabled() is True + + def test_approve_button_hidden_without_a_selection(self) -> None: + w = _sidebar() + assert w.approve_btn.isVisible() is False + + +class TestPreview: + def test_preview_click_starts_request_for_selected_device(self, fake_coolscanpy) -> None: + w = _sidebar() + _pick_device(w, device_id="dev-42") + + w._on_preview_clicked() + + from negpy.desktop.workers.roll_worker import RollPreviewRequest + + req = w.controller.start_roll_preview.call_args[0][0] + assert isinstance(req, RollPreviewRequest) + assert req.device_id == "dev-42" + + def test_preview_click_without_a_device_is_a_noop(self) -> None: + w = _sidebar() + w._on_preview_clicked() + w.controller.start_roll_preview.assert_not_called() + + def test_preview_ready_populates_the_contact_sheet(self, fake_coolscanpy) -> None: + w = _sidebar() + thumbs = [_thumb(fake_coolscanpy, s) for s in (2, 1, 3)] + + w._on_preview_ready(thumbs) + + assert w.contact_sheet.count() == 3 + assert sorted(w._thumbnails.keys()) == [1, 2, 3] + # the widget lists slots in ascending order regardless of preview()'s return order + from negpy.desktop.view.sidebar.coolscan_roll import _SLOT_ROLE + + listed = [w.contact_sheet.item(i).data(_SLOT_ROLE) for i in range(w.contact_sheet.count())] + assert listed == [1, 2, 3] + + def test_needs_approval_slot_is_marked_and_shows_approve_button(self, fake_coolscanpy) -> None: + w = _sidebar() + w._on_preview_ready([_thumb(fake_coolscanpy, 5, needs_approval=True)]) + + item = w.contact_sheet.item(0) + assert "⚠" in item.text() + + w.contact_sheet.setCurrentItem(item) + assert not w.approve_btn.isHidden() + + def test_selecting_a_slot_seeds_the_offset_spinner(self, fake_coolscanpy) -> None: + w = _sidebar() + w._on_preview_ready([_thumb(fake_coolscanpy, 7, spacing_offset=-15)]) + + w.contact_sheet.setCurrentItem(w.contact_sheet.item(0)) + + assert w.slot_label.text() == "7" + assert w.offset_spin.value() == -15 + + def test_new_preview_clears_the_old_contact_sheet(self, fake_coolscanpy) -> None: + w = _sidebar() + w._on_preview_ready([_thumb(fake_coolscanpy, 1)]) + assert w.contact_sheet.count() == 1 + + w._clear_contact_sheet() # what _on_preview_clicked does before requesting a fresh preview + w._on_preview_ready([_thumb(fake_coolscanpy, 9)]) + + assert w.contact_sheet.count() == 1 + assert list(w._thumbnails.keys()) == [9] + + +class TestSpacingAndApproval: + def test_apply_offset_forwards_the_spinner_value(self, fake_coolscanpy) -> None: + w = _sidebar() + w._on_preview_ready([_thumb(fake_coolscanpy, 4)]) + w.contact_sheet.setCurrentItem(w.contact_sheet.item(0)) + w.offset_spin.setValue(-30) + + w._on_apply_offset() + + w.controller.set_roll_spacing_offset.assert_called_once_with(4, -30) + + def test_spacing_offset_set_updates_local_thumbnail_state(self, fake_coolscanpy) -> None: + w = _sidebar() + w._on_preview_ready([_thumb(fake_coolscanpy, 4, spacing_offset=0)]) + + w._on_spacing_offset_set(4, -8) + + assert w._thumbnails[4].spacing_offset == -8 + + def test_approve_click_forwards_the_selected_slot(self, fake_coolscanpy) -> None: + w = _sidebar() + w._on_preview_ready([_thumb(fake_coolscanpy, 6, needs_approval=True)]) + w.contact_sheet.setCurrentItem(w.contact_sheet.item(0)) + + w._on_approve() + + w.controller.approve_roll_slot.assert_called_once_with(6) + + def test_approved_clears_the_warning_marker(self, fake_coolscanpy) -> None: + w = _sidebar() + w._on_preview_ready([_thumb(fake_coolscanpy, 6, needs_approval=True)]) + w.contact_sheet.setCurrentItem(w.contact_sheet.item(0)) + assert not w.approve_btn.isHidden() + + w._on_approved(6) + + assert w._thumbnails[6].needs_approval is False + assert "⚠" not in w.contact_sheet.item(0).text() + assert w.approve_btn.isHidden() + + +class TestScanning: + def test_scan_click_builds_request_from_selection_and_settings(self, fake_coolscanpy, tmp_path) -> None: + w = _sidebar() + _pick_device(w, device_id="dev-1") + w.folder_edit.setText(str(tmp_path)) + w.pattern_edit.setText('{{ "%03d" % seq }}') + w._on_preview_ready([_thumb(fake_coolscanpy, 1), _thumb(fake_coolscanpy, 2)]) + w.contact_sheet.item(0).setSelected(True) + w.contact_sheet.item(1).setSelected(True) + + w._on_scan_clicked() + + from negpy.desktop.workers.roll_worker import RollBatchScanRequest + + req = w.controller.start_roll_scan.call_args[0][0] + assert isinstance(req, RollBatchScanRequest) + assert req.device_id == "dev-1" + assert req.slots == (1, 2) + assert req.output_folder == str(tmp_path) + assert w._scanning is True + + def test_scan_click_without_a_selected_slot_is_a_noop(self, fake_coolscanpy, tmp_path) -> None: + w = _sidebar() + _pick_device(w) + w.folder_edit.setText(str(tmp_path)) + w._on_scan_clicked() + w.controller.start_roll_scan.assert_not_called() + + def test_finished_clears_scanning_state(self, fake_coolscanpy) -> None: + w = _sidebar() + w.set_scanning(True) + w._on_finished([]) + assert w._scanning is False + assert "Scanned 0" in w.status_label.text() + + def test_safe_stop_disables_itself_and_forwards_to_controller(self, fake_coolscanpy) -> None: + w = _sidebar() + w.set_scanning(True) + + w._on_safe_stop_clicked() + + w.controller.roll_safe_stop.assert_called_once() + assert w.safe_stop_btn.isEnabled() is False + assert "Stopping" in w.status_label.text() + + def test_safe_stop_suppresses_further_progress_chatter(self, fake_coolscanpy) -> None: + w = _sidebar() + w.set_scanning(True) + w._on_safe_stop_clicked() + + w._on_progress(0.5, "slot 3 complete") + + assert "Stopping" in w.status_label.text() # not overwritten by the progress message + + def test_cancelled_clears_scanning_state(self, fake_coolscanpy) -> None: + w = _sidebar() + w.set_scanning(True) + w._on_cancelled() + assert w._scanning is False + assert w.safe_stop_btn.isEnabled() is False + + def test_error_clears_scanning_state_and_shows_message(self, fake_coolscanpy) -> None: + w = _sidebar() + w.set_scanning(True) + w._on_error("transport jam") + assert w._scanning is False + assert "transport jam" in w.status_label.text() + + def test_safe_stop_button_only_enabled_while_scanning(self, fake_coolscanpy) -> None: + w = _sidebar() + assert w.safe_stop_btn.isEnabled() is False + w.set_scanning(True) + assert w.safe_stop_btn.isEnabled() is True + w.set_scanning(False) + assert w.safe_stop_btn.isEnabled() is False + + +class TestSettingsPersistence: + def test_settings_round_trip_through_the_repo(self, fake_coolscanpy, tmp_path) -> None: + w = _sidebar() + _pick_device(w, device_id="dev-7") + w.folder_edit.setText(str(tmp_path)) + w.pattern_edit.setText('{{ "%03d" % seq }}') + + w._update_settings_from_ui() + + saved = w.controller.session.repo.save_global_setting.call_args[0] + assert saved[0] == "roll_scan_settings" + assert saved[1]["last_device_id"] == "dev-7" + assert saved[1]["output_folder"] == str(tmp_path) + + def test_load_settings_restores_last_device_on_device_list(self, fake_coolscanpy) -> None: + ctrl = MagicMock() + ctrl.session.repo.get_global_setting.return_value = {"last_device_id": "dev-9", "output_folder": "", "filename_pattern": ""} + w = CoolscanRollSidebar(ctrl) + + class _Dev: + id = "dev-9" + vendor = "Nikon" + model = "LS-5000" + + w._on_devices_ready([_Dev()]) + + assert w.device_combo.currentData() == "dev-9" + + +class TestActivation: + def test_on_activated_requests_devices_once_devices_have_arrived(self, fake_coolscanpy) -> None: + w = _sidebar() + w.on_activated() + assert w.controller.request_roll_devices.call_count == 1 + w._on_devices_ready([]) # simulate the controller's reply landing + w.on_activated() + assert w.controller.request_roll_devices.call_count == 1 # already loaded, no repeat + + def test_on_activated_refreshes_the_setup_hint(self, fake_coolscanpy) -> None: + w = _sidebar() + assert w._setup_hint.isVisible() is False # built while coolscanpy (faked) was available From 5fbb9d939902d68b54661dfb076ee072b26dc99f Mon Sep 17 00:00:00 2001 From: Rohan Pandula Date: Sat, 18 Jul 2026 23:36:46 -0700 Subject: [PATCH 08/48] Wire RollWorker and CoolscanRollSidebar into the app AppController gets a roll_thread + RollWorker pair, lazily started like the capture thread (_ensure_roll_thread mirrors _ensure_capture_thread), request/result signal pairs for every RollWorker operation, and public methods the sidebar calls (request_roll_devices, start_roll_preview, set_roll_spacing_offset, approve_roll_slot, start_roll_scan, roll_safe_stop). roll_safe_stop() calls the worker directly, same as cancel_capture()/cancel_scan() -- no thread needs to be running for a thread-safe Event.set() to be worth queuing. _on_roll_scan_finished feeds written frames into NegPy's file list via request_asset_discovery(), the same seam the plain Scan and Camera Scanning routes already use for their own output. Unlike _on_capture_finished it does no rgbscan_mode/process_mode bookkeeping: each roll frame is already one complete per-slot RGB TIFF, not an R/G/B triplet needing a merge flag. RightPanel's Scan tab gains a third collapsible section, "Roll Scanning", alongside the existing "Scanner (SANE)" and "Camera Scanning" sections, and on_activated() now reaches all three sidebars on tab switch. --- negpy/desktop/controller.py | 90 +++++++++++++++++++++++ negpy/desktop/view/sidebar/right_panel.py | 21 ++++-- 2 files changed, 106 insertions(+), 5 deletions(-) diff --git a/negpy/desktop/controller.py b/negpy/desktop/controller.py index 96c117c8..96881116 100644 --- a/negpy/desktop/controller.py +++ b/negpy/desktop/controller.py @@ -35,6 +35,11 @@ CaptureWorker, LiveViewRequest, ) +from negpy.desktop.workers.roll_worker import ( + RollBatchScanRequest, + RollPreviewRequest, + RollWorker, +) from negpy.domain.models import ( ExportFormat, ExportPreset, @@ -224,6 +229,22 @@ class AppController(QObject): connection_polled = pyqtSignal(dict) # {usb_ok, usb_model, light_ok, light_detail} poll_light_temp_requested = pyqtSignal(str) # light port (temp-only poll, runs even mid-live-view) light_temp_polled = pyqtSignal(object) # Scanlight LED temperature °C, or None + roll_devices_requested = pyqtSignal() + roll_devices_ready = pyqtSignal(list) # list[coolscanpy.DeviceInfo] + roll_opened = pyqtSignal(str) # device_id now open + roll_preview_requested = pyqtSignal(RollPreviewRequest) + roll_preview_ready = pyqtSignal(list) # list[coolscanpy.Thumbnail] + roll_spacing_offset_requested = pyqtSignal(int, int) + roll_spacing_offset_set = pyqtSignal(int, int) + roll_approve_requested = pyqtSignal(int) + roll_approved = pyqtSignal(int) + roll_scan_requested = pyqtSignal(RollBatchScanRequest) + roll_progress = pyqtSignal(float, str) + roll_frame_written = pyqtSignal(object) # RollFrameOutput + roll_finished = pyqtSignal(list) # list[RollFrameOutput] + roll_cancelled = pyqtSignal() + roll_error = pyqtSignal(str) + roll_status = pyqtSignal(str) def __init__(self, session_manager: DesktopSessionManager): super().__init__() @@ -307,6 +328,12 @@ def __init__(self, session_manager: DesktopSessionManager): # Scanning tab polls or the user acts. self._capture_thread_started = False + self.roll_thread = QThread() + self.roll_worker = RollWorker() + self.roll_worker.moveToThread(self.roll_thread) + # Same lazy-start reasoning as _capture_thread_started (_ensure_roll_thread). + self._roll_thread_started = False + self.canvas: Any = None self._is_rendering = False self._pending_render_task: Any = None @@ -493,6 +520,22 @@ def _connect_signals(self) -> None: self.capture_worker.poll_status.connect(self.connection_polled.emit) self.poll_light_temp_requested.connect(self.capture_worker.poll_light_temp) self.capture_worker.light_temp_polled.connect(self.light_temp_polled.emit) + self.roll_devices_requested.connect(self.roll_worker.list_devices) + self.roll_worker.devices_ready.connect(self.roll_devices_ready.emit) + self.roll_worker.opened.connect(self.roll_opened.emit) + self.roll_preview_requested.connect(self.roll_worker.run_preview) + self.roll_worker.preview_ready.connect(self.roll_preview_ready.emit) + self.roll_spacing_offset_requested.connect(self.roll_worker.set_spacing_offset) + self.roll_worker.spacing_offset_set.connect(self.roll_spacing_offset_set.emit) + self.roll_approve_requested.connect(self.roll_worker.approve) + self.roll_worker.approved.connect(self.roll_approved.emit) + self.roll_scan_requested.connect(self.roll_worker.run_batch_scan) + self.roll_worker.progress.connect(self.roll_progress.emit) + self.roll_worker.frame_written.connect(self.roll_frame_written.emit) + self.roll_worker.finished.connect(self._on_roll_scan_finished) + self.roll_worker.cancelled.connect(self.roll_cancelled.emit) + self.roll_worker.error.connect(self.roll_error.emit) + self.roll_worker.status.connect(self.roll_status.emit) self.session.active_file_changing.connect(lambda: self._update_thumbnail_from_state(force_readback=True)) self.session.session_emptied.connect(self._render_memo.clear) @@ -2116,6 +2159,49 @@ def _on_capture_finished(self, paths: list) -> None: self._pending_scanned_file = paths[0] self.request_asset_discovery(list(paths)) + # ── Roll scanning integration ───────────────────────────────────── + + def _ensure_roll_thread(self) -> None: + """Start the roll worker's thread on first use (lazy), mirroring + `_ensure_capture_thread`.""" + if not self._roll_thread_started: + self.roll_thread.start() + self._roll_thread_started = True + + def request_roll_devices(self) -> None: + self._ensure_roll_thread() + self.roll_devices_requested.emit() + + def start_roll_preview(self, req: RollPreviewRequest) -> None: + self._ensure_roll_thread() + self.roll_preview_requested.emit(req) + + def set_roll_spacing_offset(self, slot: int, offset_rows: int) -> None: + self._ensure_roll_thread() + self.roll_spacing_offset_requested.emit(slot, offset_rows) + + def approve_roll_slot(self, slot: int) -> None: + self._ensure_roll_thread() + self.roll_approve_requested.emit(slot) + + def start_roll_scan(self, req: RollBatchScanRequest) -> None: + self._ensure_roll_thread() + self.roll_scan_requested.emit(req) + + def roll_safe_stop(self) -> None: + self.roll_worker.safe_stop() + + def _on_roll_scan_finished(self, outputs: list) -> None: + """Feed newly-written roll frames into NegPy's file list, like the plain Scan and + Camera Scanning routes already do. Each output is already one complete per-slot RGB + TIFF (coolscanpy's roll engine writes one frame per slot, not an R/G/B triplet to + merge), so unlike `_on_capture_finished` this needs no rgbscan_mode/process_mode + bookkeeping -- just discovery.""" + self.roll_finished.emit(outputs) + rgb_paths = [o.rgb_path for o in outputs] + if rgb_paths: + self.request_asset_discovery(rgb_paths) + def effective_output_icc(self) -> Optional[str]: """Output profile the preview proofs through: a custom override, else the profile for the selected export color space. None means no proof (Same as Source).""" @@ -3030,6 +3116,10 @@ def cleanup(self) -> None: if self.capture_thread.isRunning(): self.capture_thread.quit() self.capture_thread.wait() + self.roll_worker.shutdown() + if self.roll_thread.isRunning(): + self.roll_thread.quit() + self.roll_thread.wait() self.render_worker.destroy_all() # All GPU-touching threads are now joined; release the wgpu device. diff --git a/negpy/desktop/view/sidebar/right_panel.py b/negpy/desktop/view/sidebar/right_panel.py index cfe085c8..46e9c9a4 100644 --- a/negpy/desktop/view/sidebar/right_panel.py +++ b/negpy/desktop/view/sidebar/right_panel.py @@ -101,8 +101,12 @@ def wrap_scroll(widget: QWidget) -> QScrollArea: self.scanlight_sidebar = ScanlightSidebar(self.controller) - # One "Scan" tab hosting both the SANE scanner and the RGB-Scan capture as - # collapsible sections (mirrors the "Colour — Lab, Toning" tab). + from negpy.desktop.view.sidebar.coolscan_roll import CoolscanRollSidebar + + self.coolscan_roll_sidebar = CoolscanRollSidebar(self.controller) + + # One "Scan" tab hosting the SANE scanner, the RGB-Scan capture and Coolscan roll + # scanning as collapsible sections (mirrors the "Colour — Lab, Toning" tab). self.scan_page = self._build_scan_page() # Tab descriptors: workflow control-group pages first, then Export / Metadata / Scan. @@ -213,8 +217,10 @@ def _resize_splitter_for_analysis(self, expanded: bool) -> None: self.splitter.setSizes([top, max(0, total - top)]) def _build_scan_page(self) -> QWidget: - """The 'Scan' tab hosts two collapsible sections (like Color's Lab / Toning): the - SANE flatbed/film scanner on top, the RGB-Scan trichromatic capture below.""" + """The 'Scan' tab hosts three collapsible sections (like Color's Lab / Toning): the + SANE flatbed/film scanner, the RGB-Scan trichromatic capture, and Coolscan roll + scanning -- each its own acquisition route, each gated on its own optional + dependency (SANE, gphoto2, coolscanpy respectively).""" repo = self.controller.session.repo def make(title: str, key: str, icon_name: str, content: QWidget, default_expanded: bool) -> CollapsibleSection: @@ -227,6 +233,7 @@ def make(title: str, key: str, icon_name: str, content: QWidget, default_expande self.scan_sane_section = make("Scanner (SANE)", "scan_sane", "fa5s.camera-retro", self.scan_sidebar, False) self.scan_rgb_section = make("Camera Scanning", "scan_rgb", "fa5s.camera", self.scanlight_sidebar, True) + self.scan_roll_section = make("Roll Scanning", "scan_roll", "fa5s.images", self.coolscan_roll_sidebar, False) page = QWidget() page_layout = QVBoxLayout(page) @@ -235,6 +242,7 @@ def make(title: str, key: str, icon_name: str, content: QWidget, default_expande page_layout.setSpacing(8) page_layout.addWidget(self.scan_sane_section) page_layout.addWidget(self.scan_rgb_section) + page_layout.addWidget(self.scan_roll_section) return page def apply_shortcut_tooltips(self) -> None: @@ -291,12 +299,15 @@ def _switch_tab(self, index: int) -> None: self._suspended_retouch_tool = None # Trigger device detection + gating refresh when the Scan tab is selected — it now - # hosts both the SANE scanner and the RGB-Scan capture as collapsible sections. + # hosts the SANE scanner, the RGB-Scan capture and Coolscan roll scanning as + # collapsible sections. if index == self._scan_index: if hasattr(self.scan_sidebar, "on_activated"): self.scan_sidebar.on_activated() if hasattr(self.scanlight_sidebar, "on_activated"): self.scanlight_sidebar.on_activated() + if hasattr(self.coolscan_roll_sidebar, "on_activated"): + self.coolscan_roll_sidebar.on_activated() def reveal_section(self, section_attr: str) -> None: """Switch to the tab containing the given ControlsPanel section.""" From 9bfcc0950abf97ecfe5fc646d4dc3290af792fa9 Mon Sep 17 00:00:00 2001 From: Rohan Pandula Date: Sat, 18 Jul 2026 23:37:15 -0700 Subject: [PATCH 09/48] Add a regression test for the roll-scan->import seam Mirrors test_capture_controller.py: exercises AppController._on_roll_scan_finished directly against a mock controller, so a future rename of roll_finished/request_asset_discovery fails a fast unit test instead of only showing up as scanned frames silently not appearing in the file browser. --- tests/test_roll_controller.py | 47 +++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 tests/test_roll_controller.py diff --git a/tests/test_roll_controller.py b/tests/test_roll_controller.py new file mode 100644 index 00000000..7179096d --- /dev/null +++ b/tests/test_roll_controller.py @@ -0,0 +1,47 @@ +"""Regression test for the roll-scan→import seam (AppController._on_roll_scan_finished). + +Mirrors test_capture_controller.py's shape: call the seam directly against a mock +controller, no real worker/thread/session involved. RollFrameOutput's shape (slot, +rgb_path, ir_path, receipt_path) is stood in with SimpleNamespace -- this seam only +reads .rgb_path, so nothing coolscanpy-shaped needs to be constructed. +""" + +from types import SimpleNamespace +from unittest.mock import MagicMock + +from negpy.desktop.controller import AppController + + +def _output(slot: int, rgb_path: str): + return SimpleNamespace(slot=slot, rgb_path=rgb_path, ir_path=None, receipt_path=f"{rgb_path}_receipt.json") + + +def test_writes_are_discovered_in_batch_order(): + controller = MagicMock() + outputs = [_output(1, "a.tif"), _output(2, "b.tif"), _output(3, "c.tif")] + + AppController._on_roll_scan_finished(controller, outputs) + + controller.roll_finished.emit.assert_called_once_with(outputs) + controller.request_asset_discovery.assert_called_once_with(["a.tif", "b.tif", "c.tif"]) + + +def test_empty_batch_still_emits_but_does_not_request_discovery(): + controller = MagicMock() + + AppController._on_roll_scan_finished(controller, []) + + controller.roll_finished.emit.assert_called_once_with([]) + controller.request_asset_discovery.assert_not_called() + + +def test_ir_and_receipt_paths_are_not_sent_to_discovery(): + """Only the RGB TIFF is a NegPy-openable asset; the _IR sidecar and the JSON + receipt are not scannable frames and must not be handed to discovery.""" + controller = MagicMock() + outputs = [SimpleNamespace(slot=1, rgb_path="frame.tif", ir_path="frame_IR.tif", receipt_path="frame_receipt.json")] + + AppController._on_roll_scan_finished(controller, outputs) + + (discovered,), _kwargs = controller.request_asset_discovery.call_args + assert discovered == ["frame.tif"] From 356bb8060527b5940a7ae0eb2996635922cbaf82 Mon Sep 17 00:00:00 2001 From: Rohan Pandula Date: Sat, 18 Jul 2026 23:40:24 -0700 Subject: [PATCH 10/48] Update roll-scanning docs for the new structure and the desktop panel Rewrites the module layout section around infrastructure/roll and services/roll instead of the old scanners/scanning locations, documents RollScanningError and is_safe_stop() alongside the existing exception- translation section, points the integration-point cross-reference at the new file paths, and replaces the old "not built yet" note (written before this branch had a UI at all) with a description of what the panel actually does and the three deliberate simplifications in it: no material picker, no separate close-roll button, and Safe Stop as its own control rather than a Scan/Stop toggle. Also comments the coolscan-roll dependency group in pyproject.toml, matching the existing camera group's comment. --- docs/COOLSCANPY_ROLL_SCANNING.md | 186 ++++++++++++++++++++----------- pyproject.toml | 2 + 2 files changed, 124 insertions(+), 64 deletions(-) diff --git a/docs/COOLSCANPY_ROLL_SCANNING.md b/docs/COOLSCANPY_ROLL_SCANNING.md index 56cda623..83854ff7 100644 --- a/docs/COOLSCANPY_ROLL_SCANNING.md +++ b/docs/COOLSCANPY_ROLL_SCANNING.md @@ -5,24 +5,24 @@ This page describes NegPy's optional integration with Python library for whole-roll scanning on a Nikon Coolscan LS-5000 with a roll feeder. coolscanpy talks to the scanner directly over USB and does not need SANE installed. NegPy consumes it the same way it consumes -python-sane or gphoto2: as an optional dependency that the rest of the -application never requires. +python-sane or gphoto2 elsewhere in this package: as an optional dependency +that the rest of the application never requires. ## What it adds The plain Scan panel in NegPy scans one frame at a time through SANE. A -roll feeder changes the shape of the problem: you preview the whole roll in -one transport pass, check or nudge each frame's spacing, and then run a +roll feeder changes the shape of the problem. You preview the whole roll +in one transport pass, check or nudge each frame's spacing, and then run a batch fine scan across a list of slots without reloading the film. That -whole-roll workflow, preview, spacing correction, approval of any frame the -transport could not place automatically, and batch scanning with per-frame -receipts, is what coolscanpy implements and what this integration exposes -inside NegPy. +whole-roll workflow is what coolscanpy implements: preview, spacing +correction, approval of any frame the transport could not place +automatically, and batch scanning with per-frame receipts. This +integration exposes all of it inside NegPy, including a desktop panel. Coverage is narrower than the plain Scan panel. Only color negative film scans through coolscanpy's roll engine end to end today. Black and white negative previews and approves normally, but batch fine scanning it raises -`NotImplementedError` inside coolscanpy itself; that limitation is +`NotImplementedError` inside coolscanpy itself. That limitation is documented in the coolscanpy README and is unchanged by this integration. ## Install @@ -32,7 +32,8 @@ until coolscanpy is importable, and every entry point checks for that before doing anything else. If you use uv, sync the new dependency group the same way you already -sync the `scanner` group for python-sane: +sync the `scanner` group for python-sane or the `camera` group for +gphoto2: ``` uv sync --group coolscan-roll @@ -57,7 +58,7 @@ Live hardware validation on 2026-07-18 confirmed USB enumeration, device open, and a full roll preview with correct spacing and manual-review flagging. Fine scanning and infrared capture through the roll engine have not yet been re-run live since coolscanpy was extracted into its own -package; that is the remaining validation step on the coolscanpy side, not +package. That is the remaining validation step on the coolscanpy side, not something specific to this NegPy integration. Every other Coolscan model and every roll feeder other than the SA-21/SA-30 @@ -66,6 +67,33 @@ LS-5000-only where it does not have to be, but nothing beyond that one combination has scanned real film. Windows has never run the roll engine against hardware. +## Structure + +The camera capture route (Scanlight light source plus a tethered camera, +under `negpy/infrastructure/capture/` and `negpy/services/capture/`) lives +beside NegPy's single-frame `ScannerBackend` protocol, not inside it, +because it is a different acquisition workflow with its own hardware +lifecycle. Roll scanning follows the same shape rather than sitting inside +the plain-SANE scanner packages: + +- `negpy/infrastructure/roll/coolscanpy_roll.py` is the hardware adapter. + It is the only module that imports coolscanpy, and it translates + coolscanpy's typed exceptions to plain `RuntimeError`s the way + `SaneBackend` already does for the plain scanner (see Error handling + below). `negpy/infrastructure/roll/settings.py` holds the sidebar's + persisted settings, `RollScanSettings`, mirroring `ScannerSettings` and + `ScanlightSettings`. +- `negpy/services/roll/service.py` defines `RollScanningService`, which + orchestrates the coolscanpy device and roll lifecycle and writes results + to disk. It never imports coolscanpy itself, only the adapter above. +- `negpy/desktop/workers/roll_worker.py` defines `RollWorker`, a `QObject` + moved to its own thread, mirroring `CaptureWorker`. It opens a device's + roll extension lazily the first time a preview or batch scan names it, + and holds that reservation open across calls instead of reopening it + each time. +- `negpy/desktop/view/sidebar/coolscan_roll.py` defines + `CoolscanRollSidebar`, described below. + ## What this integration writes A batch scan produces, per slot, three files in the configured output @@ -75,7 +103,7 @@ folder: - a 16-bit TIFF holding the infrared plane, named with an `_IR` suffix next to the RGB file, matching the suffix NegPy's plain scan writer already uses for infrared -- a JSON file with an `_receipt` suffix, holding the full scan receipt +- a JSON file with a `_receipt` suffix, holding the full scan receipt coolscanpy returns for that frame: exposure, clipping and focus telemetry, transport-smear assessment, and the fingerprints the frame was checked against @@ -87,10 +115,12 @@ numbers are already a stable identity for a given roll, so re-scanning one bad slot overwrites that slot's old files instead of accumulating a second copy beside them. -The infrared confidence mask coolscanpy also returns per frame is not -written to disk by this integration. Nothing downstream currently reads -it, and adding a file format for it before there is a consumer would be -guessing at a convention rather than following one. +Only the RGB TIFF is handed to NegPy's asset discovery once a batch scan +finishes. The IR sidecar and the receipt are written but never opened as +an asset. The infrared confidence mask coolscanpy also returns per frame +is not written to disk at all. Nothing downstream currently reads it, and +adding a file format for it before there is a consumer would be guessing +at a convention rather than following one. ## Error handling @@ -98,61 +128,89 @@ coolscanpy raises a typed exception hierarchy rooted at `PyCoolscanError`, covering things like a roll that no longer matches its last preview, a slot that needs manual approval before it can be scanned, and a requested safe stop. NegPy's existing scanner code has no typed -exception vocabulary of its own; `SaneBackend` reports every failure as a -plain `RuntimeError` with a human-readable message, and that message is -what ends up in the Scan panel's status label. This integration follows -the same convention: every coolscanpy exception is flattened to a -`RuntimeError` at the boundary, with the original exception preserved as -`__cause__` for any caller that wants to branch on it. - -One case is worth calling out for whoever builds the interactive panel -described below. `Roll.safe_stop()` lets the frame already in flight -finish and only refuses the next one, by raising `SafeStopRequested`. The -existing plain-scan worker treats a cancellation the same way, checking -its own cancel flag and returning quietly instead of reporting an error. -A roll-scanning worker should do the same for a `RuntimeError` whose -`__cause__` is `SafeStopRequested`: that is an expected stop, not a -failure. +exception vocabulary of its own for coolscanpy's own failures; +`SaneBackend` reports every failure as a plain `RuntimeError` with a +human-readable message, and that message is what ends up in a status +label. This integration follows the same convention: every coolscanpy +exception is flattened to a `RuntimeError` at the boundary, with the +original exception preserved as `__cause__` for any caller that wants to +branch on it. + +`RollScanningService` itself raises one exception type of its own, +`RollScanningError`, for a lifecycle misuse that has nothing to do with +coolscanpy: calling `open_roll()` twice without closing the first +reservation, or calling `preview()`/`scan_many()` before `open_roll()` at +all. It subclasses `RuntimeError` and mirrors `CaptureError` on the camera +route, which exists for the same reason on that side. + +`Roll.safe_stop()` lets the frame already in flight finish and only +refuses the next one, by raising `SafeStopRequested`. `RollWorker` +distinguishes that deliberate stop from a real failure with +`coolscanpy_roll.is_safe_stop()`, which checks whether a caught +`RuntimeError`'s `__cause__` is coolscanpy's `SafeStopRequested`. A stop +reported this way ends the batch scan the same way a cancelled plain scan +already does: quietly, not as an error. ## The integration point Everything that touches coolscanpy itself lives in one file, -`negpy/infrastructure/scanners/coolscanpy_roll.py`. `open_roll()` in that +`negpy/infrastructure/roll/coolscanpy_roll.py`. `open_roll()` in that file is the single place a device handle gets resolved for the roll -workflow; it currently calls `coolscanpy.open()` directly. Everything else, -including `negpy/services/scanning/roll_service.py`, only ever talks to the -`RollHandle` that function returns. +workflow. It currently calls `coolscanpy.open()` directly. Everything +else, including `negpy/services/roll/service.py`, the worker, and the +sidebar, only ever talks to the `RollHandle` that function returns. That module is marked in a comment as the intended target for a future change. NegPy's maintainer has a generic SANE-based coolscan route planned upstream, which is expected to add a real backend-selection seam to -`ScannerService` (today `ScannerService._get_backend()` simply hardcodes +`ScannerService` (today `ScannerService._get_backend()` just hardcodes `SaneBackend()`, with no selection mechanism at all). When that seam -lands, re-pointing this roll integration at it should only require changing -how `open_roll()` resolves a device. The roll workflow built on top of it, -the exception translation in the same file, and the whole of -`roll_service.py`, should not need to change. - -## What is not built yet - -This integration ships the backend and service layer: device discovery, -roll preview, spacing correction, approval, batch scanning, and file -writing, all covered by hardware-free tests. It does not ship a contact -sheet panel with rendered thumbnail images in the desktop GUI. Building -that panel from scratch, before the maintainer's own SANE-based route -defines how a Coolscan device is meant to surface in the UI, risked adding -exactly the kind of large, hard-to-review, foreign-feeling change this -integration is meant to avoid. - -A future panel can follow the existing Scan panel closely. Look at -`negpy/desktop/view/sidebar/scan.py` for the sidebar structure and its -`_sane_available()` availability gate, `negpy/desktop/workers/scan_worker.py` -for the background-thread pattern (a `QObject` moved to its own `QThread`, -progress and result reported back over Qt signals), and -`negpy/desktop/controller.py` for how that worker's signals get wired into -the rest of the application. A roll panel would follow the same shape, -calling `RollScanningService` instead of `ScannerService`, gated behind -`coolscanpy_roll.available()` the same way the plain panel gates itself -behind SANE. The simplest first version would not need thumbnail images at -all: a plain list of slots with their approval state, backed by -`Roll.preview()`, is enough to let someone pick which slots to scan. +lands, re-pointing this roll integration at it should only require +changing how `open_roll()` resolves a device. The roll workflow built on +top of it, the exception translation in the same file, the whole of +`service.py`, and the worker and sidebar built on top of that, should not +need to change. + +## The desktop panel + +`CoolscanRollSidebar` lives in the Scan tab as a third collapsible +section, "Roll Scanning", next to "Scanner (SANE)" and "Camera Scanning". +It is built unconditionally and hidden behind a setup hint when +coolscanpy is not importable, the same way `ScanlightSidebar` handles a +missing gphoto2. + +The panel covers device selection, a whole-roll preview rendered as a +thumbnail contact sheet, a spacing-offset spinner and an approve button +for whichever slot is selected, and a batch scan of every selected slot +with a progress bar and a Safe Stop button. Safe Stop calls +`Roll.safe_stop()` through the worker, which is coolscanpy's own name for +this action and its own contract: the frame already in flight always +finishes, and only the next one is refused. A Scan/Stop toggle button, the +shape the plain Scan and Camera Scanning panels use for their own cancel +action, would imply the click stops the frame in progress. Safe Stop +cannot promise that, so it gets a name and a button that say what it +actually does. + +The panel keeps a roll reservation open for the life of the app session, +including across a tab switch. It closes the current reservation only +when the user picks a different device, opening the new one in its place. +There is no separate close button. This differs from the camera route, +where the tethered session is deliberately released once neither the +live-view window nor the calibration window is open, because some camera +bodies get stuck in a tethered-capture state if a session is left open +past the window that used it. coolscanpy's roll reservation has no +equivalent failure mode, so there is nothing to protect against by +closing it early. + +The panel does not expose a material picker. It always opens a roll as +color negative, matching the one material coolscanpy's roll engine +fine-scans end to end today. A black and white picker would only ever +support preview, not the batch scan, as the coverage note above already +explains, so adding one now would be a control with no working action +behind it. + +A contact sheet thumbnail is a raw scanner preview frame, not a NegPy +pipeline buffer, so it is converted to a displayable image with a small +local helper rather than going through NegPy's color-managed +`ImageConverter`. That converter assumes a working-space buffer a +scanner's preview array is not. diff --git a/pyproject.toml b/pyproject.toml index c61807a3..c26a46b2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,6 +56,8 @@ camera = [ "gphoto2>=2.5 ; sys_platform != 'win32'", ] coolscan-roll = [ + # Direct-USB roll scanning on a Nikon Coolscan LS-5000 with an SA-21/SA-30 feeder. + # No SANE dependency for this workflow; see docs/COOLSCANPY_ROLL_SCANNING.md. "coolscanpy>=0.1.2", ] From a4a3ccd4452d0035fcf5e4cae555682ad02bca6a Mon Sep 17 00:00:00 2001 From: Rohan Pandula Date: Sat, 18 Jul 2026 23:46:41 -0700 Subject: [PATCH 11/48] Tighten CoolscanRollSidebar's typing and guard a None item access Types _thumbnails and the worker-signal handler parameters against coolscanpy's real Thumbnail/DeviceInfo (TYPE_CHECKING-only, matching the lazy-import discipline everywhere else) and RollFrameOutput, instead of bare object/list. Guards contact_sheet.item(i) against the None a QListWidgetItem lookup can return per the PyQt6 stubs, even though the loop's own range() never actually produces an out-of-range index. --- negpy/desktop/view/sidebar/coolscan_roll.py | 26 +++++++++++++-------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/negpy/desktop/view/sidebar/coolscan_roll.py b/negpy/desktop/view/sidebar/coolscan_roll.py index 733a20b9..ead2ab02 100644 --- a/negpy/desktop/view/sidebar/coolscan_roll.py +++ b/negpy/desktop/view/sidebar/coolscan_roll.py @@ -13,6 +13,7 @@ from __future__ import annotations import dataclasses +from typing import TYPE_CHECKING import numpy as np import qtawesome as qta @@ -39,13 +40,17 @@ from negpy.desktop.view.styles.theme import THEME from negpy.infrastructure.roll import coolscanpy_roll from negpy.infrastructure.roll.settings import RollScanSettings +from negpy.services.roll.service import RollFrameOutput + +if TYPE_CHECKING: + import coolscanpy _SLOT_ROLE = Qt.ItemDataRole.UserRole _WARN_COLOR = "#C8922E" # matches ScanlightSidebar's advisory tone _THUMBNAIL_SIZE = QSize(96, 96) -def _thumbnail_pixmap(image) -> QPixmap: +def _thumbnail_pixmap(image: "np.ndarray") -> QPixmap: """A `coolscanpy.Thumbnail.image` array as a displayable QPixmap. Deliberately dumb: this is a raw scanner preview frame for framing and @@ -76,7 +81,7 @@ def __init__(self, controller) -> None: self._settings: RollScanSettings = self._load_settings() self._devices: list = [] self._devices_loaded = False - self._thumbnails: dict[int, object] = {} + self._thumbnails: dict[int, "coolscanpy.Thumbnail"] = {} self._scanning = False self._stopping = False # safe-stop acknowledged, waiting for the in-flight frame @@ -271,7 +276,7 @@ def _on_refresh(self) -> None: self._request_devices() @pyqtSlot(list) - def _on_devices_ready(self, devices: list) -> None: + def _on_devices_ready(self, devices: "list[coolscanpy.DeviceInfo]") -> None: self._devices = devices self._devices_loaded = True self.device_combo.clear() @@ -326,7 +331,7 @@ def _on_preview_clicked(self) -> None: self.controller.start_roll_preview(RollPreviewRequest(device_id=device_id)) @pyqtSlot(list) - def _on_preview_ready(self, thumbnails: list) -> None: + def _on_preview_ready(self, thumbnails: "list[coolscanpy.Thumbnail]") -> None: self.progress_bar.setVisible(False) self._thumbnails = {t.slot: t for t in thumbnails} self.contact_sheet.clear() @@ -335,7 +340,7 @@ def _on_preview_ready(self, thumbnails: list) -> None: self._show_slot_detail(None) self._apply_gating() - def _add_slot_item(self, thumb) -> None: + def _add_slot_item(self, thumb: "coolscanpy.Thumbnail") -> None: pixmap = _thumbnail_pixmap(thumb.image).scaled( _THUMBNAIL_SIZE, Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation ) @@ -351,7 +356,7 @@ def _on_selection_changed(self) -> None: self._show_slot_detail(self._thumbnails.get(slot) if slot is not None else None) self._apply_gating() - def _show_slot_detail(self, thumb) -> None: + def _show_slot_detail(self, thumb: "coolscanpy.Thumbnail | None") -> None: if thumb is None: self.slot_label.setText("—") self.offset_spin.setEnabled(False) @@ -398,11 +403,12 @@ def _on_approved(self, slot: int) -> None: self._thumbnails[slot] = dataclasses.replace(thumb, needs_approval=False) for i in range(self.contact_sheet.count()): item = self.contact_sheet.item(i) - if item.data(_SLOT_ROLE) == slot: + if item is not None and item.data(_SLOT_ROLE) == slot: item.setText(f"Slot {slot}") item.setData(Qt.ItemDataRole.ForegroundRole, None) # back to the theme's default text color break - if self.contact_sheet.currentItem() is not None and self.contact_sheet.currentItem().data(_SLOT_ROLE) == slot: + current = self.contact_sheet.currentItem() + if current is not None and current.data(_SLOT_ROLE) == slot: self.approve_btn.setVisible(False) self._set_status(f"Slot {slot} approved.") self._apply_gating() @@ -442,12 +448,12 @@ def _on_progress(self, fraction: float, message: str) -> None: self._set_status(message) @pyqtSlot(object) - def _on_frame_written(self, output) -> None: + def _on_frame_written(self, output: RollFrameOutput) -> None: if not self._stopping: self._set_status(f"Wrote slot {output.slot}.") @pyqtSlot(list) - def _on_finished(self, outputs: list) -> None: + def _on_finished(self, outputs: list[RollFrameOutput]) -> None: self.set_scanning(False) self._set_status(f"Scanned {len(outputs)} frame(s).") From 713cc53acbfe7808151e67e48292ce97c923369c Mon Sep 17 00:00:00 2001 From: Rohan Pandula Date: Sat, 18 Jul 2026 23:47:54 -0700 Subject: [PATCH 12/48] Correct an overstated comparison in RollWorker.safe_stop()'s docstring The previous wording implied the camera route's threading.Event cancel is "instant"; it isn't, it's checked between channels the same way safe_stop is checked between frames here. The real reason RollWorker carries no local cancel flag of its own is that coolscanpy already exposes Roll.safe_stop() for this, where gphoto2 exposes nothing equivalent and CaptureWorker had to build its own. --- negpy/desktop/workers/roll_worker.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/negpy/desktop/workers/roll_worker.py b/negpy/desktop/workers/roll_worker.py index 21b6d080..7ca46003 100644 --- a/negpy/desktop/workers/roll_worker.py +++ b/negpy/desktop/workers/roll_worker.py @@ -162,11 +162,13 @@ def safe_stop(self) -> None: Not a `@pyqtSlot`, called directly cross-thread like `CaptureWorker .cancel()`: `RollScanningService.safe_stop()` only sets a `threading.Event`, which is thread-safe on its own and needs no Qt - queueing. The transport has no equivalent of the camera route's - instant `threading.Event`-gated cancel -- a fine-scan already in - flight cannot be aborted mid-pull, so unlike `CaptureWorker` there - is no separate local cancel flag here; safe_stop() is the only stop, - and it always lets that one frame finish first. + queueing. Unlike `CaptureWorker`, there is no separate local cancel + flag here -- gphoto2 has no stop primitive of its own, so + `CaptureWorker` keeps its own `threading.Event` and checks it + between channels; coolscanpy already exposes `Roll.safe_stop()` for + exactly this, so adding a second flag here would only duplicate it. + Either way the frame already in flight finishes; only the next one + is refused. """ self._service.safe_stop() From c5a863882c17a404ae78c3f5ace6954db8309629 Mon Sep 17 00:00:00 2001 From: Rohan Pandula Date: Sun, 19 Jul 2026 02:08:19 -0700 Subject: [PATCH 13/48] Add the Tier-2 repair engine seam RollScanningService will write a frame's dust/scratch-repaired RGBI as its own output tier, but no repair engine ships in this package. This adds the plug point: available()/register_engine()/repair(), with nothing registered by default so a caller degrades instead of writing data mislabeled as repaired. --- negpy/infrastructure/roll/repair.py | 101 ++++++++++++++++++++++++++++ tests/roll/conftest.py | 41 +++++++++++ tests/roll/test_repair.py | 62 +++++++++++++++++ 3 files changed, 204 insertions(+) create mode 100644 negpy/infrastructure/roll/repair.py create mode 100644 tests/roll/test_repair.py diff --git a/negpy/infrastructure/roll/repair.py b/negpy/infrastructure/roll/repair.py new file mode 100644 index 00000000..7f67d646 --- /dev/null +++ b/negpy/infrastructure/roll/repair.py @@ -0,0 +1,101 @@ +"""Tier-2 dust/scratch repair engine seam for roll-scanned RGBI frames. + +The roll adapter writes a captured frame in up to three tiers (see +`negpy.services.roll.service.write_frame`): the unrepaired RGBI capture, the +same frame with infrared-guided dust/scratch repair applied, and a positive +rendered from the repaired frame. This module is the plug point for the +middle tier -- it does not repair anything itself. + +No repair engine ships in this package. `available()` returns False and +`repair()` raises until something calls `register_engine()` with a real +implementation. That keeps Tier 2 honest: a frame this module cannot +actually repair is reported as unavailable rather than written to disk as +"repaired" when it is really just a copy of Tier 1. See +`docs/COOLSCANPY_ROLL_SCANNING.md` for the intended capture -> repair -> +invert ordering and which project is expected to fill this slot. +""" + +from __future__ import annotations + +from dataclasses import dataclass +from enum import StrEnum +from typing import Protocol + +import numpy as np + + +class RepairMode(StrEnum): + """A Tier-2 repair variant. + + EXACT heals only the pixels the infrared channel confidently flags as a + defect. HYBRID additionally routes severe zero-signal regions (where the + infrared channel carries no usable signal at all, e.g. a dense scratch) + to an inpainting model. Both are deterministic -- the same frame and mode + repair to the same result every time -- which is what makes Tier 2 worth + caching: unlike Tier 3, it never needs to be redone once it has run. + """ + + EXACT = "exact" + HYBRID = "hybrid" + + +@dataclass(frozen=True) +class RepairResult: + """One frame's Tier-2 RGB, plus what the receipt needs to audit or + reproduce it. The infrared plane is not part of this result -- Tier 2 + retains Tier 1's own infrared plane unchanged (see `service.write_frame`), + since repair consumes infrared to find defects rather than producing a + repaired version of it.""" + + rgb: np.ndarray + engine: str + engine_version: str + mode: RepairMode + + +class RepairEngine(Protocol): + """What a repair implementation must provide to `register_engine()`.""" + + def repair(self, rgb: np.ndarray, ir: np.ndarray, mode: RepairMode) -> RepairResult: ... + + +_engine: RepairEngine | None = None + + +def available() -> bool: + """True once a repair engine has been registered. + + Cheap and side-effect-free, mirroring `coolscanpy_roll.available()`, so + `RollScanningService.write_frame` can decide whether Tier 2 (and, since + Tier 3 is defined as Tier 2 inverted, Tier 3 too) can be produced before + doing any work. + """ + return _engine is not None + + +def register_engine(engine: RepairEngine) -> None: + """The integration point a future repair implementation is expected to + call -- e.g. at import time, guarded the same way `coolscanpy_roll` + guards its own optional dependency -- to make Tier 2 and Tier 3 + available. Nothing in this codebase calls this today; see the module + docstring.""" + global _engine + _engine = engine + + +def unregister_engine() -> None: + """Reverts to the unavailable state. Mainly a test teardown hook.""" + global _engine + _engine = None + + +def repair(rgb: np.ndarray, ir: np.ndarray, mode: RepairMode) -> RepairResult: + """Repair one frame's RGB using its infrared plane. + + Raises `RuntimeError` if no engine is registered -- callers on the + roll-scanning write path check `available()` first so a missing engine + degrades Tier 2/3 with a clear status instead of raising mid-batch. + """ + if _engine is None: + raise RuntimeError("no dust-repair engine is registered; Tier 2 and Tier 3 are unavailable") + return _engine.repair(rgb, ir, mode) diff --git a/tests/roll/conftest.py b/tests/roll/conftest.py index 47eb4659..e8db2a68 100644 --- a/tests/roll/conftest.py +++ b/tests/roll/conftest.py @@ -17,6 +17,8 @@ import pytest +from negpy.infrastructure.roll import repair as roll_repair + class FakePyCoolscanError(Exception): """Stand-in for coolscanpy.PyCoolscanError.""" @@ -193,3 +195,42 @@ def fake_open(devname: str): Frame=FakeFrame, Receipt=FakeReceipt, ) + + +class FakeRepairEngine: + """Scripted stand-in for `negpy.infrastructure.roll.repair.RepairEngine`. + + By default `repair()` is the identity transform (returns `rgb` unchanged, + tagged with this engine's name/version/mode) -- set `.transform` to a + function to script a detectable change, or `.raise_error` to an exception + instance to script a failure. `.calls` records every `(rgb, ir, mode)` + the service passed in, so a test can assert *what* was repaired (e.g. + the Tier-1 array, not something already touched) without needing the + repair to actually alter pixels. + """ + + def __init__(self, *, engine: str = "test-repair-engine", engine_version: str = "0.0.1-test") -> None: + self.engine = engine + self.engine_version = engine_version + self.transform = lambda rgb: rgb + self.raise_error: Exception | None = None + self.calls: list[tuple[Any, Any, roll_repair.RepairMode]] = [] + + def repair(self, rgb: Any, ir: Any, mode: roll_repair.RepairMode) -> roll_repair.RepairResult: + self.calls.append((rgb, ir, mode)) + if self.raise_error is not None: + raise self.raise_error + return roll_repair.RepairResult(rgb=self.transform(rgb), engine=self.engine, engine_version=self.engine_version, mode=mode) + + +@pytest.fixture +def fake_repair_engine(): + """Registers a `FakeRepairEngine` as the Tier-2 repair engine for one test, + unregistering it afterward so `repair.available()` reverts to False for the + next test regardless of how this one exits.""" + engine = FakeRepairEngine() + roll_repair.register_engine(engine) + try: + yield engine + finally: + roll_repair.unregister_engine() diff --git a/tests/roll/test_repair.py b/tests/roll/test_repair.py new file mode 100644 index 00000000..b9da715c --- /dev/null +++ b/tests/roll/test_repair.py @@ -0,0 +1,62 @@ +"""Tests for the Tier-2 repair engine seam (negpy.infrastructure.roll.repair). + +No repair engine ships in this package, so `available()` must be False and +`repair()` must raise until a caller registers one -- these tests pin that +contract down, since `RollScanningService.write_frame`'s Tier 2/3 degrade +path depends on it. `fake_repair_engine` (tests/roll/conftest.py) scripts a +registered engine for the tests that need one; it always unregisters on +teardown, so a test that does not use it sees the unavailable state. +""" + +from __future__ import annotations + +import numpy as np +import pytest + +from negpy.infrastructure.roll import repair as roll_repair + + +class TestUnavailableByDefault: + def test_available_is_false_with_nothing_registered(self) -> None: + assert roll_repair.available() is False + + def test_repair_raises_when_unavailable(self) -> None: + rgb = np.zeros((2, 2, 3), dtype=np.uint16) + ir = np.zeros((2, 2), dtype=np.uint16) + with pytest.raises(RuntimeError, match="no dust-repair engine is registered"): + roll_repair.repair(rgb, ir, roll_repair.RepairMode.EXACT) + + +class TestRegisteredEngine: + def test_available_becomes_true(self, fake_repair_engine) -> None: + assert roll_repair.available() is True + + def test_repair_delegates_to_the_registered_engine(self, fake_repair_engine) -> None: + rgb = np.full((2, 2, 3), 100, dtype=np.uint16) + ir = np.full((2, 2), 50, dtype=np.uint16) + + result = roll_repair.repair(rgb, ir, roll_repair.RepairMode.HYBRID) + + assert fake_repair_engine.calls == [(rgb, ir, roll_repair.RepairMode.HYBRID)] + assert result.engine == "test-repair-engine" + assert result.engine_version == "0.0.1-test" + assert result.mode == roll_repair.RepairMode.HYBRID + np.testing.assert_array_equal(result.rgb, rgb) # default transform is identity + + def test_unregister_reverts_to_unavailable(self, fake_repair_engine) -> None: + roll_repair.unregister_engine() + assert roll_repair.available() is False + with pytest.raises(RuntimeError): + roll_repair.repair(np.zeros((1, 1, 3), dtype=np.uint16), np.zeros((1, 1), dtype=np.uint16), roll_repair.RepairMode.EXACT) + + +class TestRepairMode: + def test_values_are_exact_and_hybrid(self) -> None: + assert roll_repair.RepairMode.EXACT.value == "exact" + assert roll_repair.RepairMode.HYBRID.value == "hybrid" + + def test_constructible_from_its_own_string_value(self) -> None: + """RollScanningService.write_frame coerces a persisted settings string + back to a RepairMode this way; pin the round-trip.""" + assert roll_repair.RepairMode("exact") is roll_repair.RepairMode.EXACT + assert roll_repair.RepairMode("hybrid") is roll_repair.RepairMode.HYBRID From 5fecdc8f7ae45cb1f74d17d43ee2c740ccbe9fd9 Mon Sep 17 00:00:00 2001 From: Rohan Pandula Date: Sun, 19 Jul 2026 02:08:29 -0700 Subject: [PATCH 14/48] Add Tier-3 positive rendering via NegPy's own pipeline render_positive() inverts a scanner-linear RGB buffer to a positive by calling ImageProcessor.run_pipeline directly on the in-memory array -- the same DarkroomEngine entry point a freshly imported negative reaches on first open, and the same one NegPy's own export pipeline calls after decoding a file. A stock WorkspaceConfig is used (C41, print intent, auto exposure on): the ordinary default conversion, not a custom one, since this tier is a regenerable view rather than an edit. Each call gets a fresh source_hash rather than one derived from the frame, deliberately: the engine's stage cache reuses a prior render when both the source hash and the settings hash match, and every Tier-3 call here shares the same stock settings, so a stable hash reused across two different frames would silently hand back a previous frame's pixels. --- negpy/services/roll/positive.py | 108 ++++++++++++++++++++++++++++++++ tests/roll/test_positive.py | 82 ++++++++++++++++++++++++ 2 files changed, 190 insertions(+) create mode 100644 negpy/services/roll/positive.py create mode 100644 tests/roll/test_positive.py diff --git a/negpy/services/roll/positive.py b/negpy/services/roll/positive.py new file mode 100644 index 00000000..e076b95c --- /dev/null +++ b/negpy/services/roll/positive.py @@ -0,0 +1,108 @@ +"""Tier-3 positive rendering: NegPy's own negative-to-positive pipeline, +called on an in-memory Tier-2 buffer instead of introducing a second one. + +`DarkroomEngine.process()` (`negpy.services.rendering.engine`) is how any +imported negative reaches a rendered positive in NegPy today -- it is what +runs when a scan is first opened, what the live canvas re-runs on every +edit, and what the export pipeline (`ImageProcessor.process_export`) calls +after decoding a source file. `ImageProcessor.run_pipeline` is that same +entry point one layer up, taking an in-memory buffer directly rather than a +file path -- it is what `process_export` itself calls after its own decode +step. A roll-scanned frame is already an in-memory buffer, so this module +calls `run_pipeline` straight, skipping the file-decode and the ICC export +encoding `process_export` also does, neither of which apply to a Tier 2 +buffer that never touches disk to get here (see `service.write_frame`'s +"never derive an upper tier from a lower one" -- the positive comes from the +same in-memory array Tier 2 optionally writes, not by reading a Tier 2 file +back). +""" + +from __future__ import annotations + +import uuid +from dataclasses import dataclass + +import numpy as np + +import negpy +from negpy.domain.models import WorkspaceConfig +from negpy.kernel.image.logic import float_to_uint16, uint16_to_float32 +from negpy.kernel.system.config import APP_CONFIG +from negpy.services.rendering.image_processor import ImageProcessor + + +@dataclass(frozen=True) +class PositiveResult: + """One frame's Tier-3 RGB, plus what the receipt needs to audit or + reproduce it.""" + + rgb: np.ndarray + render_intent: str + process_mode: str + auto_exposure: bool + negpy_version: str + + +def available() -> bool: + """True if the inversion path can be used. + + Unlike coolscanpy or a Tier-2 repair engine, NegPy's rendering pipeline + is not an optional external dependency -- it ships with this + application, so this always returns True today. It exists as a seam + with the same shape as `coolscanpy_roll.available()` and + `repair.available()`, so `service.write_frame` has one place to check + before rendering (and a test simulating "inversion unavailable" has one + place to patch) instead of a bare `try/except` being the only signal. + """ + return True + + +def render_positive(rgb_u16: np.ndarray, *, processor: ImageProcessor) -> PositiveResult: + """Invert a scanner-linear RGB buffer (Tier 2, or Tier 1 if Tier 2 could + not be produced -- callers decide what to pass) to a positive. + + Uses a stock, unedited `WorkspaceConfig`: process mode C41 (this + adapter only ever opens a roll as color negative -- see + `coolscanpy_roll.open_roll`), render intent PRINT (NegPy's default + photographic-paper conversion, as opposed to the flat digital- + intermediate master), and auto exposure on. That is exactly what a user + would see on first opening this frame's negative in NegPy without + touching a single slider -- the ordinary default conversion, not a + custom or maximal-latitude one, since this tier is a regenerable view + rather than an edit a person has invested time in. + + `processor` is injected rather than constructed here so a caller can + hold one `ImageProcessor` across a whole batch scan (its constructor + probes for GPU acceleration, which is worth doing once, not per frame) + even though rendering itself always runs on the CPU engine below. + """ + settings = WorkspaceConfig() + img = uint16_to_float32(np.ascontiguousarray(rgb_u16, dtype=np.uint16)) + + rendered, _metrics = processor.run_pipeline( + img, + settings, + # A fresh id per call, deliberately not a stable hash of the frame: the + # engine's stage cache reuses a prior render whenever both source_hash + # and the settings hash match, and every Tier-3 render here shares the + # same stock settings -- a stable/reused hash across two different + # frames (or two different slots re-scanned in one session) would + # silently hand back a *previous frame's* rendered pixels instead of + # this one's. A fresh id guarantees a real render every time; it has + # no bearing on reproducibility, since the pipeline math itself is a + # pure function of (image, settings) and this cache is a same-process + # performance optimization, not a determinism mechanism. + source_hash=uuid.uuid4().hex, + render_size_ref=float(APP_CONFIG.preview_render_size), + prefer_gpu=False, # CPU engine: deterministic, and this is a batch worker, not the live canvas + wants_uv_grid=False, + skip_flatfield=True, # no flat-field calibration exists for a roll-scanner buffer + ) + + return PositiveResult( + rgb=float_to_uint16(rendered), + render_intent=str(settings.exposure.render_intent), + process_mode=str(settings.process.process_mode), + auto_exposure=settings.exposure.auto_exposure, + negpy_version=negpy.__version__, + ) diff --git a/tests/roll/test_positive.py b/tests/roll/test_positive.py new file mode 100644 index 00000000..8fbd365a --- /dev/null +++ b/tests/roll/test_positive.py @@ -0,0 +1,82 @@ +"""Tests for Tier-3 rendering (negpy.services.roll.positive): a thin wrapper +around NegPy's own ImageProcessor.run_pipeline, exercised for real -- no +scanner or GPU involved, `prefer_gpu=False` forces the CPU engine, matching +how the rest of this test suite already drives ImageProcessor directly +(see tests/test_image_processor.py). +""" + +from __future__ import annotations + +import numpy as np + +from negpy.services.rendering.image_processor import ImageProcessor +from negpy.services.roll import positive + + +def _negative_like(seed: int, shape=(40, 60)) -> np.ndarray: + """A plausible scanner-linear C41 negative: red-biased (orange mask), + with real per-pixel variance so the pipeline's percentile-based bounds + analysis has something non-degenerate to measure.""" + rng = np.random.default_rng(seed) + rgb = np.zeros((*shape, 3), dtype=np.uint16) + rgb[..., 0] = rng.integers(40000, 60000, size=shape) + rgb[..., 1] = rng.integers(25000, 45000, size=shape) + rgb[..., 2] = rng.integers(15000, 35000, size=shape) + return rgb + + +class TestAvailable: + def test_always_true(self) -> None: + """Unlike coolscanpy or a Tier-2 repair engine, NegPy's own rendering + pipeline ships with this application -- there is nothing to install.""" + assert positive.available() is True + + +class TestRenderPositive: + def test_returns_uint16_rgb_of_the_same_shape(self) -> None: + rgb = _negative_like(seed=1) + result = positive.render_positive(rgb, processor=ImageProcessor()) + + assert result.rgb.shape == rgb.shape + assert result.rgb.dtype == np.uint16 + + def test_uses_the_default_c41_print_conversion(self) -> None: + """Stock WorkspaceConfig(): the same conversion a freshly opened + negative gets before any slider is touched.""" + rgb = _negative_like(seed=2) + result = positive.render_positive(rgb, processor=ImageProcessor()) + + assert result.process_mode == "C41" + assert result.render_intent == "print" + assert result.auto_exposure is True + + def test_records_the_negpy_version(self) -> None: + import negpy + + rgb = _negative_like(seed=3) + result = positive.render_positive(rgb, processor=ImageProcessor()) + + assert result.negpy_version == negpy.__version__ + + def test_actually_inverts_not_a_passthrough(self) -> None: + """A scanner-linear negative and its rendered positive must not be the + same array -- catches a wrapper that accidentally short-circuits the + engine (e.g. a stubbed process()) and returns the source untouched.""" + rgb = _negative_like(seed=4) + result = positive.render_positive(rgb, processor=ImageProcessor()) + + assert not np.array_equal(result.rgb, rgb) + + def test_two_different_frames_on_one_processor_render_differently(self) -> None: + """Regression guard: render_positive must pass a fresh source_hash per + call. DarkroomEngine's stage cache reuses a prior render whenever + source_hash *and* the settings hash both match, and every Tier-3 call + shares the same stock WorkspaceConfig -- a reused/stable hash across + two different frames would silently hand back the first frame's + pixels for the second. Two distinct frames rendered on the *same* + ImageProcessor (so its cache is actually in play) must differ.""" + processor = ImageProcessor() + first = positive.render_positive(_negative_like(seed=5), processor=processor) + second = positive.render_positive(_negative_like(seed=6), processor=processor) + + assert not np.array_equal(first.rgb, second.rgb) From 694d696fd8951b9f6ecd7ec5e05d7d5e5f84a067 Mon Sep 17 00:00:00 2001 From: Rohan Pandula Date: Sun, 19 Jul 2026 02:08:33 -0700 Subject: [PATCH 15/48] Extend RollScanSettings with the three output-tier flags write_unrepaired, write_repaired and write_positive select which of the three output tiers a batch scan writes, independently of one another rather than as a single three-way choice. write_unrepaired defaults on: it is the archival master and the only tier the scanner itself can reproduce. repair_mode carries the Tier-2 variant (exact or hybrid) once a repair engine is registered. --- negpy/infrastructure/roll/settings.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/negpy/infrastructure/roll/settings.py b/negpy/infrastructure/roll/settings.py index e27799dc..9b88ad3f 100644 --- a/negpy/infrastructure/roll/settings.py +++ b/negpy/infrastructure/roll/settings.py @@ -4,6 +4,8 @@ from dataclasses import dataclass +from negpy.infrastructure.roll.repair import RepairMode + @dataclass(frozen=True) class RollScanSettings: @@ -15,11 +17,26 @@ class RollScanSettings: approvals are not persisted here at all -- they live on the open coolscanpy `Roll` for as long as it stays open, and `preview()` resets them on its own the moment it re-reads the transport. + + `write_unrepaired`, `write_repaired` and `write_positive` select which of + the three output tiers a batch scan writes (see + `negpy.services.roll.service.write_frame`); any combination is valid, and + they are independent, not a single three-way choice. `write_unrepaired` + defaults on because it is the archival master and the only tier the + scanner itself can reproduce -- the other two default off since they are + derived, regenerable, and (for `write_positive`) subject to a rendering + pipeline that is still being tuned. `repair_mode` governs Tier 2 (and, + through it, Tier 3) whenever a repair engine is registered; see + `negpy.infrastructure.roll.repair`. """ last_device_id: str = "" output_folder: str = "" filename_pattern: str = '{{ date }}_{{ "%03d" % seq }}' + write_unrepaired: bool = True + write_repaired: bool = False + write_positive: bool = False + repair_mode: str = RepairMode.EXACT.value @classmethod def defaults(cls) -> "RollScanSettings": From e8924b6ada66ba6b126821ec8eacaf9ac1c727b8 Mon Sep 17 00:00:00 2001 From: Rohan Pandula Date: Sun, 19 Jul 2026 02:08:51 -0700 Subject: [PATCH 16/48] Write captured frames across three independently-selectable tiers write_frame() now writes up to three output tiers per frame instead of always writing the one it used to: unrepaired (Tier 1, the archival master), repaired (Tier 2, via the repair seam), and positive (Tier 3, Tier 2 inverted). Any combination is valid. Tier 3 always derives from Tier 2's in-memory result, computing repair whenever either flag needs it even if Tier 2 itself isn't written to disk, and never from a Tier 1 or Tier 2 file already on disk. Tier 2 and Tier 3 degrade with a status in the receipt instead of raising when they can't be produced -- no repair engine registered, no infrared plane to guide one, the engine or the render failing -- so a problem in a derived tier never costs the archival capture. Naming follows the existing _IR-sidecar convention: _repaired.tif plus _repaired_IR.tif for Tier 2 (the infrared sidecar is Tier 1's own, retained unchanged since repair consumes it rather than producing a repaired version of it), _positive.tif for Tier 3. The receipt's existing top-level fields (coolscanpy's own per-frame receipt) are unchanged; a new "outputs" key records what was written per tier, and for Tier 2/3 the provenance needed to audit or regenerate them -- engine name, version and mode for Tier 2, and render intent, process mode, auto-exposure setting and the repair that fed it for Tier 3. RollFrameOutput's rgb_path is now optional to match: Tier 1 can be turned off same as the other two. --- negpy/services/roll/service.py | 185 +++++++++++++++++-- tests/roll/test_service.py | 316 +++++++++++++++++++++++++++++++++ 2 files changed, 484 insertions(+), 17 deletions(-) diff --git a/negpy/services/roll/service.py b/negpy/services/roll/service.py index 13d64a5c..2493b8a3 100644 --- a/negpy/services/roll/service.py +++ b/negpy/services/roll/service.py @@ -13,6 +13,15 @@ presence check re-exported from that module, and every other method only reaches coolscanpy (transitively, through `coolscanpy_roll`) once actually called. + +`write_frame` writes a captured frame across up to three output tiers -- +unrepaired, repaired, positive -- each independently selectable and each +derived from the one before it. See its docstring for the tier definitions, +the write order, and how a tier that cannot be produced degrades instead of +losing the tiers that still can be. Tier 2 (repaired) is backed by +`negpy.infrastructure.roll.repair`, an optional-engine seam with nothing +registered in this codebase; Tier 3 (positive) is backed by +`negpy.services.roll.positive`, which reuses NegPy's own rendering engine. """ from __future__ import annotations @@ -28,7 +37,11 @@ import tifffile from negpy.infrastructure.roll import coolscanpy_roll +from negpy.infrastructure.roll import repair as roll_repair +from negpy.infrastructure.roll.repair import RepairMode from negpy.kernel.system.logging import get_logger +from negpy.services.rendering.image_processor import ImageProcessor +from negpy.services.roll import positive as roll_positive from negpy.services.scanning.templating import render_scan_filename if TYPE_CHECKING: @@ -50,11 +63,17 @@ class RollScanningError(RuntimeError): @dataclasses.dataclass(frozen=True) class RollFrameOutput: - """Where one scanned frame's files landed on disk.""" + """Where one scanned frame's files landed on disk, one pair of fields per + tier. A tier that was not selected, or could not be produced, leaves its + field(s) `None` -- the receipt at `receipt_path` records which of those + two it was and, for a tier that did write, its provenance.""" slot: int - rgb_path: str - ir_path: str | None + rgb_path: str | None # Tier 1 (unrepaired): RGB plane + ir_path: str | None # Tier 1 (unrepaired): infrared plane + repaired_rgb_path: str | None # Tier 2 (repaired): RGB plane + repaired_ir_path: str | None # Tier 2 (repaired): infrared plane (Tier 1's own, retained unchanged) + positive_path: str | None # Tier 3 (positive) receipt_path: str @@ -68,6 +87,9 @@ class RollScanningService: def __init__(self) -> None: self._roll: coolscanpy_roll.RollHandle | None = None + # Built lazily, on the first frame that actually needs Tier 3 -- see + # `_get_image_processor()`. Most batches never touch it. + self._image_processor: ImageProcessor | None = None # -- device / roll lifecycle ----------------------------------------- @@ -121,38 +143,167 @@ def safe_stop(self) -> None: # -- writing ------------------------------------------------------------- - def write_frame(self, frame: "coolscanpy.Frame", output_folder: str, filename_pattern: str) -> RollFrameOutput: - """Write one scanned `Frame` to disk: a 16-bit TIFF master, an `_IR` - TIFF sidecar when the frame carries an infrared plane (matching - `writer.write_tiff_16bit`'s own `_IR.tif` convention), and - a `_receipt.json` sidecar holding the full `Receipt`. + def write_frame( + self, + frame: "coolscanpy.Frame", + output_folder: str, + filename_pattern: str, + *, + write_unrepaired: bool = True, + write_repaired: bool = False, + write_positive: bool = False, + repair_mode: str = RepairMode.EXACT.value, + ) -> RollFrameOutput: + """Write one scanned `Frame` to disk across up to three tiers, plus a + `_receipt.json` sidecar. Each tier is independently selectable; any + combination of the three flags is valid. + + Tier 1, unrepaired (`write_unrepaired`): the frame exactly as + captured -- a 16-bit RGB TIFF plus an `_IR` sidecar when the frame + carries an infrared plane, matching `writer.write_tiff_16bit`'s own + `_IR.tif` convention. This is the archival master: the + only tier the scanner itself can reproduce. A failure writing it is + allowed to raise, unlike Tier 2/3 below. + + Tier 2, repaired (`write_repaired`): Tier 1 with infrared-guided + dust/scratch repair applied through `negpy.infrastructure.roll + .repair`, written as `_repaired.tif` plus a + `_repaired_IR.tif` sidecar. That sidecar is Tier 1's own + infrared plane, unchanged -- repair consumes infrared to find + defects, it does not produce a repaired version of it, and keeping + the original lets a later re-repair under a different mode start + from the same evidence Tier 1 captured. + + Tier 3, positive (`write_positive`): Tier 2 inverted through + `negpy.services.roll.positive`, written as `_positive.tif`. + Requested independently of `write_repaired`, but always derived from + Tier 2's in-memory result -- repair runs whenever either flag needs + it, so a positive is never silently missing the repair pass a caller + asked for, and never from a Tier 1 or Tier 2 file already on disk + (an upper tier is never derived from a lower tier's file, only from + the in-memory data the lower tier is also optionally written from). + + Tier 2 and Tier 3 degrade instead of raising when they cannot be + produced -- no repair engine registered, no infrared plane to guide + one, the engine itself fails, or the render fails -- recording why in + the receipt and leaving the corresponding `RollFrameOutput` path(s) + `None`. Tier 1 always writes regardless (or Tier 2, for a Tier-3 + failure): losing the archival capture to a problem in a derived, + regenerable tier is exactly what this must not do. `filename_pattern` is the same Jinja2 template the plain scan path uses (variables: `date`, `seq`), but `seq` is seeded from the frame's physical slot number rather than probed for the next free name: a roll slot is already a stable identity, so re-scanning slot 5 replaces slot 5's old files instead of piling up a `..._002` beside - them. + them, for every tier. `frame.ir_validity` (the per-pixel IR confidence mask) is not persisted here -- it isn't part of NegPy's existing scan-output convention and nothing downstream reads it yet. """ + try: + resolved_repair_mode = RepairMode(repair_mode) + except ValueError: + resolved_repair_mode = RepairMode.EXACT + os.makedirs(output_folder, exist_ok=True) date_str = _date.today().strftime("%Y%m%d") basename = render_scan_filename(filename_pattern, date_str, frame.slot) base_path = os.path.join(output_folder, basename) - rgb_path = _atomic_write_tiff(base_path + ".tif", frame.rgb, photometric="rgb") - - ir_path = None - if frame.ir is not None: - ir_path = _atomic_write_tiff(base_path + "_IR.tif", frame.ir, photometric="minisblack") + outputs: dict = {} + + # -- Tier 1: unrepaired ----------------------------------------------- + rgb_path = ir_path = None + if write_unrepaired: + rgb_path = _atomic_write_tiff(base_path + ".tif", frame.rgb, photometric="rgb") + if frame.ir is not None: + ir_path = _atomic_write_tiff(base_path + "_IR.tif", frame.ir, photometric="minisblack") + outputs["unrepaired"] = {"written": True, "rgb_path": rgb_path, "ir_path": ir_path} + else: + outputs["unrepaired"] = {"written": False, "status": "not selected"} + + # -- Tier 2: repaired (also feeds Tier 3, even when not itself written) -- + outputs["repaired"] = {"written": False, "status": "not selected"} + repair_result: roll_repair.RepairResult | None = None + repaired_rgb_path = repaired_ir_path = None + if write_repaired or write_positive: + if frame.ir is None: + outputs["repaired"] = {"written": False, "status": "unavailable: frame has no infrared plane to guide repair"} + elif not roll_repair.available(): + outputs["repaired"] = {"written": False, "status": "unavailable: no dust-repair engine registered"} + else: + try: + repair_result = roll_repair.repair(frame.rgb, frame.ir, resolved_repair_mode) + except Exception as error: + outputs["repaired"] = {"written": False, "status": f"repair failed: {error}"} + else: + entry = { + "engine": repair_result.engine, + "engine_version": repair_result.engine_version, + "mode": str(repair_result.mode), + } + if write_repaired: + repaired_rgb_path = _atomic_write_tiff(base_path + "_repaired.tif", repair_result.rgb, photometric="rgb") + repaired_ir_path = _atomic_write_tiff(base_path + "_repaired_IR.tif", frame.ir, photometric="minisblack") + entry.update(written=True, rgb_path=repaired_rgb_path, ir_path=repaired_ir_path) + else: + entry.update(written=False, status="not selected (computed in memory for the positive)") + outputs["repaired"] = entry + + # -- Tier 3: positive ----------------------------------------------- + outputs["positive"] = {"written": False, "status": "not selected"} + positive_path = None + if write_positive: + if repair_result is None: + tier2_status = outputs["repaired"].get("status", "unavailable") + outputs["positive"] = {"written": False, "status": f"unavailable: Tier 2 (repaired) could not be produced ({tier2_status})"} + elif not roll_positive.available(): + outputs["positive"] = {"written": False, "status": "unavailable: inversion path not available"} + else: + try: + result = roll_positive.render_positive(repair_result.rgb, processor=self._get_image_processor()) + except Exception as error: + outputs["positive"] = {"written": False, "status": f"inversion failed: {error}"} + else: + positive_path = _atomic_write_tiff(base_path + "_positive.tif", result.rgb, photometric="rgb") + outputs["positive"] = { + "written": True, + "rgb_path": positive_path, + "inversion_path": "negpy.services.rendering.image_processor.ImageProcessor.run_pipeline", + "render_intent": result.render_intent, + "process_mode": result.process_mode, + "auto_exposure": result.auto_exposure, + "negpy_version": result.negpy_version, + "repair_engine": repair_result.engine, + "repair_engine_version": repair_result.engine_version, + "repair_mode": str(repair_result.mode), + } receipt_path = base_path + "_receipt.json" - _atomic_write_json(receipt_path, dataclasses.asdict(frame.receipt)) - - return RollFrameOutput(slot=frame.slot, rgb_path=rgb_path, ir_path=ir_path, receipt_path=receipt_path) + receipt_payload = dataclasses.asdict(frame.receipt) + receipt_payload["outputs"] = outputs + _atomic_write_json(receipt_path, receipt_payload) + + return RollFrameOutput( + slot=frame.slot, + rgb_path=rgb_path, + ir_path=ir_path, + repaired_rgb_path=repaired_rgb_path, + repaired_ir_path=repaired_ir_path, + positive_path=positive_path, + receipt_path=receipt_path, + ) + + def _get_image_processor(self) -> ImageProcessor: + """Built on first use and reused across a batch: its constructor + probes for GPU acceleration, worth paying once per service instance + rather than once per frame, even though Tier-3 rendering itself + always runs on the CPU engine (see `positive.render_positive`).""" + if self._image_processor is None: + self._image_processor = ImageProcessor() + return self._image_processor # -- internals ----------------------------------------------------------- diff --git a/tests/roll/test_service.py b/tests/roll/test_service.py index df07168b..cb3897f1 100644 --- a/tests/roll/test_service.py +++ b/tests/roll/test_service.py @@ -161,3 +161,319 @@ def test_creates_output_folder(self, fake_coolscanpy, tmp_path) -> None: output = service.write_frame(frame, str(nested), '{{ "%03d" % seq }}') assert os.path.exists(output.rgb_path) + + +def _tier_frame(fake_coolscanpy, *, slot=11, ir=True, seed=0): + """A frame with real per-pixel variance (not a flat fill) so a rendered + Tier-3 positive has something non-degenerate to measure -- red-biased + like a plausible C41 negative, matching tests/roll/test_positive.py's + own synthetic data.""" + rng = np.random.default_rng(seed) + shape = (24, 32) + rgb = np.zeros((*shape, 3), dtype=np.uint16) + rgb[..., 0] = rng.integers(40000, 60000, size=shape) + rgb[..., 1] = rng.integers(25000, 45000, size=shape) + rgb[..., 2] = rng.integers(15000, 35000, size=shape) + ir_plane = rng.integers(0, 65535, size=shape, dtype=np.uint16) if ir else None + receipt = fake_coolscanpy.Receipt(version=1, slot=slot, dpi=4000, depth=16, device_id="usb:1:2", transport_smear_verdict="clean") + return fake_coolscanpy.Frame(slot=slot, rgb=rgb, ir=ir_plane, ir_validity=None, receipt=receipt) + + +class TestThreeTierWriting: + """`write_frame`'s three independently-selectable output tiers: unrepaired + (Tier 1), repaired (Tier 2, needs a registered repair engine), and + positive (Tier 3, always derived from Tier 2's in-memory result). Naming, + receipt provenance, and every degrade path get covered here; the plain + default-settings behavior (Tier 1 only) is already covered above by + TestWriteFrame, unchanged.""" + + def _receipt(self, path: str) -> dict: + with open(path) as fh: + return json.load(fh) + + # -- selecting nothing -------------------------------------------------- + + def test_no_tier_selected_writes_only_the_receipt(self, fake_coolscanpy, tmp_path) -> None: + frame = _tier_frame(fake_coolscanpy) + service = RollScanningService() + + output = service.write_frame( + frame, str(tmp_path), '{{ "%03d" % seq }}', write_unrepaired=False, write_repaired=False, write_positive=False + ) + + assert (output.rgb_path, output.ir_path, output.repaired_rgb_path, output.repaired_ir_path, output.positive_path) == ( + None, + None, + None, + None, + None, + ) + assert os.path.exists(output.receipt_path) + payload = self._receipt(output.receipt_path) + assert payload["outputs"]["unrepaired"] == {"written": False, "status": "not selected"} + assert payload["outputs"]["repaired"] == {"written": False, "status": "not selected"} + assert payload["outputs"]["positive"] == {"written": False, "status": "not selected"} + + # -- Tier 2 without a registered engine ---------------------------------- + + def test_repaired_without_an_engine_degrades_but_unrepaired_still_writes(self, fake_coolscanpy, tmp_path) -> None: + frame = _tier_frame(fake_coolscanpy) + service = RollScanningService() + + output = service.write_frame(frame, str(tmp_path), '{{ "%03d" % seq }}', write_unrepaired=True, write_repaired=True) + + assert output.rgb_path is not None and os.path.exists(output.rgb_path) + assert output.repaired_rgb_path is None + assert output.repaired_ir_path is None + payload = self._receipt(output.receipt_path) + assert payload["outputs"]["unrepaired"]["written"] is True + assert payload["outputs"]["repaired"] == {"written": False, "status": "unavailable: no dust-repair engine registered"} + + def test_positive_without_an_engine_degrades_too_since_it_needs_tier_2(self, fake_coolscanpy, tmp_path) -> None: + frame = _tier_frame(fake_coolscanpy) + service = RollScanningService() + + output = service.write_frame(frame, str(tmp_path), '{{ "%03d" % seq }}', write_unrepaired=True, write_positive=True) + + assert output.rgb_path is not None and os.path.exists(output.rgb_path) + assert output.positive_path is None + payload = self._receipt(output.receipt_path) + assert payload["outputs"]["unrepaired"]["written"] is True + assert payload["outputs"]["positive"]["written"] is False + assert "Tier 2" in payload["outputs"]["positive"]["status"] + assert "no dust-repair engine registered" in payload["outputs"]["positive"]["status"] + + # -- Tier 2 with a registered engine ------------------------------------- + + def test_repaired_writes_rgb_and_retains_original_ir(self, fake_coolscanpy, fake_repair_engine, tmp_path) -> None: + fake_repair_engine.transform = lambda rgb: np.clip(rgb.astype(np.int32) + 1000, 0, 65535).astype(np.uint16) + frame = _tier_frame(fake_coolscanpy) + service = RollScanningService() + + output = service.write_frame(frame, str(tmp_path), '{{ "%03d" % seq }}', write_unrepaired=True, write_repaired=True) + + assert output.repaired_rgb_path is not None + assert output.repaired_rgb_path.endswith("_repaired.tif") + readback = tifffile.imread(output.repaired_rgb_path) + np.testing.assert_array_equal(readback, fake_repair_engine.transform(frame.rgb)) + assert not np.array_equal(readback, frame.rgb) # actually repaired, not a copy + + assert output.repaired_ir_path is not None + assert output.repaired_ir_path.endswith("_repaired_IR.tif") + ir_readback = tifffile.imread(output.repaired_ir_path) + np.testing.assert_array_equal(ir_readback, frame.ir) # Tier 1's own IR, unchanged + + def test_repaired_naming_does_not_collide_with_unrepaired(self, fake_coolscanpy, fake_repair_engine, tmp_path) -> None: + frame = _tier_frame(fake_coolscanpy) + service = RollScanningService() + + output = service.write_frame(frame, str(tmp_path), '{{ "%03d" % seq }}', write_unrepaired=True, write_repaired=True) + + paths = {output.rgb_path, output.ir_path, output.repaired_rgb_path, output.repaired_ir_path} + assert len(paths) == 4 # four distinct files + assert all(os.path.exists(p) for p in paths) + + def test_repaired_receipt_records_engine_provenance(self, fake_coolscanpy, fake_repair_engine, tmp_path) -> None: + frame = _tier_frame(fake_coolscanpy) + service = RollScanningService() + + output = service.write_frame( + frame, str(tmp_path), '{{ "%03d" % seq }}', write_unrepaired=False, write_repaired=True, repair_mode="hybrid" + ) + + payload = self._receipt(output.receipt_path) + entry = payload["outputs"]["repaired"] + assert entry["written"] is True + assert entry["engine"] == "test-repair-engine" + assert entry["engine_version"] == "0.0.1-test" + assert entry["mode"] == "hybrid" + assert entry["rgb_path"] == output.repaired_rgb_path + + def test_repair_engine_failure_degrades_without_losing_unrepaired(self, fake_coolscanpy, fake_repair_engine, tmp_path) -> None: + fake_repair_engine.raise_error = RuntimeError("inpainting model unavailable") + frame = _tier_frame(fake_coolscanpy) + service = RollScanningService() + + output = service.write_frame( + frame, str(tmp_path), '{{ "%03d" % seq }}', write_unrepaired=True, write_repaired=True, write_positive=True + ) + + assert output.rgb_path is not None and os.path.exists(output.rgb_path) + assert output.repaired_rgb_path is None + assert output.positive_path is None + payload = self._receipt(output.receipt_path) + assert payload["outputs"]["unrepaired"]["written"] is True + assert "repair failed" in payload["outputs"]["repaired"]["status"] + assert "inpainting model unavailable" in payload["outputs"]["repaired"]["status"] + assert "Tier 2" in payload["outputs"]["positive"]["status"] + + def test_frame_without_infrared_degrades_repaired_and_positive(self, fake_coolscanpy, fake_repair_engine, tmp_path) -> None: + frame = _tier_frame(fake_coolscanpy, ir=False) + service = RollScanningService() + + output = service.write_frame( + frame, str(tmp_path), '{{ "%03d" % seq }}', write_unrepaired=True, write_repaired=True, write_positive=True + ) + + assert fake_repair_engine.calls == [] # never even attempted + assert output.repaired_rgb_path is None + assert output.positive_path is None + payload = self._receipt(output.receipt_path) + assert "no infrared plane" in payload["outputs"]["repaired"]["status"] + + def test_invalid_repair_mode_string_falls_back_to_exact(self, fake_coolscanpy, fake_repair_engine, tmp_path) -> None: + frame = _tier_frame(fake_coolscanpy) + service = RollScanningService() + + service.write_frame(frame, str(tmp_path), '{{ "%03d" % seq }}', write_repaired=True, repair_mode="bogus-mode") + + assert len(fake_repair_engine.calls) == 1 + assert fake_repair_engine.calls[0][2] == roll_service.RepairMode.EXACT + + def test_positive_requested_without_write_repaired_still_repairs_in_memory(self, fake_coolscanpy, fake_repair_engine, tmp_path) -> None: + frame = _tier_frame(fake_coolscanpy) + service = RollScanningService() + + output = service.write_frame(frame, str(tmp_path), '{{ "%03d" % seq }}', write_unrepaired=False, write_positive=True) + + assert len(fake_repair_engine.calls) == 1 # repair ran to feed Tier 3... + assert output.repaired_rgb_path is None # ...but Tier 2 itself was never written + assert output.positive_path is not None + payload = self._receipt(output.receipt_path) + entry = payload["outputs"]["repaired"] + assert entry["written"] is False + assert entry["status"] == "not selected (computed in memory for the positive)" + assert entry["engine"] == "test-repair-engine" # still recorded even though unwritten + + # -- Tier 3 ----------------------------------------------------------------- + + def test_positive_is_written_and_derived_from_repaired_not_raw_rgb( + self, fake_coolscanpy, fake_repair_engine, tmp_path, monkeypatch + ) -> None: + fake_repair_engine.transform = lambda rgb: np.clip(rgb.astype(np.int32) + 5000, 0, 65535).astype(np.uint16) + frame = _tier_frame(fake_coolscanpy) + service = RollScanningService() + + from negpy.services.roll import positive as roll_positive_module + + captured = {} + real_render = roll_positive_module.render_positive + + def _spy(rgb_u16, *, processor): + captured["rgb_u16"] = rgb_u16 + return real_render(rgb_u16, processor=processor) + + monkeypatch.setattr(roll_positive_module, "render_positive", _spy) + + output = service.write_frame(frame, str(tmp_path), '{{ "%03d" % seq }}', write_unrepaired=False, write_positive=True) + + np.testing.assert_array_equal(captured["rgb_u16"], fake_repair_engine.transform(frame.rgb)) + assert not np.array_equal(captured["rgb_u16"], frame.rgb) + assert output.positive_path is not None + readback = tifffile.imread(output.positive_path) + assert readback.shape == frame.rgb.shape + assert readback.dtype == np.uint16 + + def test_positive_receipt_records_inversion_and_repair_provenance(self, fake_coolscanpy, fake_repair_engine, tmp_path) -> None: + frame = _tier_frame(fake_coolscanpy) + service = RollScanningService() + + output = service.write_frame(frame, str(tmp_path), '{{ "%03d" % seq }}', write_unrepaired=False, write_positive=True) + + payload = self._receipt(output.receipt_path) + entry = payload["outputs"]["positive"] + assert entry["written"] is True + assert entry["rgb_path"] == output.positive_path + assert entry["inversion_path"] == "negpy.services.rendering.image_processor.ImageProcessor.run_pipeline" + assert entry["render_intent"] == "print" + assert entry["process_mode"] == "C41" + assert entry["auto_exposure"] is True + assert entry["negpy_version"] + assert entry["repair_engine"] == "test-repair-engine" + assert entry["repair_engine_version"] == "0.0.1-test" + assert entry["repair_mode"] == "exact" + + def test_positive_filename_has_no_infrared_companion(self, fake_coolscanpy, fake_repair_engine, tmp_path) -> None: + frame = _tier_frame(fake_coolscanpy) + service = RollScanningService() + + output = service.write_frame(frame, str(tmp_path), '{{ "%03d" % seq }}', write_unrepaired=False, write_positive=True) + + assert output.positive_path is not None + assert output.positive_path.endswith("_positive.tif") + assert os.path.exists(output.positive_path) + assert not any(name.endswith("_positive_IR.tif") for name in os.listdir(str(tmp_path))) + + def test_inversion_unavailable_degrades_positive_but_keeps_unrepaired_and_repaired( + self, fake_coolscanpy, fake_repair_engine, tmp_path, monkeypatch + ) -> None: + from negpy.services.roll import positive as roll_positive_module + + monkeypatch.setattr(roll_positive_module, "available", lambda: False) + frame = _tier_frame(fake_coolscanpy) + service = RollScanningService() + + output = service.write_frame( + frame, str(tmp_path), '{{ "%03d" % seq }}', write_unrepaired=True, write_repaired=True, write_positive=True + ) + + assert output.rgb_path is not None and os.path.exists(output.rgb_path) + assert output.repaired_rgb_path is not None and os.path.exists(output.repaired_rgb_path) + assert output.positive_path is None + payload = self._receipt(output.receipt_path) + assert payload["outputs"]["unrepaired"]["written"] is True + assert payload["outputs"]["repaired"]["written"] is True + assert payload["outputs"]["positive"] == {"written": False, "status": "unavailable: inversion path not available"} + + def test_inversion_failure_degrades_positive_only(self, fake_coolscanpy, fake_repair_engine, tmp_path, monkeypatch) -> None: + from negpy.services.roll import positive as roll_positive_module + + def _boom(rgb_u16, *, processor): + raise RuntimeError("no CPU render backend available") + + monkeypatch.setattr(roll_positive_module, "render_positive", _boom) + frame = _tier_frame(fake_coolscanpy) + service = RollScanningService() + + output = service.write_frame( + frame, str(tmp_path), '{{ "%03d" % seq }}', write_unrepaired=True, write_repaired=True, write_positive=True + ) + + assert output.rgb_path is not None and os.path.exists(output.rgb_path) + assert output.repaired_rgb_path is not None and os.path.exists(output.repaired_rgb_path) + assert output.positive_path is None + payload = self._receipt(output.receipt_path) + assert payload["outputs"]["repaired"]["written"] is True + assert "inversion failed" in payload["outputs"]["positive"]["status"] + assert "no CPU render backend available" in payload["outputs"]["positive"]["status"] + + # -- all three together, and receipt backward-compatibility ----------------- + + def test_all_three_tiers_together(self, fake_coolscanpy, fake_repair_engine, tmp_path) -> None: + frame = _tier_frame(fake_coolscanpy) + service = RollScanningService() + + output = service.write_frame( + frame, str(tmp_path), '{{ "%03d" % seq }}', write_unrepaired=True, write_repaired=True, write_positive=True + ) + + for path in (output.rgb_path, output.ir_path, output.repaired_rgb_path, output.repaired_ir_path, output.positive_path): + assert path is not None and os.path.exists(path) + assert len({output.rgb_path, output.ir_path, output.repaired_rgb_path, output.repaired_ir_path, output.positive_path}) == 5 + payload = self._receipt(output.receipt_path) + assert all(payload["outputs"][tier]["written"] for tier in ("unrepaired", "repaired", "positive")) + + def test_receipt_keeps_the_original_scan_receipt_fields_alongside_outputs(self, fake_coolscanpy, tmp_path) -> None: + """`outputs` must be additive -- write_frame's pre-tiering callers (and + TestWriteFrame's tests above) read coolscanpy's own receipt fields + (slot, dpi, transport_smear_verdict, ...) at the JSON root.""" + frame = _tier_frame(fake_coolscanpy, slot=42) + service = RollScanningService() + + output = service.write_frame(frame, str(tmp_path), '{{ "%03d" % seq }}') + + payload = self._receipt(output.receipt_path) + assert payload["slot"] == 42 + assert payload["dpi"] == 4000 + assert payload["transport_smear_verdict"] == "clean" + assert "outputs" in payload From da91f7902a06fe4080778059615e96b79c8d75bc Mon Sep 17 00:00:00 2001 From: Rohan Pandula Date: Sun, 19 Jul 2026 02:08:58 -0700 Subject: [PATCH 17/48] Thread the output-tier settings through RollWorker RollBatchScanRequest gains write_unrepaired/write_repaired/ write_positive/repair_mode, defaulting to RollScanSettings.defaults() (Tier 1 only) so a request built without naming them behaves exactly as before. run_batch_scan forwards them to write_frame(). --- negpy/desktop/workers/roll_worker.py | 20 +++++++- tests/roll/test_worker.py | 76 ++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+), 1 deletion(-) diff --git a/negpy/desktop/workers/roll_worker.py b/negpy/desktop/workers/roll_worker.py index 7ca46003..00548c87 100644 --- a/negpy/desktop/workers/roll_worker.py +++ b/negpy/desktop/workers/roll_worker.py @@ -14,6 +14,8 @@ from PyQt6.QtCore import QObject, pyqtSignal, pyqtSlot from negpy.infrastructure.roll import coolscanpy_roll +from negpy.infrastructure.roll.repair import RepairMode +from negpy.infrastructure.roll.settings import RollScanSettings from negpy.kernel.system.logging import get_logger from negpy.services.roll.service import RollFrameOutput, RollScanningService @@ -32,6 +34,14 @@ class RollBatchScanRequest: slots: tuple[int, ...] output_folder: str filename_pattern: str + # Which of the three output tiers to write -- see + # `RollScanningService.write_frame`. Defaults mirror `RollScanSettings`, + # so a request built without naming them writes Tier 1 only, matching + # this integration's behavior before Tier 2/3 existed. + write_unrepaired: bool = RollScanSettings.defaults().write_unrepaired + write_repaired: bool = RollScanSettings.defaults().write_repaired + write_positive: bool = RollScanSettings.defaults().write_positive + repair_mode: str = RepairMode.EXACT.value class RollWorker(QObject): @@ -142,7 +152,15 @@ def run_batch_scan(self, req: RollBatchScanRequest) -> None: req.slots, on_progress=lambda p: self.progress.emit(p.fraction, p.message), ): - output = self._service.write_frame(frame, req.output_folder, req.filename_pattern) + output = self._service.write_frame( + frame, + req.output_folder, + req.filename_pattern, + write_unrepaired=req.write_unrepaired, + write_repaired=req.write_repaired, + write_positive=req.write_positive, + repair_mode=req.repair_mode, + ) written.append(output) self.frame_written.emit(output) self.finished.emit(written) diff --git a/tests/roll/test_worker.py b/tests/roll/test_worker.py index c16178ab..09dac661 100644 --- a/tests/roll/test_worker.py +++ b/tests/roll/test_worker.py @@ -294,3 +294,79 @@ def test_shutdown_stops_and_closes(self, fake_coolscanpy) -> None: def test_shutdown_without_any_roll_open_does_not_raise(self) -> None: RollWorker().shutdown() + + +class TestBatchScanOutputTiers: + """RollBatchScanRequest's tier fields, forwarded to RollScanningService + .write_frame -- see tests/roll/test_service.py for the full tier-behavior + matrix (naming, receipt provenance, every degrade path); these tests only + pin the plumbing between the request and the service call.""" + + def test_request_defaults_write_only_tier_1(self) -> None: + """A request built without naming the tier fields (every pre-existing + test in this file builds one this way) matches RollScanSettings + .defaults() -- unchanged from before Tier 2/3 existed.""" + req = RollBatchScanRequest(device_id="d", slots=(1,), output_folder="/tmp/x", filename_pattern="p") + + assert req.write_unrepaired is True + assert req.write_repaired is False + assert req.write_positive is False + assert req.repair_mode == "exact" + + def test_tier_flags_are_forwarded_to_write_frame(self, fake_coolscanpy, tmp_path, monkeypatch) -> None: + _open(fake_coolscanpy, fake_coolscanpy.Roll(frames=[_frame(fake_coolscanpy, slot=1)])) + worker = RollWorker() + captured = {} + real_write_frame = worker._service.write_frame + + def _spy(frame, output_folder, filename_pattern, **kwargs): + captured.update(kwargs) + return real_write_frame(frame, output_folder, filename_pattern, **kwargs) + + monkeypatch.setattr(worker._service, "write_frame", _spy) + + worker.run_batch_scan( + RollBatchScanRequest( + device_id="ls5000-usb-001", + slots=(1,), + output_folder=str(tmp_path), + filename_pattern='{{ "%03d" % seq }}', + write_unrepaired=False, + write_repaired=True, + write_positive=True, + repair_mode="hybrid", + ) + ) + + # write_repaired/write_positive=True with no repair engine registered degrades + # gracefully (see test_service.py) rather than raising, so this exercises the + # real write_frame end to end without needing a fake engine here. + assert captured == { + "write_unrepaired": False, + "write_repaired": True, + "write_positive": True, + "repair_mode": "hybrid", + } + + def test_repaired_tier_writes_through_the_worker_with_a_registered_engine(self, fake_coolscanpy, fake_repair_engine, tmp_path) -> None: + ir = np.zeros((4, 6), dtype=np.uint16) + _open(fake_coolscanpy, fake_coolscanpy.Roll(frames=[_frame(fake_coolscanpy, slot=1, ir=ir)])) + worker = RollWorker() + written = [] + worker.frame_written.connect(written.append) + + worker.run_batch_scan( + RollBatchScanRequest( + device_id="ls5000-usb-001", + slots=(1,), + output_folder=str(tmp_path), + filename_pattern='{{ "%03d" % seq }}', + write_unrepaired=True, + write_repaired=True, + ) + ) + + assert len(written) == 1 + assert written[0].repaired_rgb_path is not None + assert os.path.exists(written[0].repaired_rgb_path) + assert fake_repair_engine.calls # the worker's request actually reached the engine From 5ade086372058038168f4a94893743fc47675265 Mon Sep 17 00:00:00 2001 From: Rohan Pandula Date: Sun, 19 Jul 2026 02:09:05 -0700 Subject: [PATCH 18/48] Add output-tier controls to CoolscanRollSidebar Three checkboxes (Unrepaired/Repaired/Positive) and a repair-mode dropdown in the Output section, seeded from and persisted through RollScanSettings the same way the folder and filename-pattern fields already are. Scanning is gated on at least one tier being selected, matching the existing device/slots/folder gates. Unchecking Unrepaired shows a warning label: it's the only tier the scanner itself can reproduce, so turning it off is a real tradeoff the UI should say out loud rather than a setting like any other. --- negpy/desktop/view/sidebar/coolscan_roll.py | 84 ++++++++++++++- tests/roll/test_sidebar.py | 107 ++++++++++++++++++++ 2 files changed, 190 insertions(+), 1 deletion(-) diff --git a/negpy/desktop/view/sidebar/coolscan_roll.py b/negpy/desktop/view/sidebar/coolscan_roll.py index ead2ab02..7b6d5f24 100644 --- a/negpy/desktop/view/sidebar/coolscan_roll.py +++ b/negpy/desktop/view/sidebar/coolscan_roll.py @@ -21,6 +21,7 @@ from PyQt6.QtGui import QColor, QIcon, QImage, QPixmap from PyQt6.QtWidgets import ( QAbstractItemView, + QCheckBox, QComboBox, QFileDialog, QFormLayout, @@ -39,6 +40,7 @@ from negpy.desktop.view.styles.templates import section_subheader from negpy.desktop.view.styles.theme import THEME from negpy.infrastructure.roll import coolscanpy_roll +from negpy.infrastructure.roll.repair import RepairMode from negpy.infrastructure.roll.settings import RollScanSettings from negpy.services.roll.service import RollFrameOutput @@ -109,6 +111,10 @@ def _update_settings_from_ui(self) -> None: last_device_id=self._current_device_id() or self._settings.last_device_id, output_folder=self.folder_edit.text().strip(), filename_pattern=self.pattern_edit.text().strip() or RollScanSettings.defaults().filename_pattern, + write_unrepaired=self.write_unrepaired_check.isChecked(), + write_repaired=self.write_repaired_check.isChecked(), + write_positive=self.write_positive_check.isChecked(), + repair_mode=self.repair_mode_combo.currentData() or RepairMode.EXACT.value, ) if updated == self._settings: return @@ -170,8 +176,57 @@ def _init_ui(self) -> None: self.pattern_edit = QLineEdit(self._settings.filename_pattern) self.pattern_edit.setToolTip('Jinja2 template. Variables: {{ date }}, {{ seq }} (the slot number).\nExample: {{ date }}_{{ "%03d" % seq }}') out_form.addRow("Filename", self.pattern_edit) + + # Three independent tiers, not a single three-way choice -- any combination is + # valid. Unrepaired defaults on (see RollScanSettings): it is the archival + # master and the only tier the scanner itself can reproduce. + tiers_row = QHBoxLayout() + self.write_unrepaired_check = QCheckBox("Unrepaired") + self.write_unrepaired_check.setChecked(self._settings.write_unrepaired) + self.write_unrepaired_check.setToolTip( + "Tier 1, the archival master: the frame exactly as captured, with no repair applied. " + "This is the only tier the scanner can reproduce -- turning it off loses data that " + "cannot be recreated from the other two tiers." + ) + self.write_repaired_check = QCheckBox("Repaired") + self.write_repaired_check.setChecked(self._settings.write_repaired) + self.write_repaired_check.setToolTip( + "Tier 2: the unrepaired capture with infrared-guided dust/scratch repair applied, " + "still scanner-linear. Needs a repair engine to be registered; degrades with a " + "status in the receipt when none is available." + ) + self.write_positive_check = QCheckBox("Positive") + self.write_positive_check.setChecked(self._settings.write_positive) + self.write_positive_check.setToolTip( + "Tier 3: the repaired capture inverted through NegPy's own rendering pipeline. " + "A regenerable preview, not an edit -- it will be superseded as the pipeline " + "improves, and needs Tier 2 to be producible first." + ) + tiers_row.addWidget(self.write_unrepaired_check) + tiers_row.addWidget(self.write_repaired_check) + tiers_row.addWidget(self.write_positive_check) + out_form.addRow("Write", tiers_row) + + self.repair_mode_combo = QComboBox() + self.repair_mode_combo.addItem("Exact", RepairMode.EXACT.value) + self.repair_mode_combo.addItem("Hybrid (inpaint severe defects)", RepairMode.HYBRID.value) + self.repair_mode_combo.setToolTip( + "Governs Tier 2, and Tier 3 through it, whenever a repair engine is registered. Hybrid " + "additionally routes severe zero-signal regions to an inpainting model and costs " + "substantially more time per frame than Exact." + ) + idx = self.repair_mode_combo.findData(self._settings.repair_mode) + if idx >= 0: + self.repair_mode_combo.setCurrentIndex(idx) + out_form.addRow("Repair mode", self.repair_mode_combo) + layout.addLayout(out_form) + self.tier_hint = QLabel("") + self.tier_hint.setStyleSheet(f"color: {_WARN_COLOR}; font-size: {THEME.font_size_small}px;") + self.tier_hint.setWordWrap(True) + layout.addWidget(self.tier_hint) + # ── PROGRESS / STATUS ──────────────────────────────── self.progress_bar = QProgressBar() self.progress_bar.setVisible(False) @@ -233,6 +288,9 @@ def _connect_signals(self) -> None: self.folder_browse.clicked.connect(self._on_browse_folder) for w in (self.folder_edit, self.pattern_edit): w.editingFinished.connect(self._update_settings_from_ui) + for cb in (self.write_unrepaired_check, self.write_repaired_check, self.write_positive_check): + cb.toggled.connect(self._update_settings_from_ui) + self.repair_mode_combo.currentIndexChanged.connect(self._update_settings_from_ui) self.preview_btn.clicked.connect(self._on_preview_clicked) self.contact_sheet.itemSelectionChanged.connect(self._on_selection_changed) self.offset_apply_btn.clicked.connect(self._on_apply_offset) @@ -419,7 +477,7 @@ def _on_scan_clicked(self) -> None: device_id = self._current_device_id() slots = self._selected_slots() output_folder = self.folder_edit.text().strip() - if not device_id or not slots or not output_folder: + if not device_id or not slots or not output_folder or not self._any_tier_selected(): return from negpy.desktop.workers.roll_worker import RollBatchScanRequest @@ -430,6 +488,10 @@ def _on_scan_clicked(self) -> None: slots=tuple(slots), output_folder=output_folder, filename_pattern=self.pattern_edit.text().strip() or RollScanSettings.defaults().filename_pattern, + write_unrepaired=self.write_unrepaired_check.isChecked(), + write_repaired=self.write_repaired_check.isChecked(), + write_positive=self.write_positive_check.isChecked(), + repair_mode=self.repair_mode_combo.currentData() or RepairMode.EXACT.value, ) self.set_scanning(True) self.controller.start_roll_scan(req) @@ -495,20 +557,40 @@ def _missing_for_preview(self) -> list[str]: m.append("select a device") return m + def _any_tier_selected(self) -> bool: + return self.write_unrepaired_check.isChecked() or self.write_repaired_check.isChecked() or self.write_positive_check.isChecked() + def _missing_for_scan(self) -> list[str]: m = list(self._missing_for_preview()) if not self._selected_slots(): m.append("select at least one slot") if not self.folder_edit.text().strip(): m.append("choose an output folder") + if not self._any_tier_selected(): + m.append("select at least one output tier") return m + def _update_tier_hint(self) -> None: + """Makes the archival tradeoff legible instead of a silent default: Tier 1 is the + only tier the scanner itself can reproduce, so turning it off is a real choice + with a real cost, not just another checkbox.""" + if not self.write_unrepaired_check.isChecked(): + self.tier_hint.setText( + "Unrepaired (Tier 1) is off. If this frame ever needs to be re-scanned, only " + "the scanner can reproduce it -- Repaired and Positive are both derived from it." + ) + self.tier_hint.setVisible(True) + else: + self.tier_hint.setText("") + self.tier_hint.setVisible(False) + def _apply_gating(self) -> None: missing_preview = self._missing_for_preview() missing_scan = self._missing_for_scan() self.preview_btn.setEnabled(not missing_preview and not self._scanning) self.scan_btn.setEnabled(not missing_scan and not self._scanning) self.safe_stop_btn.setEnabled(self._scanning) + self._update_tier_hint() if missing_scan: self.gate_hint.setText("To scan: " + ", ".join(missing_scan) + ".") self.gate_hint.setVisible(True) diff --git a/tests/roll/test_sidebar.py b/tests/roll/test_sidebar.py index 45772153..2e4248ed 100644 --- a/tests/roll/test_sidebar.py +++ b/tests/roll/test_sidebar.py @@ -301,6 +301,113 @@ class _Dev: assert w.device_combo.currentData() == "dev-9" +class TestOutputTiers: + """The three independent output-tier checkboxes and the repair-mode + combo. See tests/roll/test_service.py and test_worker.py for what each + tier actually does; these tests only cover the sidebar's own wiring.""" + + def test_sidebar_builds_with_tier_controls(self) -> None: + w = _sidebar() + for attr in ("write_unrepaired_check", "write_repaired_check", "write_positive_check", "repair_mode_combo", "tier_hint"): + assert hasattr(w, attr), attr + + def test_defaults_match_settings_defaults(self) -> None: + w = _sidebar() + assert w.write_unrepaired_check.isChecked() is True + assert w.write_repaired_check.isChecked() is False + assert w.write_positive_check.isChecked() is False + assert w.repair_mode_combo.currentData() == "exact" + assert w.tier_hint.isHidden() is True # Tier 1 is on by default -- nothing to warn about + + def test_tier_hint_shown_when_unrepaired_is_turned_off(self) -> None: + w = _sidebar() + + w.write_unrepaired_check.setChecked(False) + + assert not w.tier_hint.isHidden() + assert "Unrepaired" in w.tier_hint.text() + + def test_tier_hint_hidden_again_once_unrepaired_is_back_on(self) -> None: + w = _sidebar() + w.write_unrepaired_check.setChecked(False) + assert not w.tier_hint.isHidden() + + w.write_unrepaired_check.setChecked(True) + + assert w.tier_hint.isHidden() is True + + def test_scan_blocked_when_no_tier_is_selected(self, fake_coolscanpy, tmp_path) -> None: + w = _sidebar() + _pick_device(w) + w.folder_edit.setText(str(tmp_path)) + w._on_preview_ready([_thumb(fake_coolscanpy, 1)]) + w.contact_sheet.item(0).setSelected(True) + w.write_unrepaired_check.setChecked(False) # the only tier on by default + + assert "select at least one output tier" in w._missing_for_scan() + assert w.scan_btn.isEnabled() is False + + def test_scan_click_forwards_tier_flags_and_repair_mode(self, fake_coolscanpy, tmp_path) -> None: + w = _sidebar() + _pick_device(w, device_id="dev-1") + w.folder_edit.setText(str(tmp_path)) + w._on_preview_ready([_thumb(fake_coolscanpy, 1)]) + w.contact_sheet.item(0).setSelected(True) + w.write_unrepaired_check.setChecked(False) + w.write_repaired_check.setChecked(True) + w.write_positive_check.setChecked(True) + w.repair_mode_combo.setCurrentIndex(w.repair_mode_combo.findData("hybrid")) + + w._on_scan_clicked() + + req = w.controller.start_roll_scan.call_args[0][0] + assert req.write_unrepaired is False + assert req.write_repaired is True + assert req.write_positive is True + assert req.repair_mode == "hybrid" + + def test_scan_click_without_any_tier_selected_is_a_noop(self, fake_coolscanpy, tmp_path) -> None: + w = _sidebar() + _pick_device(w) + w.folder_edit.setText(str(tmp_path)) + w._on_preview_ready([_thumb(fake_coolscanpy, 1)]) + w.contact_sheet.item(0).setSelected(True) + w.write_unrepaired_check.setChecked(False) + + w._on_scan_clicked() + + w.controller.start_roll_scan.assert_not_called() + + def test_tier_settings_round_trip_through_the_repo(self) -> None: + w = _sidebar() + w.write_unrepaired_check.setChecked(False) + w.write_repaired_check.setChecked(True) + w.repair_mode_combo.setCurrentIndex(w.repair_mode_combo.findData("hybrid")) + + w._update_settings_from_ui() + + saved = w.controller.session.repo.save_global_setting.call_args[0] + assert saved[0] == "roll_scan_settings" + assert saved[1]["write_unrepaired"] is False + assert saved[1]["write_repaired"] is True + assert saved[1]["repair_mode"] == "hybrid" + + def test_load_settings_restores_tier_choices(self) -> None: + ctrl = MagicMock() + ctrl.session.repo.get_global_setting.return_value = { + "write_unrepaired": False, + "write_repaired": True, + "write_positive": True, + "repair_mode": "hybrid", + } + w = CoolscanRollSidebar(ctrl) + + assert w.write_unrepaired_check.isChecked() is False + assert w.write_repaired_check.isChecked() is True + assert w.write_positive_check.isChecked() is True + assert w.repair_mode_combo.currentData() == "hybrid" + + class TestActivation: def test_on_activated_requests_devices_once_devices_have_arrived(self, fake_coolscanpy) -> None: w = _sidebar() From cfb1ec52dfd6734d5cda069f18b49986475f9fd3 Mon Sep 17 00:00:00 2001 From: Rohan Pandula Date: Sun, 19 Jul 2026 02:09:15 -0700 Subject: [PATCH 19/48] Skip unwritten Tier-1 outputs when feeding roll scans to discovery rgb_path can now be None (Tier 1 turned off in favor of Tier 2/3 alone), which _on_roll_scan_finished's discovery seam didn't account for. Filters those out instead of handing NegPy's asset discovery a None path; a batch with at least one real Tier-1 output still discovers those normally. --- negpy/desktop/controller.py | 8 ++++++-- tests/test_roll_controller.py | 26 ++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/negpy/desktop/controller.py b/negpy/desktop/controller.py index 96881116..b2cc3bfb 100644 --- a/negpy/desktop/controller.py +++ b/negpy/desktop/controller.py @@ -2196,9 +2196,13 @@ def _on_roll_scan_finished(self, outputs: list) -> None: Camera Scanning routes already do. Each output is already one complete per-slot RGB TIFF (coolscanpy's roll engine writes one frame per slot, not an R/G/B triplet to merge), so unlike `_on_capture_finished` this needs no rgbscan_mode/process_mode - bookkeeping -- just discovery.""" + bookkeeping -- just discovery. + + Only Tier 1 (`rgb_path`) is discovered here, and only when it was actually written -- + the roll sidebar's output-tier setting can leave it `None`. Tier 2 and Tier 3 are not + opened as NegPy assets by this seam; see docs/COOLSCANPY_ROLL_SCANNING.md.""" self.roll_finished.emit(outputs) - rgb_paths = [o.rgb_path for o in outputs] + rgb_paths = [o.rgb_path for o in outputs if o.rgb_path] if rgb_paths: self.request_asset_discovery(rgb_paths) diff --git a/tests/test_roll_controller.py b/tests/test_roll_controller.py index 7179096d..de979ebe 100644 --- a/tests/test_roll_controller.py +++ b/tests/test_roll_controller.py @@ -45,3 +45,29 @@ def test_ir_and_receipt_paths_are_not_sent_to_discovery(): (discovered,), _kwargs = controller.request_asset_discovery.call_args assert discovered == ["frame.tif"] + + +def test_outputs_with_no_rgb_path_are_skipped_for_discovery(): + """The roll sidebar's output-tier setting can leave Tier 1 (rgb_path) + unwritten -- e.g. a positive-only scan -- in which case rgb_path is None. + Those frames have nothing NegPy can open and must not reach discovery, + but a frame that did write Tier 1 in the same batch still should.""" + controller = MagicMock() + outputs = [ + SimpleNamespace(slot=1, rgb_path=None, ir_path=None, receipt_path="a_receipt.json"), + SimpleNamespace(slot=2, rgb_path="b.tif", ir_path=None, receipt_path="b_receipt.json"), + ] + + AppController._on_roll_scan_finished(controller, outputs) + + controller.roll_finished.emit.assert_called_once_with(outputs) + controller.request_asset_discovery.assert_called_once_with(["b.tif"]) + + +def test_batch_with_no_rgb_paths_at_all_does_not_request_discovery(): + controller = MagicMock() + outputs = [SimpleNamespace(slot=1, rgb_path=None, ir_path=None, receipt_path="a_receipt.json")] + + AppController._on_roll_scan_finished(controller, outputs) + + controller.request_asset_discovery.assert_not_called() From c143c2f2a36a52035019c1526dec7696cb64ec63 Mon Sep 17 00:00:00 2001 From: Rohan Pandula Date: Sun, 19 Jul 2026 02:09:21 -0700 Subject: [PATCH 20/48] Document the three output tiers and their archival reasoning Explains Tier 1 (unrepaired, the archival master), Tier 2 (repaired, cached because it's expensive but deterministic), and Tier 3 (positive, cheap and treated as a regenerable view since NegPy's color rendering is still being tuned), the capture-then-repair-then- invert ordering, what each tier's receipt entry records, how the degrade paths behave, and the storage cost of writing every tier. --- docs/COOLSCANPY_ROLL_SCANNING.md | 129 ++++++++++++++++++++++++++----- 1 file changed, 108 insertions(+), 21 deletions(-) diff --git a/docs/COOLSCANPY_ROLL_SCANNING.md b/docs/COOLSCANPY_ROLL_SCANNING.md index 83854ff7..09a52b86 100644 --- a/docs/COOLSCANPY_ROLL_SCANNING.md +++ b/docs/COOLSCANPY_ROLL_SCANNING.md @@ -85,7 +85,14 @@ the plain-SANE scanner packages: `ScanlightSettings`. - `negpy/services/roll/service.py` defines `RollScanningService`, which orchestrates the coolscanpy device and roll lifecycle and writes results - to disk. It never imports coolscanpy itself, only the adapter above. + to disk across the three output tiers described below. It never imports + coolscanpy itself, only the adapter above. +- `negpy/infrastructure/roll/repair.py` is the Tier-2 repair engine seam: + an `available()` check and a `register_engine()` entry point, with no + engine registered by default. See "Output tiers" below. +- `negpy/services/roll/positive.py` renders Tier 3. It calls + `ImageProcessor.run_pipeline`, the same in-memory entry point NegPy's own + export pipeline uses after decoding a file, directly on a Tier-2 buffer. - `negpy/desktop/workers/roll_worker.py` defines `RollWorker`, a `QObject` moved to its own thread, mirroring `CaptureWorker`. It opens a device's roll extension lazily the first time a preview or batch scan names it, @@ -96,31 +103,102 @@ the plain-SANE scanner packages: ## What this integration writes -A batch scan produces, per slot, three files in the configured output -folder: - -- a 16-bit TIFF holding the scanner-linear RGB image -- a 16-bit TIFF holding the infrared plane, named with an `_IR` suffix - next to the RGB file, matching the suffix NegPy's plain scan writer - already uses for infrared -- a JSON file with a `_receipt` suffix, holding the full scan receipt - coolscanpy returns for that frame: exposure, clipping and focus - telemetry, transport-smear assessment, and the fingerprints the frame was - checked against +A batch scan can produce up to three tiers of output per slot, plus one +receipt. Each tier is a separate on/off setting in the sidebar, and any +combination is valid. See "Output tiers" below for what each tier is, why +the defaults are set the way they are, and what happens when a tier cannot +be produced. The filename pattern is the same Jinja2 template the plain Scan panel uses, with `date` and `seq` variables. For roll scanning, `seq` is the frame's physical slot number rather than an incrementing counter. Slot numbers are already a stable identity for a given roll, so re-scanning one bad slot overwrites that slot's old files instead of accumulating a second -copy beside them. - -Only the RGB TIFF is handed to NegPy's asset discovery once a batch scan -finishes. The IR sidecar and the receipt are written but never opened as -an asset. The infrared confidence mask coolscanpy also returns per frame -is not written to disk at all. Nothing downstream currently reads it, and -adding a file format for it before there is a consumer would be guessing -at a convention rather than following one. +copy beside them. This holds for every tier. + +Only a written Tier 1 (unrepaired) RGB TIFF is handed to NegPy's asset +discovery once a batch scan finishes. Tier 2 and Tier 3 are written to +disk when selected, but neither is opened as a NegPy asset automatically. +The receipt is not opened as an asset either. The infrared confidence mask +coolscanpy also returns per frame is not written to disk at all. Nothing +downstream currently reads it, and adding a file format for it before +there is a consumer would be guessing at a convention rather than +following one. + +## Output tiers + +Tier 1 is the unrepaired capture: the scanner-linear RGB and its aligned +infrared plane, exactly as coolscanpy returned them. This is the archival +master, and the only tier the scanner itself can reproduce. It is written +as `.tif` plus `_IR.tif`, the same infrared suffix the +plain Scan panel already uses. It defaults on. + +Tier 2 is the repaired capture: Tier 1 with infrared-guided dust and +scratch repair applied, still scanner-linear and still a negative. It is +written as `_repaired.tif` plus `_repaired_IR.tif`. +That infrared sidecar is Tier 1's own infrared plane, unchanged, not a +repaired version of it. Repair consumes infrared to find defects; it does +not produce a new infrared image. Keeping the original lets a later repair +pass, run under a different mode, start from the same evidence. + +Repair runs in one of two modes. Exact mode heals only the pixels the +infrared channel confidently flags as a defect. Hybrid mode additionally +routes severe zero-signal regions, places where the infrared channel gives +no usable reading at all, to an inpainting model. Both modes are +deterministic: the same frame and the same mode always repair to the same +result. That is what makes Tier 2 worth caching. Once a frame has been +repaired under a given mode, repairing it again produces an identical +file, so there is no need to redo the work. Exact mode is expected to take +about 10 seconds per frame. Hybrid mode is expected to take 70 to 210 +seconds per frame, since inpainting is far slower than the exact-match +heal. + +Tier 3 is the positive: Tier 2 inverted through NegPy's own +negative-to-positive rendering pipeline, the same pipeline a freshly +imported negative reaches the first time it is opened in NegPy. It is +written as `_positive.tif`. Tier 3 always derives from Tier 2's +result in memory, never from a Tier 1 or Tier 2 file already on disk, and +never from Tier 1 directly. Selecting Tier 3 without Tier 2 still runs +repair; the repaired result is just not written to disk on its own. Tier 3 +defaults off. + +Repair, when it can run, always runs before inversion: capture, then +repair, then invert. That way Tier 3 benefits from whatever Tier 2 was +able to fix, rather than inverting an uncorrected frame. + +The defaults follow from an asymmetry between the tiers. Tier 1 is the +only tier the scanner can produce, so losing it is permanent. Tier 2 is +expensive to compute but, once computed under a given mode, never needs +recomputing. Tier 3 is cheap to compute, and NegPy's color rendering is +still being tuned, so a Tier 3 file written today is expected to look +different from what the same negative renders to later. Treat a Tier 3 +file as a current preview, not as a finished edit. Tier 1 defaults on +because turning it off risks losing data that only the scanner can +reproduce. Tier 2 and Tier 3 default off because both can be regenerated +from Tier 1 at any time, once a repair engine is registered. + +No repair engine ships with this integration today. `write_frame` checks +whether one is registered before attempting Tier 2, and records a plain +status in the receipt instead of raising when none is available. Since +Tier 3 always depends on Tier 2's result, Tier 3 degrades the same way +whenever repair is unavailable, even if Tier 2 itself was not selected for +writing. Tier 1 still writes when selected, regardless of what happens to +Tier 2 or Tier 3. If NegPy's own rendering pipeline fails for any reason, +Tier 3 degrades on its own, and Tier 1 and Tier 2 are unaffected. + +The receipt records, for every tier, whether it was written, and a status +explaining why not when it was not. A successful Tier 2 write also records +the repair engine's name and version, along with which mode ran. A +successful Tier 3 write also records which rendering path produced it. It +records the process mode and render intent that were used, and whether +auto exposure was on, so a later regeneration or audit has something +concrete to compare against. A Tier 3 receipt entry also carries the Tier +2 provenance that fed it, so it stays self-contained even when Tier 2 was +not itself written to disk. + +Writing every tier is not free of storage cost. At 4000 dpi, one frame's +three tiers together take up roughly half a gigabyte on disk. A long roll +scanned at every tier adds up quickly, so plan storage accordingly. ## Error handling @@ -182,7 +260,16 @@ missing gphoto2. The panel covers device selection, a whole-roll preview rendered as a thumbnail contact sheet, a spacing-offset spinner and an approve button for whichever slot is selected, and a batch scan of every selected slot -with a progress bar and a Safe Stop button. Safe Stop calls +with a progress bar and a Safe Stop button. + +The Output section has three checkboxes, one per tier, plus a repair-mode +dropdown that governs Tier 2. Any combination of the three checkboxes is +valid, and at least one must be checked before Scan Selected enables. +Unchecking Unrepaired shows a warning below the checkboxes: Tier 1 is the +only tier the scanner itself can reproduce, so turning it off is a real +tradeoff, not just another setting. + +Safe Stop calls `Roll.safe_stop()` through the worker, which is coolscanpy's own name for this action and its own contract: the frame already in flight always finishes, and only the next one is refused. A Scan/Stop toggle button, the From 19448bd3134127a6841d5621f2da428e1c9a5e61 Mon Sep 17 00:00:00 2001 From: Rohan Pandula Date: Tue, 21 Jul 2026 09:23:10 -0700 Subject: [PATCH 21/48] Wire digital-fauxice into the roll repair seam with 285 dpi meter prepass --- docs/FAUXICE_IR_REPAIR.md | 143 ++++ negpy/infrastructure/roll/fauxice_bridge.py | 49 ++ negpy/infrastructure/roll/repair.py | 10 +- negpy/services/repair/__init__.py | 25 + .../services/repair/fauxice_hybrid_runner.py | 208 +++++ negpy/services/repair/fauxice_ir_repair.py | 611 ++++++++++++++ negpy/services/roll/service.py | 3 +- pyproject.toml | 6 + tests/repair/__init__.py | 0 tests/repair/test_fauxice_hybrid_runner.py | 264 ++++++ tests/repair/test_fauxice_ir_repair.py | 762 ++++++++++++++++++ uv.lock | 236 ++++++ 12 files changed, 2313 insertions(+), 4 deletions(-) create mode 100644 docs/FAUXICE_IR_REPAIR.md create mode 100644 negpy/infrastructure/roll/fauxice_bridge.py create mode 100644 negpy/services/repair/__init__.py create mode 100644 negpy/services/repair/fauxice_hybrid_runner.py create mode 100644 negpy/services/repair/fauxice_ir_repair.py create mode 100644 tests/repair/__init__.py create mode 100644 tests/repair/test_fauxice_hybrid_runner.py create mode 100644 tests/repair/test_fauxice_ir_repair.py diff --git a/docs/FAUXICE_IR_REPAIR.md b/docs/FAUXICE_IR_REPAIR.md new file mode 100644 index 00000000..1a67d2cc --- /dev/null +++ b/docs/FAUXICE_IR_REPAIR.md @@ -0,0 +1,143 @@ +# Optional IR dust repair (digital-fauxice) + +`negpy.services.repair` adds an optional post-import step that repairs +infrared-flagged dust and scratches in a scanned frame. It calls +[digital-fauxice](https://github.com/rohanpandula/digital-fauxice), an +independently reverse-engineered, byte-exact reproduction of the Nikon Super +Coolscan's Digital ICE processing. NegPy does not implement any dust repair +logic itself here; it hands a frame to the engine and writes back what comes +out. + +The feature is off by default and stays off unless a caller explicitly +enables it. Nothing in the normal import or render path touches it. + +## What it needs + +Digital ICE does not repair a single scan. It needs two RGBI captures of the +same physical frame: a 285 dpi prepass and a 4000 dpi main scan, with focus, +exposure, frame position, and crop held fixed between them. The prepass +carries per-frame calibration the main pass depends on. It is not a +downsampled convenience copy and cannot be reconstructed from the main scan +after the fact. + +NegPy's current scanning pipeline captures one pass per frame and keeps no +prepass. On a frame imported that way, this module reports a skipped status +rather than inventing one. The calling contract exists so that a future +acquisition path, such as one built on +[coolscanpy](https://github.com/rohanpandula/coolscanpy), can supply a real +prepass and have repair start working without any change to this module. + +## Install + +Two optional dependency groups exist in `pyproject.toml`, alongside the +existing `scanner` group that carries `python-sane`: + +``` +uv sync --group fauxice # core engine only, exact mode +uv sync --group fauxice-hybrid # adds the hybrid companion +``` + +Neither package is on PyPI. Both install from a digital-fauxice GitHub +release, the same way `python-sane` is an optional group rather than a hard +dependency: NegPy runs without either installed, and every function in this +module reports a clear status instead of raising when they are missing. + +Hybrid mode additionally needs a separately installed IOPaint 1.6.0 runtime +and the `big-lama.pt` weights, in their own virtual environment. Neither is +installed by the `fauxice-hybrid` group. See digital-fauxice's own +`hybrid/docs/hybrid-repair.md` for that setup; NegPy only points at the +paths once they exist. + +## Two modes + +The setting offers `exact` and `hybrid`. `exact` is the default. + +`exact` reproduces Nikon's own Digital ICE output value for value. The +engine's validation compared two complete frames against Nikon's real +output, 68,447,316 16-bit samples per frame, with zero mismatches. + +`hybrid` additionally routes the frame's worst damage, the regions where the +engine's own defect signal maxes out, to a LaMa inpainting model, and +composites the result back into the exact output. It needs the separate +`fauxice-hybrid` package and its IOPaint runtime. If hybrid is requested but +either is missing, or the run fails for any reason, repair falls back to +`exact` and the sidecar records why. Hybrid mode never fails the whole +repair; it degrades to exact. + +Exact is the default because of the time difference, measured by +digital-fauxice on its own validation frames on an Apple M4: + +| | Frame 1 (lightly marked) | Frame 2 (badly marked) | +|---|---:|---:| +| Exact repair, cpu-fast | about 10 s | about 10 s | +| Hybrid, whole frame | 72 s | 210 s | + +That is roughly seven times the exact path on a lightly marked frame and +twenty times on a badly marked one, because the model runs once per routed +region on CPU and the routed area tracks how damaged the frame is, not how +large it is. Over a 36-frame roll the difference is about six minutes +against something between forty minutes and two hours. The sensible pattern +is to run exact across a whole roll and reach for hybrid only on the few +frames that still show visible ICE residue afterward. + +## Progress and cancellation + +`repair_ir_dust` and `repair_frame_files` accept an optional `progress` +callback and `cancel` event, in the same shape +`ScannerService.run_scan` already uses. Both only cover the exact path: the +engine supports cooperative progress and cancellation natively there. Hybrid +mode is one blocking call to an external CLI, so `progress` is never called +during a hybrid run, and `cancel` is only checked before that call starts, +not while it runs. + +## Output and provenance + +A repair never rewrites the source master. NegPy keys stored edits by the +source file's content hash, so silently changing that file would orphan +every edit already saved against it. A successful repair instead writes a +new companion, `_FAUXICE.tif`, next to the untouched original. The +`_IR.tif` companion is only ever opened for reading. + +Every call also writes `_FAUXICE.json`, whether or not the repair +was applied, so a caller can show why. It records: + +- the status: applied, skipped, or unavailable, and a plain-text reason +- the mode requested and the mode actually used +- the engine's package name and installed version +- the compute backend requested and the one actually used +- for a hybrid run, the routing counts from the tool's own receipt (how + many regions were routed, how many pixels were synthesized, out of how + many pixels in the frame) and the SHA-256 of the disclosure mask PNG + +## Hybrid fill is disclosed, not hidden + +Hybrid mode does not claim its filled pixels are recovered film information. +They are generated by a model. Every synthesized pixel is recorded in a mask +PNG written alongside the output (`_FAUXICE_SYNTH.png`), where a +white pixel marks a synthesized one. Outside that mask, the hybrid output is +byte-identical to the exact output; digital-fauxice's own receipt proves +this pixel by pixel before NegPy ever sees the result. + +The routing that decides what gets filled is conservative on purpose and +fails closed: if the routed regions would cover more than 2% of the frame, +digital-fauxice refuses to run hybrid mode on that frame at all rather than +fill an unbounded area. On its own two validation frames the tool routed +0.07% and 1.36% of the frame respectively. + +## Limits + +This only works on color negative film with a real infrared channel +captured on the same physical frame as the visible-light scan. Traditional +silver-based black and white film and some Kodachrome stocks block infrared +light outright, so neither this module nor Nikon's own Digital ICE can +clean them; there is no IR signal to read. + +The validated profile is narrow: a Nikon Super Coolscan 5000 ED running +Digital ICE Normal at the resolution metrics used in digital-fauxice's own +validation. An acquisition outside that profile is rejected before any +processing runs. + +Until NegPy's scanning pipeline captures a paired prepass, this module has +nothing to repair on ordinary imports and will report a skipped status. The +config, the sidecar format, and the tests in this repository exist so that +gap can close without another round of adapter work. diff --git a/negpy/infrastructure/roll/fauxice_bridge.py b/negpy/infrastructure/roll/fauxice_bridge.py new file mode 100644 index 00000000..b4081b41 --- /dev/null +++ b/negpy/infrastructure/roll/fauxice_bridge.py @@ -0,0 +1,49 @@ +"""Wire digital-fauxice into the roll repair seam. + +Import this module and ``roll_repair.available()`` flips True when +``portable-digital-ice`` is installed -- same guarded-import trick +``coolscanpy_roll`` uses. Without the engine, import does nothing. + +The 285 dpi RGBI meter pass (``Frame.meter_rgbi``, 425x281x4 uint16) +rides along as ``prepass_rgbi``; fauxice needs both captures of the +same physical frame and will not run without the prepass. +""" + +from __future__ import annotations + +from negpy.infrastructure.roll import repair as roll_repair +from negpy.infrastructure.roll.repair import RepairMode, RepairResult + +try: + from negpy.services.repair.fauxice_ir_repair import ( + FauxiceRepairConfig, + engine_available, + repair_ir_dust, + ) +except ImportError: + pass +else: + if engine_available(): + + class _FauxiceEngine: + def repair(self, rgb, ir, mode: RepairMode, *, prepass_rgbi=None) -> RepairResult: + from negpy.services.repair.fauxice_ir_repair import RepairMode as FauxiceMode, RepairStatus + + fauxice_mode = FauxiceMode.HYBRID if mode is RepairMode.HYBRID else FauxiceMode.EXACT + config = FauxiceRepairConfig(enabled=True, mode=fauxice_mode) + result = repair_ir_dust( + rgb, ir, same_frame_id="roll-frame", config=config, prepass_rgbi=prepass_rgbi + ) + + if result.status is RepairStatus.APPLIED and result.repaired_rgb16 is not None: + from negpy.services.repair.fauxice_ir_repair import _engine_version + + return RepairResult( + rgb=result.repaired_rgb16, + engine="digital-fauxice", + engine_version=_engine_version() or "unknown", + mode=mode, + ) + raise RuntimeError(f"fauxice repair did not produce output: {result.reason}") + + roll_repair.register_engine(_FauxiceEngine()) diff --git a/negpy/infrastructure/roll/repair.py b/negpy/infrastructure/roll/repair.py index 7f67d646..9c203b94 100644 --- a/negpy/infrastructure/roll/repair.py +++ b/negpy/infrastructure/roll/repair.py @@ -56,7 +56,9 @@ class RepairResult: class RepairEngine(Protocol): """What a repair implementation must provide to `register_engine()`.""" - def repair(self, rgb: np.ndarray, ir: np.ndarray, mode: RepairMode) -> RepairResult: ... + def repair( + self, rgb: np.ndarray, ir: np.ndarray, mode: RepairMode, *, prepass_rgbi: np.ndarray | None = None + ) -> RepairResult: ... _engine: RepairEngine | None = None @@ -89,7 +91,9 @@ def unregister_engine() -> None: _engine = None -def repair(rgb: np.ndarray, ir: np.ndarray, mode: RepairMode) -> RepairResult: +def repair( + rgb: np.ndarray, ir: np.ndarray, mode: RepairMode, *, prepass_rgbi: np.ndarray | None = None +) -> RepairResult: """Repair one frame's RGB using its infrared plane. Raises `RuntimeError` if no engine is registered -- callers on the @@ -98,4 +102,4 @@ def repair(rgb: np.ndarray, ir: np.ndarray, mode: RepairMode) -> RepairResult: """ if _engine is None: raise RuntimeError("no dust-repair engine is registered; Tier 2 and Tier 3 are unavailable") - return _engine.repair(rgb, ir, mode) + return _engine.repair(rgb, ir, mode, prepass_rgbi=prepass_rgbi) diff --git a/negpy/services/repair/__init__.py b/negpy/services/repair/__init__.py new file mode 100644 index 00000000..9807d1e8 --- /dev/null +++ b/negpy/services/repair/__init__.py @@ -0,0 +1,25 @@ +"""Optional post-import repair steps. No Qt dependencies.""" + +from negpy.services.repair.fauxice_hybrid_runner import HybridRuntimeConfig +from negpy.services.repair.fauxice_ir_repair import ( + FauxiceRepairConfig, + FauxiceRepairResult, + RepairMode, + RepairStatus, + engine_available, + hybrid_available, + repair_frame_files, + repair_ir_dust, +) + +__all__ = [ + "FauxiceRepairConfig", + "FauxiceRepairResult", + "HybridRuntimeConfig", + "RepairMode", + "RepairStatus", + "engine_available", + "hybrid_available", + "repair_frame_files", + "repair_ir_dust", +] diff --git a/negpy/services/repair/fauxice_hybrid_runner.py b/negpy/services/repair/fauxice_hybrid_runner.py new file mode 100644 index 00000000..abb9d4a5 --- /dev/null +++ b/negpy/services/repair/fauxice_hybrid_runner.py @@ -0,0 +1,208 @@ +"""Subprocess bridge to the optional ``fauxce-hybrid`` companion tool. + +``fauxce-hybrid`` is a separate, independently optional package from the core +``portable_digital_ice`` engine (see ``fauxice_ir_repair.py``). It has no +importable "run a repair" function of its own; ``src/fauxce_hybrid/cli.py`` +in the upstream project keeps that logic private and exposes only the +``fauxce-hybrid`` console script. Shelling out to that script, exactly as its +own docs show, is therefore the calling contract, not a shortcut around one. + +The hybrid CLI needs the same paired 285 dpi prepass + 4000 dpi main RGBI +acquisition as the core engine (it runs the core engine internally to get the +``at_floor_mask`` it routes on), so it never relaxes the prepass requirement +described in ``fauxice_ir_repair.py``. + +IOPaint and the LaMa model weights are never invoked by NegPy directly. They +run inside the fauxce-hybrid subprocess, pointed at a pinned interpreter and +hash-verified weights that ``HybridRuntimeConfig`` names but never bundles. +""" + +from __future__ import annotations + +import hashlib +import json +import numpy as np +import numpy.typing as npt +from dataclasses import dataclass +from pathlib import Path +from typing import Any, Callable, Sequence + +import subprocess + + +class HybridRunError(RuntimeError): + """The fauxce-hybrid subprocess did not produce a usable result.""" + + +@dataclass(frozen=True) +class HybridRuntimeConfig: + """Where the pinned IOPaint interpreter and hash-verified weights live. + + None of these paths are discovered automatically. fauxce-hybrid's own + docs (``hybrid/docs/hybrid-repair.md`` in the digital-fauxice repository) + require the caller to install IOPaint 1.6.0 into its own virtualenv and + to supply the measured SHA-256 of ``big-lama.pt`` rather than trusting a + filename; this config just carries those caller-verified values through. + """ + + iopaint_python: Path + iopaint_executable: Path + iopaint_source_manifest_sha256: str + model_dir: Path + model_weights: Path + model_weights_sha256: str + inpaint_device: str = "cpu" + inpaint_threads: int = 1 + inpaint_seed: int = 0 + executable: str = "fauxce-hybrid" + + +@dataclass(frozen=True) +class HybridRunResult: + """What one fauxce-hybrid subprocess call produced, read back into memory. + + The mask is carried as bytes rather than a path: the CLI's ``--out`` + directory must not already exist and is treated as scratch space by this + module, so nothing durable survives the call except what the caller + persists from this result. + """ + + hybrid_rgb16: npt.NDArray[np.uint16] + synth_mask_png: bytes + synth_mask_sha256: str + synthesis_fraction: float | None + engine_version: str | None + backend_requested: str | None + backend_used: str | None + backend_selection_reason: str | None + routing_counts: dict[str, int] | None + + +def run_hybrid_repair( + main_rgbi: npt.NDArray[np.uint16], + prepass_rgbi: npt.NDArray[np.uint16], + *, + same_frame_id: str, + backend: str, + runtime: HybridRuntimeConfig, + scratch_dir: Path, + timeout_seconds: float = 1800.0, + runner: Callable[..., "subprocess.CompletedProcess[str]"] = subprocess.run, +) -> HybridRunResult: + """Run one frame through the fauxce-hybrid CLI and read its outputs back. + + ``scratch_dir`` must exist and be empty; this function creates the CLI's + ``--out`` subdirectory itself (the CLI refuses a pre-existing one). The + caller owns ``scratch_dir``'s lifetime; nothing here deletes it, so a + caller that wants ephemeral scratch space should wrap the call in its own + ``tempfile.TemporaryDirectory()``. + """ + + prepass_path = scratch_dir / "prepass.rgbi16.npy" + main_path = scratch_dir / "main.rgbi16.npy" + out_dir = scratch_dir / "out" + np.save(prepass_path, prepass_rgbi, allow_pickle=False) + np.save(main_path, main_rgbi, allow_pickle=False) + + argv: Sequence[str] = [ + runtime.executable, + "--prepass", + str(prepass_path), + "--main", + str(main_path), + "--out", + str(out_dir), + "--same-frame-id", + same_frame_id, + "--assert-focus-exposure-locked", + "--backend", + backend, + "--iopaint-python", + str(runtime.iopaint_python), + "--iopaint-executable", + str(runtime.iopaint_executable), + "--iopaint-source-manifest-sha256", + runtime.iopaint_source_manifest_sha256, + "--model-dir", + str(runtime.model_dir), + "--model-weights", + str(runtime.model_weights), + "--model-weights-sha256", + runtime.model_weights_sha256, + "--inpaint-device", + runtime.inpaint_device, + "--inpaint-threads", + str(runtime.inpaint_threads), + "--inpaint-seed", + str(runtime.inpaint_seed), + ] + try: + completed = runner(list(argv), capture_output=True, text=True, timeout=timeout_seconds) + except OSError as error: + raise HybridRunError(f"could not launch {runtime.executable!r}: {error}") from error + + if completed.returncode != 0: + detail = (completed.stderr or completed.stdout or "").strip() + raise HybridRunError(f"fauxce-hybrid exited {completed.returncode}: {detail[-2000:]}") + + hybrid_path = out_dir / "output-hybrid.rgb16.npy" + mask_path = out_dir / "synth-mask.png" + receipt_path = out_dir / "hybrid-receipt.json" + for required in (hybrid_path, mask_path, receipt_path): + if not required.is_file(): + raise HybridRunError(f"fauxce-hybrid reported success but {required.name} is missing from {out_dir}") + + hybrid_rgb16 = np.load(hybrid_path, allow_pickle=False) + mask_bytes = mask_path.read_bytes() + receipt = json.loads(receipt_path.read_text(encoding="utf-8")) + return HybridRunResult( + hybrid_rgb16=hybrid_rgb16, + synth_mask_png=mask_bytes, + synth_mask_sha256=hashlib.sha256(mask_bytes).hexdigest(), + **_receipt_fields(receipt), + ) + + +_ROUTING_COUNT_KEYS = ("final_regions", "synthesis_pixels", "frame_pixels", "at_floor_pixels") + + +def _receipt_fields(receipt: dict[str, Any]) -> dict[str, Any]: + """Pull the few fields NegPy's sidecar cares about out of hybrid-receipt.json. + + Reads defensively (``.get`` all the way down): this module is a + provenance consumer, not the receipt's verifier. ``fauxce-hybrid`` itself + is the authority on whether a given receipt is internally consistent. + + ``routing_counts`` comes from the receipt's ``routing.counts`` object + (``fauxce-hybrid-receipt-v2.schema.json``): the disclosed region/pixel + counts behind the single ``synthesis_fraction`` float, e.g. "13 regions, + 16137 pixels of 22815772". ``None`` when the receipt is missing any of + the expected keys, rather than publishing a partial count. + """ + + core = receipt.get("core", {}) if isinstance(receipt, dict) else {} + backend = core.get("backend", {}) if isinstance(core, dict) else {} + synthesis = receipt.get("synthesis", {}) if isinstance(receipt, dict) else {} + routing = receipt.get("routing", {}) if isinstance(receipt, dict) else {} + counts = routing.get("counts", {}) if isinstance(routing, dict) else {} + routing_counts = ( + {key: counts[key] for key in _ROUTING_COUNT_KEYS} + if isinstance(counts, dict) and all(key in counts for key in _ROUTING_COUNT_KEYS) + else None + ) + return { + "synthesis_fraction": synthesis.get("fraction"), + "engine_version": core.get("version"), + "backend_requested": backend.get("requested"), + "backend_used": backend.get("used"), + "backend_selection_reason": backend.get("reason"), + "routing_counts": routing_counts, + } + + +__all__ = [ + "HybridRunError", + "HybridRunResult", + "HybridRuntimeConfig", + "run_hybrid_repair", +] diff --git a/negpy/services/repair/fauxice_ir_repair.py b/negpy/services/repair/fauxice_ir_repair.py new file mode 100644 index 00000000..3b8f3070 --- /dev/null +++ b/negpy/services/repair/fauxice_ir_repair.py @@ -0,0 +1,611 @@ +"""Optional post-import infrared dust repair via the digital-fauxice engine. + +This wraps ``portable-digital-ice`` (import name ``portable_digital_ice``), a +byte-exact, independently reverse-engineered reimplementation of the Nikon +Coolscan's Digital ICE infrared dust repair. It is an optional dependency: +every function here degrades to a reported status instead of raising when the +engine, or its optional ``fauxce-hybrid`` companion, is not installed. + +Read this before wiring a caller to this module. The input contract is +narrower than "an RGB master plus its IR sidecar": + +The engine repairs one main scan using a *second*, earlier capture of the +same physical frame: a 285 dpi RGBI prepass that supplies per-frame +calibration the main pass depends on. Its own docs are explicit that this +is not a resampling convenience and cannot be reconstructed after the fact: +"Reconstructing or guessing it from the main scan would move outside the +byte-exact claim." NegPy's current scanning pipeline +(``negpy/services/scanning/writer.py``) captures one pass per frame and +never retains a prepass, so on essentially every frame NegPy has imported to +date, ``prepass_rgbi`` is unavailable and this module reports ``SKIPPED`` +rather than fabricate one. See ``docs/FAUXICE_IR_REPAIR.md`` for the +consequence of that in practice, and for the ``exact``/``hybrid`` mode +split (``fauxice_hybrid_runner.py`` covers the hybrid path). + +This is a one-shot repair, not a per-render pipeline stage: the exact CPU +reference measures roughly an hour per frame, and its compiled and GPU +backends measure roughly six to ten seconds, still far too slow to sit in +``DarkroomEngine.process()`` and re-run on every slider drag the way +``RetouchConfig.ir_dust_remove`` does. Hybrid mode is slower still (see +``docs/FAUXICE_IR_REPAIR.md``), so this module also accepts an optional +progress callback and cancellation event, mirroring the shape +``ScannerService.run_scan`` already uses for its own long operation +(``negpy/services/scanning/service.py``), so a future background worker can +wrap a repair call the same way ``ScanWorker`` wraps a scan. + +A repaired frame is published as a new companion file +(``_FAUXICE.tif``) next to the untouched original, never as an +in-place rewrite: NegPy keys stored edits by +the source file's content hash (see ``StorageRepository`` and +``negpy/services/assets/sidecar.py``), so silently rewriting the master +would orphan any edits already saved against it. The original ``_IR.tif`` +companion is only ever opened for reading here. +""" + +from __future__ import annotations + +import importlib.util +import json +import os +import subprocess +import tempfile +import threading +from dataclasses import dataclass +from enum import Enum +from pathlib import Path +from typing import Callable + +import numpy as np +import numpy.typing as npt +import tifffile + +from negpy.kernel.system.logging import get_logger +from negpy.services.repair.fauxice_hybrid_runner import ( + HybridRunError, + HybridRuntimeConfig, + run_hybrid_repair, +) + +logger = get_logger(__name__) + +ENGINE_DISTRIBUTION = "portable-digital-ice" +ENGINE_IMPORT_NAME = "portable_digital_ice" +HYBRID_IMPORT_NAME = "fauxce_hybrid" +ENGINE_HOME = "https://github.com/rohanpandula/digital-fauxice" + +IR_SIDECAR_SUFFIX = "_IR" +REPAIR_OUTPUT_SUFFIX = "_FAUXICE" +REPAIR_SIDECAR_SUFFIX = "_FAUXICE" +SYNTH_MASK_SUFFIX = "_FAUXICE_SYNTH" + +# The engine currently validates exactly one acquisition profile (see its +# own profile.py: LS5000Selector8NormalProfile). These are not arbitrary +# defaults; they are the only dpi pair it accepts today. +PREPASS_DPI = 285 +MAIN_DPI = 4000 + + +class RepairStatus(str, Enum): + APPLIED = "applied" + SKIPPED = "skipped" + UNAVAILABLE = "unavailable" + + +class RepairMode(str, Enum): + EXACT = "exact" + HYBRID = "hybrid" + + +def engine_available() -> bool: + """True if the core digital-fauxice engine is importable. + + Uses ``find_spec`` rather than a real import so an availability check + never pays the engine's import cost (or runs any stub's side effects) + just to answer "is this installed." + """ + return importlib.util.find_spec(ENGINE_IMPORT_NAME) is not None + + +def hybrid_available() -> bool: + """True if the optional fauxce-hybrid companion is importable. + + Independent of ``engine_available()``: a NegPy install can have the core + engine without the hybrid companion (the expected common case, since + hybrid also needs a separately configured, pinned IOPaint runtime that + this module never installs or manages). + """ + return importlib.util.find_spec(HYBRID_IMPORT_NAME) is not None + + +def _engine_version() -> str | None: + try: + from importlib.metadata import PackageNotFoundError, version + + return version(ENGINE_DISTRIBUTION) + except PackageNotFoundError: + return None + except Exception: + return None + + +def _format_fraction(value: float | None) -> str: + return "unknown" if value is None else f"{value:.4%}" + + +@dataclass(frozen=True) +class FauxiceRepairConfig: + """Off-by-default settings for the post-import Digital ICE repair step. + + This mirrors NegPy's existing "bool = False" feature-flag idiom (see + ``RetouchConfig.ir_dust_remove`` in ``negpy/features/retouch/models.py``) + without living inside ``WorkspaceConfig``. ``WorkspaceConfig`` holds + cheap per-render recipe parameters ``DarkroomEngine`` replays on every + slider drag; this step is an explicit, one-shot external process a + caller (a worker, a CLI, a future sidebar action) triggers deliberately, + so it gets its own config object instead. + """ + + enabled: bool = False + mode: RepairMode = RepairMode.EXACT + backend: str = "auto" + + +@dataclass(frozen=True) +class FauxiceRepairResult: + """The outcome of one ``repair_ir_dust`` call.""" + + status: RepairStatus + reason: str + mode_requested: RepairMode + mode_resolved: RepairMode | None = None + repaired_rgb16: npt.NDArray[np.uint16] | None = None + engine_version: str | None = None + backend_requested: str | None = None + backend_used: str | None = None + backend_selection_reason: str | None = None + hybrid_mask_png: bytes | None = None + hybrid_mask_sha256: str | None = None + hybrid_synthesis_fraction: float | None = None + hybrid_routing_counts: dict[str, int] | None = None + + def provenance(self) -> dict: + """JSON-serializable provenance record; the sidecar's payload.""" + + payload: dict = { + "kind": "negpy.fauxice-ir-repair", + "version": 1, + "status": self.status.value, + "reason": self.reason, + "mode_requested": self.mode_requested.value, + "mode_resolved": self.mode_resolved.value if self.mode_resolved is not None else None, + "engine": { + "package": ENGINE_IMPORT_NAME, + "version": self.engine_version, + "backend_requested": self.backend_requested, + "backend_used": self.backend_used, + "backend_selection_reason": self.backend_selection_reason, + }, + } + if self.mode_resolved is RepairMode.HYBRID: + payload["hybrid"] = { + "package": HYBRID_IMPORT_NAME, + "disclosure_mask_sha256": self.hybrid_mask_sha256, + "synthesis_fraction": self.hybrid_synthesis_fraction, + # Region/pixel counts straight from the tool's own receipt + # (routing.counts); None when the receipt did not carry the + # expected keys rather than guessing at partial data. + "routing_counts": self.hybrid_routing_counts, + } + return payload + + +def _disabled_result(mode: RepairMode) -> FauxiceRepairResult: + return FauxiceRepairResult( + status=RepairStatus.SKIPPED, + reason="disabled by configuration (FauxiceRepairConfig.enabled is False)", + mode_requested=mode, + ) + + +def _unavailable_result(mode: RepairMode) -> FauxiceRepairResult: + return FauxiceRepairResult( + status=RepairStatus.UNAVAILABLE, + reason=( + f"{ENGINE_IMPORT_NAME} is not installed; install the optional " + f"'fauxice' dependency group from {ENGINE_HOME} " + "(see docs/FAUXICE_IR_REPAIR.md)" + ), + mode_requested=mode, + ) + + +def repair_ir_dust( + rgb: npt.NDArray[np.uint16], + ir: npt.NDArray[np.uint16], + *, + same_frame_id: str, + config: FauxiceRepairConfig, + prepass_rgbi: npt.NDArray[np.uint16] | None = None, + validity_mask: npt.NDArray[np.bool_] | None = None, + hybrid_runtime: HybridRuntimeConfig | None = None, + hybrid_subprocess_runner: Callable[..., "subprocess.CompletedProcess[str]"] | None = None, + progress: Callable[[float], None] | None = None, + cancel: threading.Event | None = None, +) -> FauxiceRepairResult: + """Repair infrared-flagged dust in ``rgb`` using the digital-fauxice engine. + + ``rgb`` (HxWx3 uint16) and ``ir`` (HxW uint16) are the two halves of + NegPy's own single-pass capture (``negpy/services/scanning/writer.py`` + writes them from one ``ScanResult``), so reassembling them into one + HxWx4 RGBI array is a lossless relabeling, not a reconstruction. + + ``prepass_rgbi`` is different in kind: seeing the module docstring's + prepass note before wiring a caller to this function. Pass ``None`` + (the default) unless a real 285 dpi acquisition of the same frame is + available; passing anything reconstructed or guessed would silently + break the engine's exactness claim. + + Malformed array shapes/dtypes raise ``ValueError`` immediately (a caller + bug, not an expected runtime state). Missing preconditions (feature + disabled, engine not installed, no prepass, hybrid unavailable) never + raise; they come back as a status on the result, so a caller can show + it to a user without a try/except. + + ``progress`` and ``cancel`` follow ``ScannerService.run_scan``'s own + shape (a ``0.0``-``1.0`` callback plus a ``threading.Event``) so a + background worker can wire them the same way ``ScanWorker`` wires a + scan. Both are optional and both only cover the exact path: the engine + natively supports cooperative progress and cancellation there, but the + hybrid path is one blocking call to an external CLI, so ``progress`` is + never invoked during a hybrid run and ``cancel`` is only checked before + that call starts, not while it is running (see + ``docs/FAUXICE_IR_REPAIR.md``). + """ + + mode_requested = config.mode + + if not config.enabled: + return _disabled_result(mode_requested) + if not engine_available(): + return _unavailable_result(mode_requested) + + if rgb.ndim != 3 or rgb.shape[2] != 3 or rgb.dtype != np.uint16: + raise ValueError("rgb must be an HxWx3 uint16 array") + if ir.ndim != 2 or ir.dtype != np.uint16: + raise ValueError("ir must be an HxW uint16 array") + if ir.shape != rgb.shape[:2]: + raise ValueError(f"ir shape {ir.shape} does not match rgb shape {rgb.shape[:2]}") + if validity_mask is not None and validity_mask.shape != rgb.shape[:2]: + raise ValueError("validity_mask must match rgb's height and width") + if prepass_rgbi is not None and (prepass_rgbi.ndim != 3 or prepass_rgbi.shape[2] != 4 or prepass_rgbi.dtype != np.uint16): + raise ValueError("prepass_rgbi must be an HxWx4 uint16 array") + if not same_frame_id.strip(): + raise ValueError("same_frame_id must be non-empty") + + if prepass_rgbi is None: + return FauxiceRepairResult( + status=RepairStatus.SKIPPED, + reason=( + "no paired 285 dpi prepass acquisition available for this frame; " + "Digital ICE requires one captured alongside the main scan and " + "cannot be reconstructed from it (see docs/FAUXICE_IR_REPAIR.md)" + ), + mode_requested=mode_requested, + ) + + if cancel is not None and cancel.is_set(): + return FauxiceRepairResult( + status=RepairStatus.SKIPPED, + reason="cancelled by caller before repair started", + mode_requested=mode_requested, + ) + + main_rgbi = np.dstack([rgb, ir]) + + mode_resolved = RepairMode.EXACT + hybrid_note = "" + hybrid_outcome = None + + if mode_requested is RepairMode.HYBRID: + if not hybrid_available(): + hybrid_note = f"hybrid mode requested but {HYBRID_IMPORT_NAME} is not installed; degraded to exact repair. " + elif hybrid_runtime is None: + hybrid_note = "hybrid mode requested but no hybrid runtime is configured; degraded to exact repair. " + elif cancel is not None and cancel.is_set(): + hybrid_note = "hybrid mode requested but cancelled before the hybrid run started; degraded to exact repair. " + else: + try: + with tempfile.TemporaryDirectory(prefix="negpy-fauxice-hybrid-") as scratch: + hybrid_outcome = run_hybrid_repair( + main_rgbi, + prepass_rgbi, + same_frame_id=same_frame_id, + backend=config.backend, + runtime=hybrid_runtime, + scratch_dir=Path(scratch), + runner=hybrid_subprocess_runner or subprocess.run, + ) + mode_resolved = RepairMode.HYBRID + except HybridRunError as error: + hybrid_note = f"hybrid mode requested but the fauxce-hybrid run failed ({error}); degraded to exact repair. " + + result_kwargs: dict + if mode_resolved is RepairMode.HYBRID and hybrid_outcome is not None: + repaired = hybrid_outcome.hybrid_rgb16 + result_kwargs = { + "engine_version": hybrid_outcome.engine_version, + "backend_requested": hybrid_outcome.backend_requested, + "backend_used": hybrid_outcome.backend_used, + "backend_selection_reason": hybrid_outcome.backend_selection_reason, + "hybrid_mask_png": hybrid_outcome.synth_mask_png, + "hybrid_mask_sha256": hybrid_outcome.synth_mask_sha256, + "hybrid_synthesis_fraction": hybrid_outcome.synthesis_fraction, + "hybrid_routing_counts": hybrid_outcome.routing_counts, + } + region_note = "" + if result_kwargs["hybrid_routing_counts"] is not None: + region_note = f"; {result_kwargs['hybrid_routing_counts']['final_regions']} region(s) routed" + reason = ( + f"applied via {HYBRID_IMPORT_NAME} (engine {result_kwargs['engine_version']}, " + f"backend {result_kwargs['backend_used']}); synthesis fraction " + f"{_format_fraction(result_kwargs['hybrid_synthesis_fraction'])}{region_note}" + ) + else: + from portable_digital_ice import ProcessingCancelled + + try: + repaired, backend_info = _run_exact( + main_rgbi, + prepass_rgbi, + same_frame_id=same_frame_id, + backend=config.backend, + progress=progress, + cancel=cancel, + ) + except ProcessingCancelled as error: + return FauxiceRepairResult( + status=RepairStatus.SKIPPED, + reason=f"{hybrid_note}cancelled before completion: {error}", + mode_requested=mode_requested, + mode_resolved=None, + ) + except (ValueError, TypeError, RuntimeError) as error: + return FauxiceRepairResult( + status=RepairStatus.SKIPPED, + reason=f"{hybrid_note}engine rejected the acquisition: {error}", + mode_requested=mode_requested, + mode_resolved=None, + ) + result_kwargs = {"engine_version": _engine_version(), **backend_info} + reason = ( + f"{hybrid_note}applied via {ENGINE_IMPORT_NAME} {result_kwargs['engine_version']} (backend {result_kwargs['backend_used']})" + ) + + if validity_mask is not None: + repaired = np.where(validity_mask[:, :, None], repaired, rgb) + + return FauxiceRepairResult( + status=RepairStatus.APPLIED, + reason=reason, + mode_requested=mode_requested, + mode_resolved=mode_resolved, + repaired_rgb16=repaired, + **result_kwargs, + ) + + +def _run_exact( + main_rgbi: npt.NDArray[np.uint16], + prepass_rgbi: npt.NDArray[np.uint16], + *, + same_frame_id: str, + backend: str, + progress: Callable[[float], None] | None, + cancel: threading.Event | None, +) -> tuple[npt.NDArray[np.uint16], dict]: + """Call the installed engine directly. + + Only reached after ``engine_available()`` is true. The import is local + to this function, never at module scope, so importing this module never + requires the optional engine to be installed. + + ``progress``/``cancel`` are NegPy's own ``Callable[[float], None]`` / + ``threading.Event`` shapes; the engine's native callbacks take a + ``ProcessingProgress`` object and a zero-argument ``() -> bool`` poll + function respectively, so this is where the two conventions meet. + """ + + from portable_digital_ice import ( + AcquisitionEpoch, + ComputeBackend, + DualRGBIAcquisition, + ProcessingJob, + ProcessingMode, + RGBI16Frame, + ScannerModel, + process, + ) + + prepass_frame = RGBI16Frame(prepass_rgbi, AcquisitionEpoch.PREPASS, PREPASS_DPI, f"{same_frame_id}-prepass") + main_frame = RGBI16Frame(main_rgbi, AcquisitionEpoch.MAIN, MAIN_DPI, f"{same_frame_id}-main") + acquisition = DualRGBIAcquisition(prepass_frame, main_frame, same_frame_id) + job = ProcessingJob( + acquisition=acquisition, + scanner_model=ScannerModel.NIKON_SUPER_COOLSCAN_5000_ED, + mode=ProcessingMode.NORMAL, + selector=8, + resolution_metric=MAIN_DPI, + bit_depth=16, + focus_exposure_locked=True, + ) + + engine_progress = None + if progress is not None: + + def engine_progress(step: object) -> None: + total = getattr(step, "total", 0) or 0 + completed = getattr(step, "completed", 0) + progress(float(completed) / float(total) if total else 0.0) + + engine_cancelled = cancel.is_set if cancel is not None else None + + routed = process( + job, + backend=ComputeBackend(backend), + progress=engine_progress, + cancelled=engine_cancelled, + ) + selection = routed.selection + return routed.result.output_rgb16, { + "backend_requested": selection.requested.value, + "backend_used": selection.used.value, + "backend_selection_reason": selection.reason, + } + + +def default_ir_path(rgb_path: Path) -> Path: + """The sidecar path NegPy's own scanning writer produces: ``_IR.tif``.""" + return rgb_path.with_name(rgb_path.stem + IR_SIDECAR_SUFFIX + rgb_path.suffix) + + +def _load_array(path: Path) -> np.ndarray: + if path.suffix.lower() == ".npy": + return np.load(path, allow_pickle=False) + return np.asarray(tifffile.imread(path)) + + +def _load_validity_mask(path: Path) -> np.ndarray: + mask = _load_array(path) + if mask.ndim == 3: + mask = mask[:, :, 0] + return mask if mask.dtype == np.bool_ else mask.astype(bool) + + +def _atomic_write_bytes(path: Path, data: bytes) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + fd, tmp_name = tempfile.mkstemp(dir=str(path.parent), suffix=".part") + try: + with os.fdopen(fd, "wb") as handle: + handle.write(data) + os.replace(tmp_name, path) + except BaseException: + Path(tmp_name).unlink(missing_ok=True) + raise + + +def _atomic_write_json(path: Path, payload: dict) -> None: + _atomic_write_bytes(path, (json.dumps(payload, indent=2, default=str) + "\n").encode("utf-8")) + + +def _atomic_write_tiff(path: Path, rgb16: np.ndarray) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + fd, tmp_name = tempfile.mkstemp(dir=str(path.parent), suffix=".part.tif") + os.close(fd) + try: + tifffile.imwrite(tmp_name, rgb16, photometric="rgb", compression="lzw") + os.replace(tmp_name, path) + except BaseException: + Path(tmp_name).unlink(missing_ok=True) + raise + + +def _write_sidecar(path: Path, result: FauxiceRepairResult, *, rgb_path: Path, ir_path: Path) -> None: + payload = result.provenance() + payload["source"] = {"rgb": rgb_path.name, "ir": ir_path.name} + if result.status is RepairStatus.APPLIED: + output = {"repaired_rgb": rgb_path.stem + REPAIR_OUTPUT_SUFFIX + rgb_path.suffix} + if result.mode_resolved is RepairMode.HYBRID and result.hybrid_mask_png is not None: + output["disclosure_mask"] = rgb_path.stem + SYNTH_MASK_SUFFIX + ".png" + payload["output"] = output + _atomic_write_json(path, payload) + + +def repair_frame_files( + rgb_path: Path, + *, + config: FauxiceRepairConfig, + ir_path: Path | None = None, + prepass_path: Path | None = None, + validity_mask_path: Path | None = None, + same_frame_id: str | None = None, + hybrid_runtime: HybridRuntimeConfig | None = None, + hybrid_subprocess_runner: Callable[..., "subprocess.CompletedProcess[str]"] | None = None, + progress: Callable[[float], None] | None = None, + cancel: threading.Event | None = None, +) -> FauxiceRepairResult: + """Load a NegPy-imported RGB master plus its ``_IR`` companion and repair it. + + Always writes a provenance sidecar (``_FAUXICE.json``), even when + the result is not ``APPLIED``, so a caller can show why. Only writes the + repaired master (``_FAUXICE.tif``, and the disclosure mask PNG for + a hybrid run) when the result is ``APPLIED``; a skipped or unavailable + attempt otherwise leaves every existing file byte-for-byte untouched. + ``ir_path`` is never opened for writing. + + ``prepass_path``, if given, may be a ``.npy`` array or a TIFF holding + the paired 285 dpi acquisition (HxWx4 uint16). NegPy's own import + pipeline does not produce one today (see the module docstring), so this + parameter exists for whatever future acquisition path can supply it, and + for tests. + """ + + rgb_path = Path(rgb_path) + ir_path = Path(ir_path) if ir_path is not None else default_ir_path(rgb_path) + frame_id = same_frame_id or rgb_path.stem + sidecar_path = rgb_path.with_name(rgb_path.stem + REPAIR_SIDECAR_SUFFIX + ".json") + + if not config.enabled: + result = _disabled_result(config.mode) + elif not engine_available(): + result = _unavailable_result(config.mode) + elif not ir_path.is_file(): + result = FauxiceRepairResult( + status=RepairStatus.SKIPPED, + reason=f"no IR companion found at {ir_path.name}", + mode_requested=config.mode, + ) + else: + rgb = np.asarray(tifffile.imread(rgb_path)) + ir = np.asarray(tifffile.imread(ir_path)) + prepass = _load_array(Path(prepass_path)) if prepass_path is not None else None + validity_mask = _load_validity_mask(Path(validity_mask_path)) if validity_mask_path is not None else None + result = repair_ir_dust( + rgb, + ir, + same_frame_id=frame_id, + config=config, + prepass_rgbi=prepass, + validity_mask=validity_mask, + hybrid_runtime=hybrid_runtime, + hybrid_subprocess_runner=hybrid_subprocess_runner, + progress=progress, + cancel=cancel, + ) + + _write_sidecar(sidecar_path, result, rgb_path=rgb_path, ir_path=ir_path) + + if result.status is RepairStatus.APPLIED and result.repaired_rgb16 is not None: + output_path = rgb_path.with_name(rgb_path.stem + REPAIR_OUTPUT_SUFFIX + rgb_path.suffix) + _atomic_write_tiff(output_path, result.repaired_rgb16) + if result.hybrid_mask_png is not None: + mask_path = rgb_path.with_name(rgb_path.stem + SYNTH_MASK_SUFFIX + ".png") + _atomic_write_bytes(mask_path, result.hybrid_mask_png) + + return result + + +__all__ = [ + "ENGINE_HOME", + "ENGINE_IMPORT_NAME", + "HYBRID_IMPORT_NAME", + "FauxiceRepairConfig", + "FauxiceRepairResult", + "HybridRuntimeConfig", + "RepairMode", + "RepairStatus", + "default_ir_path", + "engine_available", + "hybrid_available", + "repair_frame_files", + "repair_ir_dust", +] diff --git a/negpy/services/roll/service.py b/negpy/services/roll/service.py index 2493b8a3..83eae583 100644 --- a/negpy/services/roll/service.py +++ b/negpy/services/roll/service.py @@ -37,6 +37,7 @@ import tifffile from negpy.infrastructure.roll import coolscanpy_roll +from negpy.infrastructure.roll import fauxice_bridge as _fauxice_bridge # noqa: F401 — registers engine on import from negpy.infrastructure.roll import repair as roll_repair from negpy.infrastructure.roll.repair import RepairMode from negpy.kernel.system.logging import get_logger @@ -235,7 +236,7 @@ def write_frame( outputs["repaired"] = {"written": False, "status": "unavailable: no dust-repair engine registered"} else: try: - repair_result = roll_repair.repair(frame.rgb, frame.ir, resolved_repair_mode) + repair_result = roll_repair.repair(frame.rgb, frame.ir, resolved_repair_mode, prepass_rgbi=getattr(frame, "meter_rgbi", None)) except Exception as error: outputs["repaired"] = {"written": False, "status": f"repair failed: {error}"} else: diff --git a/pyproject.toml b/pyproject.toml index c26a46b2..062f0040 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -60,6 +60,12 @@ coolscan-roll = [ # No SANE dependency for this workflow; see docs/COOLSCANPY_ROLL_SCANNING.md. "coolscanpy>=0.1.2", ] +fauxice = [ + "portable-digital-ice @ https://github.com/rohanpandula/digital-fauxice/releases/download/v0.3.0/portable_digital_ice-0.3.0-py3-none-any.whl", +] +fauxice-hybrid = [ + "fauxce-hybrid @ https://github.com/rohanpandula/digital-fauxice/releases/download/v0.3.0/fauxce_hybrid-0.3.0-py3-none-any.whl", +] [project.urls] Homepage = "https://github.com/marcinz606/NegPy" diff --git a/tests/repair/__init__.py b/tests/repair/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/repair/test_fauxice_hybrid_runner.py b/tests/repair/test_fauxice_hybrid_runner.py new file mode 100644 index 00000000..915f62eb --- /dev/null +++ b/tests/repair/test_fauxice_hybrid_runner.py @@ -0,0 +1,264 @@ +"""Tests for the fauxce-hybrid subprocess bridge, with a fake CLI runner. + +No real ``fauxce-hybrid`` install is required: ``run_hybrid_repair`` never +imports it, it only shells out to the console script named on +``HybridRuntimeConfig.executable``. Every test here supplies its own fake +``runner`` callable in place of ``subprocess.run`` and writes the output +files the real CLI would have written, so these tests exercise the argv +construction and output-reading contract without a pinned IOPaint runtime. +""" + +import hashlib +import json +import subprocess +from pathlib import Path + +import numpy as np +import pytest + +from negpy.services.repair.fauxice_hybrid_runner import ( + HybridRunError, + HybridRuntimeConfig, + run_hybrid_repair, +) + + +def _runtime_config(tmp_path: Path) -> HybridRuntimeConfig: + return HybridRuntimeConfig( + iopaint_python=tmp_path / "iopaint-venv" / "bin" / "python", + iopaint_executable=tmp_path / "iopaint-venv" / "bin" / "iopaint", + iopaint_source_manifest_sha256=hashlib.sha256(b"iopaint-source").hexdigest(), + model_dir=tmp_path / "lama-models", + model_weights=tmp_path / "lama-models" / "torch" / "hub" / "checkpoints" / "big-lama.pt", + model_weights_sha256=hashlib.sha256(b"big-lama-weights").hexdigest(), + ) + + +def _rgbi(height: int, width: int, base: int) -> np.ndarray: + return np.full((height, width, 4), base, dtype=np.uint16) + + +def _receipt(*, requested: str = "cpu", used: str = "cpu", fraction: float = 0.01) -> dict: + return { + "core": { + "version": "0.3.0", + "backend": {"requested": requested, "used": used, "reason": "stub receipt"}, + }, + "synthesis": {"fraction": fraction}, + "routing": { + "counts": { + "final_regions": 3, + "synthesis_pixels": 120, + "frame_pixels": 10000, + "at_floor_pixels": 500, + } + }, + } + + +def _write_success_outputs(out_dir: Path, *, hybrid_rgb16: np.ndarray, receipt: dict) -> None: + out_dir.mkdir(parents=True) + np.save(out_dir / "output-hybrid.rgb16.npy", hybrid_rgb16, allow_pickle=False) + (out_dir / "synth-mask.png").write_bytes(b"fake-png-bytes") + (out_dir / "hybrid-receipt.json").write_text(json.dumps(receipt), encoding="utf-8") + + +class _RecordingRunner: + """Fake ``subprocess.run`` that records argv and stages the CLI's outputs.""" + + def __init__(self, *, hybrid_rgb16: np.ndarray, receipt: dict, returncode: int = 0) -> None: + self._hybrid_rgb16 = hybrid_rgb16 + self._receipt = receipt + self._returncode = returncode + self.calls: list[list[str]] = [] + + def __call__(self, argv, **kwargs) -> subprocess.CompletedProcess: + self.calls.append(list(argv)) + out_index = argv.index("--out") + out_dir = Path(argv[out_index + 1]) + if self._returncode == 0: + _write_success_outputs(out_dir, hybrid_rgb16=self._hybrid_rgb16, receipt=self._receipt) + return subprocess.CompletedProcess(argv, 0, stdout="ok", stderr="") + return subprocess.CompletedProcess(argv, self._returncode, stdout="", stderr="synthetic failure") + + +class TestRunHybridRepairSuccess: + def test_reads_hybrid_output_and_mask(self, tmp_path: Path) -> None: + hybrid_rgb16 = np.full((4, 4, 3), 4242, dtype=np.uint16) + runner = _RecordingRunner(hybrid_rgb16=hybrid_rgb16, receipt=_receipt()) + + result = run_hybrid_repair( + _rgbi(4, 4, 1000), + _rgbi(4, 4, 500), + same_frame_id="frame-001", + backend="cpu", + runtime=_runtime_config(tmp_path), + scratch_dir=tmp_path, + runner=runner, + ) + + np.testing.assert_array_equal(result.hybrid_rgb16, hybrid_rgb16) + assert result.synth_mask_png == b"fake-png-bytes" + assert result.synth_mask_sha256 == hashlib.sha256(b"fake-png-bytes").hexdigest() + + def test_receipt_fields_are_extracted(self, tmp_path: Path) -> None: + hybrid_rgb16 = np.zeros((2, 2, 3), dtype=np.uint16) + receipt = _receipt(requested="auto", used="cpu-fast", fraction=0.0136) + runner = _RecordingRunner(hybrid_rgb16=hybrid_rgb16, receipt=receipt) + + result = run_hybrid_repair( + _rgbi(2, 2, 1000), + _rgbi(2, 2, 500), + same_frame_id="frame-002", + backend="auto", + runtime=_runtime_config(tmp_path), + scratch_dir=tmp_path, + runner=runner, + ) + + assert result.engine_version == "0.3.0" + assert result.backend_requested == "auto" + assert result.backend_used == "cpu-fast" + assert result.synthesis_fraction == 0.0136 + assert result.routing_counts == { + "final_regions": 3, + "synthesis_pixels": 120, + "frame_pixels": 10000, + "at_floor_pixels": 500, + } + + def test_routing_counts_none_when_receipt_omits_expected_keys(self, tmp_path: Path) -> None: + hybrid_rgb16 = np.zeros((2, 2, 3), dtype=np.uint16) + receipt = _receipt() + del receipt["routing"]["counts"]["frame_pixels"] + runner = _RecordingRunner(hybrid_rgb16=hybrid_rgb16, receipt=receipt) + + result = run_hybrid_repair( + _rgbi(2, 2, 1000), + _rgbi(2, 2, 500), + same_frame_id="frame-003", + backend="cpu", + runtime=_runtime_config(tmp_path), + scratch_dir=tmp_path, + runner=runner, + ) + + assert result.routing_counts is None + # A partial/malformed routing block must not silently degrade the + # fields this module is confident about. + assert result.synthesis_fraction == 0.01 + + def test_argv_carries_the_documented_cli_flags(self, tmp_path: Path) -> None: + hybrid_rgb16 = np.zeros((2, 2, 3), dtype=np.uint16) + runner = _RecordingRunner(hybrid_rgb16=hybrid_rgb16, receipt=_receipt()) + runtime = _runtime_config(tmp_path) + + run_hybrid_repair( + _rgbi(2, 2, 1000), + _rgbi(2, 2, 500), + same_frame_id="frame-004", + backend="cpu", + runtime=runtime, + scratch_dir=tmp_path, + runner=runner, + ) + + assert len(runner.calls) == 1 + argv = runner.calls[0] + assert argv[0] == "fauxce-hybrid" + for flag, value in ( + ("--same-frame-id", "frame-004"), + ("--backend", "cpu"), + ("--iopaint-python", str(runtime.iopaint_python)), + ("--iopaint-executable", str(runtime.iopaint_executable)), + ("--iopaint-source-manifest-sha256", runtime.iopaint_source_manifest_sha256), + ("--model-dir", str(runtime.model_dir)), + ("--model-weights", str(runtime.model_weights)), + ("--model-weights-sha256", runtime.model_weights_sha256), + ("--inpaint-device", "cpu"), + ("--inpaint-threads", "1"), + ("--inpaint-seed", "0"), + ): + assert flag in argv, f"missing {flag}" + assert argv[argv.index(flag) + 1] == value + assert "--assert-focus-exposure-locked" in argv + # A real hybrid run must not pass --no-inpaint, or the CLI would + # never touch the model and every hybrid call would silently + # degrade to routing-only. + assert "--no-inpaint" not in argv + + def test_scratch_dir_out_subdirectory_does_not_preexist(self, tmp_path: Path) -> None: + """The CLI refuses a pre-existing --out directory; this module must create it fresh.""" + hybrid_rgb16 = np.zeros((2, 2, 3), dtype=np.uint16) + + def runner(argv, **kwargs) -> subprocess.CompletedProcess: + out_dir = Path(argv[argv.index("--out") + 1]) + assert not out_dir.exists() + _write_success_outputs(out_dir, hybrid_rgb16=hybrid_rgb16, receipt=_receipt()) + return subprocess.CompletedProcess(argv, 0, stdout="", stderr="") + + scratch = tmp_path / "scratch" + scratch.mkdir() + run_hybrid_repair( + _rgbi(2, 2, 1000), + _rgbi(2, 2, 500), + same_frame_id="frame-005", + backend="cpu", + runtime=_runtime_config(tmp_path), + scratch_dir=scratch, + runner=runner, + ) + + +class TestRunHybridRepairFailure: + def test_nonzero_exit_raises_hybrid_run_error(self, tmp_path: Path) -> None: + runner = _RecordingRunner( + hybrid_rgb16=np.zeros((2, 2, 3), dtype=np.uint16), + receipt=_receipt(), + returncode=2, + ) + + with pytest.raises(HybridRunError, match="synthetic failure"): + run_hybrid_repair( + _rgbi(2, 2, 1000), + _rgbi(2, 2, 500), + same_frame_id="frame-006", + backend="cpu", + runtime=_runtime_config(tmp_path), + scratch_dir=tmp_path, + runner=runner, + ) + + def test_missing_output_file_raises_hybrid_run_error(self, tmp_path: Path) -> None: + def runner(argv, **kwargs) -> subprocess.CompletedProcess: + out_dir = Path(argv[argv.index("--out") + 1]) + out_dir.mkdir(parents=True) + # Deliberately omit synth-mask.png and hybrid-receipt.json. + np.save(out_dir / "output-hybrid.rgb16.npy", np.zeros((2, 2, 3), dtype=np.uint16)) + return subprocess.CompletedProcess(argv, 0, stdout="", stderr="") + + with pytest.raises(HybridRunError, match="missing"): + run_hybrid_repair( + _rgbi(2, 2, 1000), + _rgbi(2, 2, 500), + same_frame_id="frame-007", + backend="cpu", + runtime=_runtime_config(tmp_path), + scratch_dir=tmp_path, + runner=runner, + ) + + def test_launch_oserror_raises_hybrid_run_error(self, tmp_path: Path) -> None: + def runner(argv, **kwargs): + raise OSError("fauxce-hybrid executable not found") + + with pytest.raises(HybridRunError, match="could not launch"): + run_hybrid_repair( + _rgbi(2, 2, 1000), + _rgbi(2, 2, 500), + same_frame_id="frame-008", + backend="cpu", + runtime=_runtime_config(tmp_path), + scratch_dir=tmp_path, + runner=runner, + ) diff --git a/tests/repair/test_fauxice_ir_repair.py b/tests/repair/test_fauxice_ir_repair.py new file mode 100644 index 00000000..ad3dffc2 --- /dev/null +++ b/tests/repair/test_fauxice_ir_repair.py @@ -0,0 +1,762 @@ +"""Tests for the fauxice IR repair adapter, with stub engine/hybrid modules. + +Neither ``portable_digital_ice`` nor ``fauxce_hybrid`` is installed in this +environment (they are optional dependencies distributed from GitHub +releases, not PyPI), so every test that needs "the engine is installed" +installs a small fake module into ``sys.modules`` first via +``_install_stub_engine`` below, rather than requiring a real install. +``importlib.util.find_spec`` resolves an already-imported module through +``sys.modules``, so this is enough to make ``engine_available()`` (and a +real ``from portable_digital_ice import ...``) see the stub. + +The stub engine's ``process()`` inverts the 16-bit main RGB plane +(``65535 - value``) as its "repair": deterministic, trivially distinguished +from the untouched input, and safely within uint16 range for the small +fixture values used here. +""" + +from __future__ import annotations + +import importlib.machinery +import json +import subprocess +import sys +import threading +import types +from dataclasses import dataclass +from pathlib import Path +from typing import Callable + +import numpy as np +import pytest +import tifffile + +from negpy.services.repair.fauxice_hybrid_runner import HybridRuntimeConfig +from negpy.services.repair.fauxice_ir_repair import ( + ENGINE_IMPORT_NAME, + HYBRID_IMPORT_NAME, + FauxiceRepairConfig, + RepairMode, + RepairStatus, + engine_available, + hybrid_available, + repair_frame_files, + repair_ir_dust, +) + +INVERT = np.uint16(65535) + + +def _install_stub_engine(monkeypatch: pytest.MonkeyPatch, **process_kwargs: object) -> types.ModuleType: + """Register a minimal fake ``portable_digital_ice`` in ``sys.modules``.""" + + module = types.ModuleType(ENGINE_IMPORT_NAME) + module.__spec__ = importlib.machinery.ModuleSpec(ENGINE_IMPORT_NAME, loader=None) + + class AcquisitionEpoch: + PREPASS = "prepass" + MAIN = "main" + + class ComputeBackend(str): + def __new__(cls, value: str) -> "ComputeBackend": + return str.__new__(cls, value) + + @property + def value(self) -> str: + return str(self) + + class ProcessingMode: + NORMAL = "normal" + + class ScannerModel: + NIKON_SUPER_COOLSCAN_5000_ED = "nikon-ls5000" + + class ProcessingCancelled(RuntimeError): + pass + + @dataclass + class RGBI16Frame: + pixels: np.ndarray + epoch: str + resolution_dpi: int + evidence_id: str + + @dataclass + class DualRGBIAcquisition: + prepass: RGBI16Frame + main: RGBI16Frame + same_frame_id: str + + @dataclass + class ProcessingJob: + acquisition: DualRGBIAcquisition + scanner_model: str + mode: str + selector: int + resolution_metric: int + bit_depth: int + focus_exposure_locked: bool + + @dataclass + class _Progress: + completed: int + total: int + + @dataclass + class _Selection: + requested: object + used: object + reason: str + + @dataclass + class _Result: + output_rgb16: np.ndarray + + @dataclass + class _Routed: + result: _Result + selection: _Selection + + calls_made = process_kwargs.get("calls", None) + + def process( + job: ProcessingJob, + *, + backend, + output_rgb16=None, + progress=None, + cancelled=None, + export_diagnostics: bool = False, + ) -> _Routed: + if calls_made is not None: + calls_made.append(job) + if progress is not None: + progress(_Progress(completed=0, total=2)) + if cancelled is not None and cancelled(): + raise ProcessingCancelled("stub engine cancelled before completion") + if progress is not None: + progress(_Progress(completed=2, total=2)) + main_rgb = job.acquisition.main.pixels[:, :, :3] + output = (INVERT - main_rgb.astype(np.int64)).astype(np.uint16) + backend_value = ComputeBackend(backend) + return _Routed( + result=_Result(output_rgb16=output), + selection=_Selection(requested=backend_value, used=backend_value, reason="stub selection"), + ) + + module.AcquisitionEpoch = AcquisitionEpoch + module.ComputeBackend = ComputeBackend + module.DualRGBIAcquisition = DualRGBIAcquisition + module.ProcessingJob = ProcessingJob + module.ProcessingMode = ProcessingMode + module.RGBI16Frame = RGBI16Frame + module.ScannerModel = ScannerModel + module.ProcessingCancelled = ProcessingCancelled + module.process = process + + monkeypatch.setitem(sys.modules, ENGINE_IMPORT_NAME, module) + return module + + +def _install_stub_hybrid(monkeypatch: pytest.MonkeyPatch) -> types.ModuleType: + """Register a bare-presence fake ``fauxce_hybrid`` (only ``find_spec`` needs it).""" + + module = types.ModuleType(HYBRID_IMPORT_NAME) + module.__spec__ = importlib.machinery.ModuleSpec(HYBRID_IMPORT_NAME, loader=None) + monkeypatch.setitem(sys.modules, HYBRID_IMPORT_NAME, module) + return module + + +class _FlippingCancelEvent: + """``threading.Event`` look-alike: unset for the first N calls, set after.""" + + def __init__(self, set_after_calls: int) -> None: + self._calls = 0 + self._set_after = set_after_calls + + def is_set(self) -> bool: + self._calls += 1 + return self._calls > self._set_after + + +def _rgb(height: int, width: int, base: int) -> np.ndarray: + return np.full((height, width, 3), base, dtype=np.uint16) + + +def _ir(height: int, width: int, base: int) -> np.ndarray: + return np.full((height, width), base, dtype=np.uint16) + + +def _prepass(height: int, width: int) -> np.ndarray: + return np.full((height, width, 4), 2000, dtype=np.uint16) + + +def _hybrid_runtime(tmp_path: Path) -> HybridRuntimeConfig: + return HybridRuntimeConfig( + iopaint_python=tmp_path / "iopaint" / "bin" / "python", + iopaint_executable=tmp_path / "iopaint" / "bin" / "iopaint", + iopaint_source_manifest_sha256="a" * 64, + model_dir=tmp_path / "models", + model_weights=tmp_path / "models" / "torch" / "hub" / "checkpoints" / "big-lama.pt", + model_weights_sha256="b" * 64, + ) + + +def _hybrid_receipt(*, fraction: float = 0.02) -> dict: + return { + "core": { + "version": "0.3.0", + "backend": {"requested": "cpu", "used": "cpu", "reason": "stub"}, + }, + "synthesis": {"fraction": fraction}, + "routing": { + "counts": { + "final_regions": 2, + "synthesis_pixels": 40, + "frame_pixels": 400, + "at_floor_pixels": 60, + } + }, + } + + +def _stub_hybrid_runner(hybrid_rgb16: np.ndarray, *, receipt: dict | None = None) -> Callable: + def runner(argv, **kwargs) -> subprocess.CompletedProcess: + out_dir = Path(argv[argv.index("--out") + 1]) + out_dir.mkdir(parents=True) + np.save(out_dir / "output-hybrid.rgb16.npy", hybrid_rgb16, allow_pickle=False) + (out_dir / "synth-mask.png").write_bytes(b"mask-bytes") + (out_dir / "hybrid-receipt.json").write_text(json.dumps(receipt or _hybrid_receipt()), encoding="utf-8") + return subprocess.CompletedProcess(argv, 0, stdout="", stderr="") + + return runner + + +# --------------------------------------------------------------------------- +# Availability gating +# --------------------------------------------------------------------------- + + +class TestAvailabilityGating: + def test_engine_unavailable_by_default(self) -> None: + # Neither optional package is installed in this test environment. + assert engine_available() is False + + def test_hybrid_unavailable_by_default(self) -> None: + assert hybrid_available() is False + + def test_engine_available_with_stub_installed(self, monkeypatch: pytest.MonkeyPatch) -> None: + _install_stub_engine(monkeypatch) + assert engine_available() is True + + def test_hybrid_available_with_stub_installed(self, monkeypatch: pytest.MonkeyPatch) -> None: + _install_stub_hybrid(monkeypatch) + assert hybrid_available() is True + + def test_engine_and_hybrid_availability_are_independent(self, monkeypatch: pytest.MonkeyPatch) -> None: + _install_stub_engine(monkeypatch) + assert engine_available() is True + assert hybrid_available() is False + + +# --------------------------------------------------------------------------- +# Disabled / unavailable / no-prepass short circuits +# --------------------------------------------------------------------------- + + +class TestShortCircuits: + def test_disabled_config_skips_without_checking_engine(self) -> None: + config = FauxiceRepairConfig(enabled=False) + result = repair_ir_dust(_rgb(4, 4, 1000), _ir(4, 4, 500), same_frame_id="f1", config=config, prepass_rgbi=_prepass(4, 4)) + assert result.status is RepairStatus.SKIPPED + assert "disabled" in result.reason + + def test_engine_unavailable_reports_unavailable_status(self) -> None: + config = FauxiceRepairConfig(enabled=True) + result = repair_ir_dust(_rgb(4, 4, 1000), _ir(4, 4, 500), same_frame_id="f1", config=config, prepass_rgbi=_prepass(4, 4)) + assert result.status is RepairStatus.UNAVAILABLE + assert ENGINE_IMPORT_NAME in result.reason + + def test_missing_prepass_skips(self, monkeypatch: pytest.MonkeyPatch) -> None: + _install_stub_engine(monkeypatch) + config = FauxiceRepairConfig(enabled=True) + result = repair_ir_dust(_rgb(4, 4, 1000), _ir(4, 4, 500), same_frame_id="f1", config=config) + assert result.status is RepairStatus.SKIPPED + assert "prepass" in result.reason + + +# --------------------------------------------------------------------------- +# Validation +# --------------------------------------------------------------------------- + + +class TestValidation: + def test_malformed_rgb_shape_raises(self, monkeypatch: pytest.MonkeyPatch) -> None: + _install_stub_engine(monkeypatch) + config = FauxiceRepairConfig(enabled=True) + with pytest.raises(ValueError, match="rgb"): + repair_ir_dust( + np.zeros((4, 4), dtype=np.uint16), + _ir(4, 4, 500), + same_frame_id="f1", + config=config, + prepass_rgbi=_prepass(4, 4), + ) + + def test_mismatched_ir_shape_raises(self, monkeypatch: pytest.MonkeyPatch) -> None: + _install_stub_engine(monkeypatch) + config = FauxiceRepairConfig(enabled=True) + with pytest.raises(ValueError, match="shape"): + repair_ir_dust( + _rgb(4, 4, 1000), + _ir(8, 8, 500), + same_frame_id="f1", + config=config, + prepass_rgbi=_prepass(4, 4), + ) + + def test_empty_same_frame_id_raises(self, monkeypatch: pytest.MonkeyPatch) -> None: + _install_stub_engine(monkeypatch) + config = FauxiceRepairConfig(enabled=True) + with pytest.raises(ValueError, match="same_frame_id"): + repair_ir_dust( + _rgb(4, 4, 1000), + _ir(4, 4, 500), + same_frame_id=" ", + config=config, + prepass_rgbi=_prepass(4, 4), + ) + + def test_malformed_prepass_shape_raises(self, monkeypatch: pytest.MonkeyPatch) -> None: + _install_stub_engine(monkeypatch) + config = FauxiceRepairConfig(enabled=True) + with pytest.raises(ValueError, match="prepass_rgbi"): + repair_ir_dust( + _rgb(4, 4, 1000), + _ir(4, 4, 500), + same_frame_id="f1", + config=config, + prepass_rgbi=np.zeros((4, 4, 3), dtype=np.uint16), + ) + + +# --------------------------------------------------------------------------- +# Mode selection: exact +# --------------------------------------------------------------------------- + + +class TestExactMode: + def test_default_mode_is_exact(self) -> None: + assert FauxiceRepairConfig().mode is RepairMode.EXACT + + def test_exact_mode_applies_via_engine(self, monkeypatch: pytest.MonkeyPatch) -> None: + _install_stub_engine(monkeypatch) + config = FauxiceRepairConfig(enabled=True, mode=RepairMode.EXACT) + rgb = _rgb(4, 4, 1000) + result = repair_ir_dust(rgb, _ir(4, 4, 500), same_frame_id="f1", config=config, prepass_rgbi=_prepass(4, 4)) + assert result.status is RepairStatus.APPLIED + assert result.mode_requested is RepairMode.EXACT + assert result.mode_resolved is RepairMode.EXACT + assert result.repaired_rgb16 is not None + np.testing.assert_array_equal(result.repaired_rgb16, INVERT - rgb.astype(np.int64)) + assert result.backend_used == "auto" + + def test_exact_mode_honours_explicit_backend(self, monkeypatch: pytest.MonkeyPatch) -> None: + _install_stub_engine(monkeypatch) + config = FauxiceRepairConfig(enabled=True, mode=RepairMode.EXACT, backend="cpu") + result = repair_ir_dust(_rgb(4, 4, 1000), _ir(4, 4, 500), same_frame_id="f1", config=config, prepass_rgbi=_prepass(4, 4)) + assert result.backend_requested == "cpu" + assert result.backend_used == "cpu" + + def test_validity_mask_restricts_repair_to_valid_region(self, monkeypatch: pytest.MonkeyPatch) -> None: + _install_stub_engine(monkeypatch) + config = FauxiceRepairConfig(enabled=True) + rgb = _rgb(4, 4, 1000) + mask = np.zeros((4, 4), dtype=bool) + mask[1:3, 1:3] = True + + result = repair_ir_dust( + rgb, + _ir(4, 4, 500), + same_frame_id="f1", + config=config, + prepass_rgbi=_prepass(4, 4), + validity_mask=mask, + ) + + repaired = result.repaired_rgb16 + assert repaired is not None + # Inside the mask: repaired (inverted) value. + np.testing.assert_array_equal(repaired[1:3, 1:3], INVERT - rgb[1:3, 1:3].astype(np.int64)) + # Outside the mask: untouched original. + np.testing.assert_array_equal(repaired[0, :], rgb[0, :]) + np.testing.assert_array_equal(repaired[3, :], rgb[3, :]) + + def test_engine_rejection_reports_skipped(self, monkeypatch: pytest.MonkeyPatch) -> None: + module = _install_stub_engine(monkeypatch) + + def failing_process(*args, **kwargs): + raise ValueError("synthetic profile rejection") + + module.process = failing_process + config = FauxiceRepairConfig(enabled=True) + result = repair_ir_dust(_rgb(4, 4, 1000), _ir(4, 4, 500), same_frame_id="f1", config=config, prepass_rgbi=_prepass(4, 4)) + assert result.status is RepairStatus.SKIPPED + assert "rejected the acquisition" in result.reason + assert "synthetic profile rejection" in result.reason + + +# --------------------------------------------------------------------------- +# Progress and cancellation (exact path) +# --------------------------------------------------------------------------- + + +class TestProgressAndCancellation: + def test_progress_callback_receives_fractional_updates(self, monkeypatch: pytest.MonkeyPatch) -> None: + _install_stub_engine(monkeypatch) + config = FauxiceRepairConfig(enabled=True) + observed: list[float] = [] + + result = repair_ir_dust( + _rgb(4, 4, 1000), + _ir(4, 4, 500), + same_frame_id="f1", + config=config, + prepass_rgbi=_prepass(4, 4), + progress=observed.append, + ) + + assert result.status is RepairStatus.APPLIED + assert observed == [0.0, 1.0] + + def test_cancel_before_start_skips_without_calling_engine(self, monkeypatch: pytest.MonkeyPatch) -> None: + calls: list[object] = [] + _install_stub_engine(monkeypatch, calls=calls) + config = FauxiceRepairConfig(enabled=True) + cancel = threading.Event() + cancel.set() + + result = repair_ir_dust( + _rgb(4, 4, 1000), + _ir(4, 4, 500), + same_frame_id="f1", + config=config, + prepass_rgbi=_prepass(4, 4), + cancel=cancel, + ) + + assert result.status is RepairStatus.SKIPPED + assert "cancelled" in result.reason + assert calls == [] # the engine was never invoked + + def test_cancel_during_exact_run_skips_with_clear_reason(self, monkeypatch: pytest.MonkeyPatch) -> None: + _install_stub_engine(monkeypatch) + config = FauxiceRepairConfig(enabled=True) + # Unset for the adapter's pre-flight check, set by the time the stub + # engine polls it mid-run. + cancel = _FlippingCancelEvent(set_after_calls=1) + + result = repair_ir_dust( + _rgb(4, 4, 1000), + _ir(4, 4, 500), + same_frame_id="f1", + config=config, + prepass_rgbi=_prepass(4, 4), + cancel=cancel, + ) + + assert result.status is RepairStatus.SKIPPED + assert "cancelled before completion" in result.reason + + +# --------------------------------------------------------------------------- +# Mode selection: hybrid, and hybrid-selected-but-absent degradation +# --------------------------------------------------------------------------- + + +class TestHybridMode: + def test_hybrid_mode_applies_via_hybrid_runner(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + _install_stub_engine(monkeypatch) + _install_stub_hybrid(monkeypatch) + config = FauxiceRepairConfig(enabled=True, mode=RepairMode.HYBRID) + hybrid_output = np.full((4, 4, 3), 9999, dtype=np.uint16) + + result = repair_ir_dust( + _rgb(4, 4, 1000), + _ir(4, 4, 500), + same_frame_id="f1", + config=config, + prepass_rgbi=_prepass(4, 4), + hybrid_runtime=_hybrid_runtime(tmp_path), + hybrid_subprocess_runner=_stub_hybrid_runner(hybrid_output), + ) + + assert result.status is RepairStatus.APPLIED + assert result.mode_requested is RepairMode.HYBRID + assert result.mode_resolved is RepairMode.HYBRID + np.testing.assert_array_equal(result.repaired_rgb16, hybrid_output) + assert result.hybrid_synthesis_fraction == 0.02 + assert result.hybrid_routing_counts == { + "final_regions": 2, + "synthesis_pixels": 40, + "frame_pixels": 400, + "at_floor_pixels": 60, + } + assert "2 region(s) routed" in result.reason + + def test_hybrid_mode_without_hybrid_package_degrades_to_exact(self, monkeypatch: pytest.MonkeyPatch) -> None: + _install_stub_engine(monkeypatch) + # fauxce_hybrid deliberately not installed. + config = FauxiceRepairConfig(enabled=True, mode=RepairMode.HYBRID) + + result = repair_ir_dust(_rgb(4, 4, 1000), _ir(4, 4, 500), same_frame_id="f1", config=config, prepass_rgbi=_prepass(4, 4)) + + assert result.status is RepairStatus.APPLIED + assert result.mode_requested is RepairMode.HYBRID + assert result.mode_resolved is RepairMode.EXACT + assert "not installed" in result.reason + assert "degraded to exact" in result.reason + + def test_hybrid_mode_without_runtime_config_degrades_to_exact(self, monkeypatch: pytest.MonkeyPatch) -> None: + _install_stub_engine(monkeypatch) + _install_stub_hybrid(monkeypatch) + config = FauxiceRepairConfig(enabled=True, mode=RepairMode.HYBRID) + + result = repair_ir_dust(_rgb(4, 4, 1000), _ir(4, 4, 500), same_frame_id="f1", config=config, prepass_rgbi=_prepass(4, 4)) + + assert result.status is RepairStatus.APPLIED + assert result.mode_resolved is RepairMode.EXACT + assert "no hybrid runtime is configured" in result.reason + + def test_hybrid_run_failure_degrades_to_exact_never_fails(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + _install_stub_engine(monkeypatch) + _install_stub_hybrid(monkeypatch) + config = FauxiceRepairConfig(enabled=True, mode=RepairMode.HYBRID) + + def broken_runner(argv, **kwargs): + return subprocess.CompletedProcess(argv, 2, stdout="", stderr="model runtime missing") + + result = repair_ir_dust( + _rgb(4, 4, 1000), + _ir(4, 4, 500), + same_frame_id="f1", + config=config, + prepass_rgbi=_prepass(4, 4), + hybrid_runtime=_hybrid_runtime(tmp_path), + hybrid_subprocess_runner=broken_runner, + ) + + assert result.status is RepairStatus.APPLIED + assert result.mode_resolved is RepairMode.EXACT + assert "fauxce-hybrid run failed" in result.reason + assert result.repaired_rgb16 is not None + + def test_hybrid_mode_cancelled_before_start_skips_entirely(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + _install_stub_engine(monkeypatch) + _install_stub_hybrid(monkeypatch) + config = FauxiceRepairConfig(enabled=True, mode=RepairMode.HYBRID) + cancel = threading.Event() + cancel.set() + + def unreachable_runner(argv, **kwargs): + raise AssertionError("hybrid subprocess must not run once cancelled") + + # The top-level pre-flight check (shared with exact mode) fires + # before the mode is even inspected, so a cancellation requested up + # front skips the whole call, hybrid included, without touching the + # subprocess runner. + result = repair_ir_dust( + _rgb(4, 4, 1000), + _ir(4, 4, 500), + same_frame_id="f1", + config=config, + prepass_rgbi=_prepass(4, 4), + hybrid_runtime=_hybrid_runtime(tmp_path), + hybrid_subprocess_runner=unreachable_runner, + cancel=cancel, + ) + + assert result.status is RepairStatus.SKIPPED + assert "cancelled by caller before repair started" in result.reason + + def test_hybrid_mode_cancelled_between_preflight_and_hybrid_start(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + """Covers the hybrid-specific cancel check, distinct from the top-level one. + + The top-level pre-flight check and the engine's own cooperative + cancellation both read ``cancel.is_set()``; a stateful fake flips + from unset to set between calls to simulate another thread calling + ``cancel.set()`` in that narrow window, the scenario the extra + hybrid-branch check exists for. + """ + _install_stub_engine(monkeypatch) + _install_stub_hybrid(monkeypatch) + config = FauxiceRepairConfig(enabled=True, mode=RepairMode.HYBRID) + # Call 1: the top-level pre-flight check (unset). Call 2: the + # hybrid-branch check (set). Call 3: the exact fallback's + # cooperative check inside the stub engine (stays set). + cancel = _FlippingCancelEvent(set_after_calls=1) + + def unreachable_runner(argv, **kwargs): + raise AssertionError("hybrid subprocess must not run once cancelled") + + result = repair_ir_dust( + _rgb(4, 4, 1000), + _ir(4, 4, 500), + same_frame_id="f1", + config=config, + prepass_rgbi=_prepass(4, 4), + hybrid_runtime=_hybrid_runtime(tmp_path), + hybrid_subprocess_runner=unreachable_runner, + cancel=cancel, + ) + + # Hybrid degrades to the exact path (never invoking the subprocess), + # and the same cancellation then cancels that exact fallback too. + assert result.status is RepairStatus.SKIPPED + assert "cancelled before the hybrid run started" in result.reason + assert "cancelled before completion" in result.reason + + +# --------------------------------------------------------------------------- +# repair_frame_files: tmp-file invocation, sidecars, IR immutability +# --------------------------------------------------------------------------- + + +class TestRepairFrameFiles: + def _write_frame(self, tmp_path: Path, *, rgb_base: int = 1000, ir_base: int = 500) -> tuple[Path, Path]: + rgb_path = tmp_path / "frame001.tif" + ir_path = tmp_path / "frame001_IR.tif" + tifffile.imwrite(rgb_path, _rgb(4, 4, rgb_base), photometric="rgb") + tifffile.imwrite(ir_path, _ir(4, 4, ir_base), photometric="minisblack") + return rgb_path, ir_path + + def test_writes_repaired_output_and_sidecar(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + _install_stub_engine(monkeypatch) + rgb_path, ir_path = self._write_frame(tmp_path) + prepass_path = tmp_path / "frame001_prepass.npy" + np.save(prepass_path, _prepass(4, 4), allow_pickle=False) + config = FauxiceRepairConfig(enabled=True) + + result = repair_frame_files(rgb_path, config=config, prepass_path=prepass_path) + + assert result.status is RepairStatus.APPLIED + output_path = tmp_path / "frame001_FAUXICE.tif" + sidecar_path = tmp_path / "frame001_FAUXICE.json" + assert output_path.is_file() + assert sidecar_path.is_file() + + written = np.asarray(tifffile.imread(output_path)) + np.testing.assert_array_equal(written, result.repaired_rgb16) + + payload = json.loads(sidecar_path.read_text(encoding="utf-8")) + assert payload["status"] == "applied" + assert payload["engine"]["package"] == ENGINE_IMPORT_NAME + assert payload["source"] == {"rgb": rgb_path.name, "ir": ir_path.name} + assert payload["output"]["repaired_rgb"] == output_path.name + + def test_missing_ir_companion_skips_without_writing_repaired_output(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + _install_stub_engine(monkeypatch) + rgb_path = tmp_path / "frame002.tif" + tifffile.imwrite(rgb_path, _rgb(4, 4, 1000), photometric="rgb") + config = FauxiceRepairConfig(enabled=True) + + result = repair_frame_files(rgb_path, config=config) + + assert result.status is RepairStatus.SKIPPED + assert "no IR companion" in result.reason + assert not (tmp_path / "frame002_FAUXICE.tif").exists() + assert (tmp_path / "frame002_FAUXICE.json").is_file() + + def test_disabled_config_writes_sidecar_only(self, tmp_path: Path) -> None: + rgb_path, _ = self._write_frame(tmp_path) + config = FauxiceRepairConfig(enabled=False) + + result = repair_frame_files(rgb_path, config=config) + + assert result.status is RepairStatus.SKIPPED + sidecar_path = tmp_path / "frame001_FAUXICE.json" + assert sidecar_path.is_file() + assert not (tmp_path / "frame001_FAUXICE.tif").exists() + + def test_ir_file_untouched_on_disk_after_repair(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + _install_stub_engine(monkeypatch) + rgb_path, ir_path = self._write_frame(tmp_path) + prepass_path = tmp_path / "frame001_prepass.npy" + np.save(prepass_path, _prepass(4, 4), allow_pickle=False) + before = ir_path.read_bytes() + config = FauxiceRepairConfig(enabled=True) + + result = repair_frame_files(rgb_path, config=config, prepass_path=prepass_path) + + assert result.status is RepairStatus.APPLIED + after = ir_path.read_bytes() + assert before == after + + def test_ir_array_not_mutated_in_memory(self, monkeypatch: pytest.MonkeyPatch) -> None: + _install_stub_engine(monkeypatch) + config = FauxiceRepairConfig(enabled=True) + ir = _ir(4, 4, 500) + ir_before = ir.copy() + + result = repair_ir_dust(_rgb(4, 4, 1000), ir, same_frame_id="f1", config=config, prepass_rgbi=_prepass(4, 4)) + + assert result.status is RepairStatus.APPLIED + np.testing.assert_array_equal(ir, ir_before) + + def test_provenance_sidecar_written_even_when_unavailable(self, tmp_path: Path) -> None: + # No stub engine installed: engine_available() is False. + rgb_path, _ = self._write_frame(tmp_path) + config = FauxiceRepairConfig(enabled=True) + + result = repair_frame_files(rgb_path, config=config) + + assert result.status is RepairStatus.UNAVAILABLE + sidecar_path = tmp_path / "frame001_FAUXICE.json" + payload = json.loads(sidecar_path.read_text(encoding="utf-8")) + assert payload["status"] == "unavailable" + assert "output" not in payload + + def test_hybrid_provenance_sidecar_records_disclosure_mask_and_routing(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + _install_stub_engine(monkeypatch) + _install_stub_hybrid(monkeypatch) + rgb_path, _ = self._write_frame(tmp_path) + prepass_path = tmp_path / "frame001_prepass.npy" + np.save(prepass_path, _prepass(4, 4), allow_pickle=False) + config = FauxiceRepairConfig(enabled=True, mode=RepairMode.HYBRID) + hybrid_output = np.full((4, 4, 3), 9999, dtype=np.uint16) + + result = repair_frame_files( + rgb_path, + config=config, + prepass_path=prepass_path, + hybrid_runtime=_hybrid_runtime(tmp_path), + hybrid_subprocess_runner=_stub_hybrid_runner(hybrid_output), + ) + + assert result.status is RepairStatus.APPLIED + assert result.mode_resolved is RepairMode.HYBRID + mask_path = tmp_path / "frame001_FAUXICE_SYNTH.png" + assert mask_path.is_file() + assert mask_path.read_bytes() == b"mask-bytes" + + sidecar_path = tmp_path / "frame001_FAUXICE.json" + payload = json.loads(sidecar_path.read_text(encoding="utf-8")) + assert payload["hybrid"]["routing_counts"]["final_regions"] == 2 + assert payload["output"]["disclosure_mask"] == mask_path.name + + def test_no_overwrite_of_existing_ir_when_ir_path_explicit(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + """A caller-supplied ir_path is also never opened for writing.""" + _install_stub_engine(monkeypatch) + rgb_path = tmp_path / "custom.tif" + ir_path = tmp_path / "custom_infrared.tif" + tifffile.imwrite(rgb_path, _rgb(4, 4, 1000), photometric="rgb") + tifffile.imwrite(ir_path, _ir(4, 4, 500), photometric="minisblack") + prepass_path = tmp_path / "prepass.npy" + np.save(prepass_path, _prepass(4, 4), allow_pickle=False) + before = ir_path.read_bytes() + config = FauxiceRepairConfig(enabled=True) + + result = repair_frame_files(rgb_path, config=config, ir_path=ir_path, prepass_path=prepass_path) + + assert result.status is RepairStatus.APPLIED + assert ir_path.read_bytes() == before diff --git a/uv.lock b/uv.lock index 400d686c..11aa69d2 100644 --- a/uv.lock +++ b/uv.lock @@ -11,6 +11,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a9/ba/000a1996d4308bc65120167c21241a3b205464a2e0b58deda26ae8ac21d1/altgraph-0.17.5-py2.py3-none-any.whl", hash = "sha256:f3a22400bce1b0c701683820ac4f3b159cd301acab067c51c653e06961600597", size = 21228, upload-time = "2025-11-21T20:35:49.444Z" }, ] +[[package]] +name = "attrs" +version = "26.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9a/8e/82a0fe20a541c03148528be8cac2408564a6c9a0cc7e9171802bc1d26985/attrs-26.1.0.tar.gz", hash = "sha256:d03ceb89cb322a8fd706d4fb91940737b6642aa36998fe130a9bc96c985eff32", size = 952055, upload-time = "2026-03-19T14:22:25.026Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl", hash = "sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309", size = 67548, upload-time = "2026-03-19T14:22:23.645Z" }, +] + [[package]] name = "cffi" version = "2.0.0" @@ -151,6 +160,33 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/61/e8/cb8e80d6f9f55b99588625062822bf946cf03ed06315df4bd8397f5632a1/coverage-7.14.0-py3-none-any.whl", hash = "sha256:8de5b61163aee3d05c8a2beab6f47913df7981dad1baf82c414d99158c286ab1", size = 211764, upload-time = "2026-05-10T18:02:29.538Z" }, ] +[[package]] +name = "fauxce-hybrid" +version = "0.3.0" +source = { url = "https://github.com/rohanpandula/digital-fauxice/releases/download/v0.3.0/fauxce_hybrid-0.3.0-py3-none-any.whl" } +dependencies = [ + { name = "imageio" }, + { name = "jsonschema" }, + { name = "numpy" }, + { name = "portable-digital-ice", extra = ["fast"] }, + { name = "scipy" }, +] +wheels = [ + { url = "https://github.com/rohanpandula/digital-fauxice/releases/download/v0.3.0/fauxce_hybrid-0.3.0-py3-none-any.whl", hash = "sha256:c732bbfd64883eec52f57a15cfb0f2d1cb71981142e977ca653b7e0471d2371c" }, +] + +[package.metadata] +requires-dist = [ + { name = "imageio", specifier = ">=2.37,<3" }, + { name = "jsonschema", specifier = ">=4.25,<5" }, + { name = "numpy", specifier = ">=2.4,<3" }, + { name = "portable-digital-ice", extras = ["fast"], specifier = "==0.3.0" }, + { name = "pytest", marker = "extra == 'test'", specifier = ">=9,<10" }, + { name = "ruff", marker = "extra == 'test'", specifier = ">=0.11" }, + { name = "scipy", specifier = ">=1.16,<2" }, +] +provides-extras = ["test"] + [[package]] name = "gphoto2" version = "2.6.4" @@ -223,6 +259,33 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" }, ] +[[package]] +name = "jsonschema" +version = "4.26.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "jsonschema-specifications" }, + { name = "referencing" }, + { name = "rpds-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/b3/fc/e067678238fa451312d4c62bf6e6cf5ec56375422aee02f9cb5f909b3047/jsonschema-4.26.0.tar.gz", hash = "sha256:0c26707e2efad8aa1bfc5b7ce170f3fccc2e4918ff85989ba9ffa9facb2be326", size = 366583, upload-time = "2026-01-07T13:41:07.246Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl", hash = "sha256:d489f15263b8d200f8387e64b4c3a75f06629559fb73deb8fdfb525f2dab50ce", size = 90630, upload-time = "2026-01-07T13:41:05.306Z" }, +] + +[[package]] +name = "jsonschema-specifications" +version = "2025.9.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "referencing" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/19/74/a633ee74eb36c44aa6d1095e7cc5569bebf04342ee146178e2d36600708b/jsonschema_specifications-2025.9.1.tar.gz", hash = "sha256:b540987f239e745613c7a9176f3edb72b832a4ac465cf02712288397832b5e8d", size = 32855, upload-time = "2025-09-08T01:34:59.186Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl", hash = "sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe", size = 18437, upload-time = "2025-09-08T01:34:57.871Z" }, +] + [[package]] name = "llvmlite" version = "0.47.0" @@ -342,6 +405,12 @@ dev = [ { name = "ruff" }, { name = "ty" }, ] +fauxice = [ + { name = "portable-digital-ice" }, +] +fauxice-hybrid = [ + { name = "fauxce-hybrid" }, +] scanner = [ { name = "python-sane" }, ] @@ -375,6 +444,8 @@ dev = [ { name = "ruff", specifier = "==0.14.10" }, { name = "ty", specifier = ">=0.0.26" }, ] +fauxice = [{ name = "portable-digital-ice", url = "https://github.com/rohanpandula/digital-fauxice/releases/download/v0.3.0/portable_digital_ice-0.3.0-py3-none-any.whl" }] +fauxice-hybrid = [{ name = "fauxce-hybrid", url = "https://github.com/rohanpandula/digital-fauxice/releases/download/v0.3.0/fauxce_hybrid-0.3.0-py3-none-any.whl" }] scanner = [{ name = "python-sane", specifier = ">=2.9" }] [[package]] @@ -563,6 +634,36 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, ] +[[package]] +name = "portable-digital-ice" +version = "0.3.0" +source = { url = "https://github.com/rohanpandula/digital-fauxice/releases/download/v0.3.0/portable_digital_ice-0.3.0-py3-none-any.whl" } +dependencies = [ + { name = "numpy" }, +] +wheels = [ + { url = "https://github.com/rohanpandula/digital-fauxice/releases/download/v0.3.0/portable_digital_ice-0.3.0-py3-none-any.whl", hash = "sha256:d3cb4145a66f28a7eb795aac112a638cc9a2c11b2000907508b2490d84c8b5bb" }, +] + +[package.optional-dependencies] +fast = [ + { name = "numba" }, +] + +[package.metadata] +requires-dist = [ + { name = "cupy-cuda12x", marker = "extra == 'cuda'", specifier = ">=13" }, + { name = "numba", marker = "extra == 'cuda'", specifier = ">=0.65,<0.68" }, + { name = "numba", marker = "extra == 'fast'", specifier = ">=0.65,<0.68" }, + { name = "numba", marker = "extra == 'metal'", specifier = ">=0.65,<0.68" }, + { name = "numpy", specifier = ">=2.4,<3" }, + { name = "pyobjc-framework-metal", marker = "extra == 'metal'", specifier = ">=10.3" }, + { name = "pytest", marker = "extra == 'dev'", specifier = ">=9,<10" }, + { name = "pytest", marker = "extra == 'test'", specifier = ">=9,<10" }, + { name = "ruff", marker = "extra == 'dev'", specifier = ">=0.11" }, +] +provides-extras = ["cuda", "fast", "metal", "test", "dev"] + [[package]] name = "pycparser" version = "3.0" @@ -807,6 +908,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/4d/01/1854b2f789d7a6d94152ba451bdaadae0010d4247099d9c2719b381d4b6b/rawpy-0.27.0-cp314-cp314-win_amd64.whl", hash = "sha256:3cac371c3b6302eb88bd1f33d43c7a92b5e553fdff14258b564f81e173a68c26", size = 940931, upload-time = "2026-05-07T08:29:15.604Z" }, ] +[[package]] +name = "referencing" +version = "0.37.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "attrs" }, + { name = "rpds-py" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/22/f5/df4e9027acead3ecc63e50fe1e36aca1523e1719559c499951bb4b53188f/referencing-0.37.0.tar.gz", hash = "sha256:44aefc3142c5b842538163acb373e24cce6632bd54bdb01b21ad5863489f50d8", size = 78036, upload-time = "2025-10-13T15:30:48.871Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl", hash = "sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231", size = 26766, upload-time = "2025-10-13T15:30:47.625Z" }, +] + [[package]] name = "rendercanvas" version = "2.6.3" @@ -819,6 +933,87 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c1/b2/b06d7d89eef452ab50ad3e752604c09ec08c8eaf49484641e566a0e5141d/rendercanvas-2.6.3-py3-none-any.whl", hash = "sha256:c2defd04e3e29dcdf94b794e827045882925677bc48b04084f3b9696fd469018", size = 105194, upload-time = "2026-03-12T13:43:08.965Z" }, ] +[[package]] +name = "rpds-py" +version = "2026.6.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/2a/9618a122aeb2a169a28b03889a2995fe297588964333d4a7d67bdf46e147/rpds_py-2026.6.3.tar.gz", hash = "sha256:1cebd1337c242e4ec2293e541f712b2da849b29f48f0c293684b71c0632625d4", size = 64051, upload-time = "2026-06-30T07:17:53.009Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a4/9e/b818ee580026ec578138e961027a68820c40afeb1ec8f6819b54fb99e196/rpds_py-2026.6.3-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:3cfe765c1da0072636ca06628261e0ea05688e160d5c8a03e0217c3854037223", size = 343012, upload-time = "2026-06-30T07:15:36.005Z" }, + { url = "https://files.pythonhosted.org/packages/f3/6b/686d9dc4359a8f163cfbbf89ee0b4e586431de22fe8248edb63a8cf50d49/rpds_py-2026.6.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f4d78253f6996be4901669ad25319f842f740eccf4d58e3c7f3dd39e6dde1d8f", size = 338203, upload-time = "2026-06-30T07:15:37.462Z" }, + { url = "https://files.pythonhosted.org/packages/9e/9b/069aa329940f8207615e091f5eedbbd40e1e15eac68a0790fd05ccdf796c/rpds_py-2026.6.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54f45a148e28767bf343d33a684693c70e451c6f4c0e9904709a723fafbdfc1f", size = 367984, upload-time = "2026-06-30T07:15:39.008Z" }, + { url = "https://files.pythonhosted.org/packages/14/db/34c203e4becff3703e4d3bc121842c00b8689197f398161203a880052f4e/rpds_py-2026.6.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:842e7b070435622248c7a2c44ae53fa1440e073cc3023bc919fed570884097a7", size = 374815, upload-time = "2026-06-30T07:15:40.253Z" }, + { url = "https://files.pythonhosted.org/packages/ee/7d/8071067d2cc453d916ad836e828c943f575e8a44612537759002a1e07381/rpds_py-2026.6.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8020133a74bd81b4572dd8e4be028a6b1ebcd70e6726edc3918008c08bee6ee6", size = 490545, upload-time = "2026-06-30T07:15:41.729Z" }, + { url = "https://files.pythonhosted.org/packages/a3/42/da06c5aa8f0484ff07f270787434204d9f4535e2f8c3b51ed402267e63c3/rpds_py-2026.6.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cdc7e35386f3847df728fbcb5e887e2d79c19e2fa1eba9e51b6621d23e3243af", size = 382828, upload-time = "2026-06-30T07:15:43.327Z" }, + { url = "https://files.pythonhosted.org/packages/57/d7/fe978efc2ae50abe48eb7464668ea99f53c010c60aeebb7b35ad27f23661/rpds_py-2026.6.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acac386b453c2516111b50985d60ce46e7fadb5ea71ae7b25f4c946935bf27cf", size = 365678, upload-time = "2026-06-30T07:15:44.992Z" }, + { url = "https://files.pythonhosted.org/packages/69/9d/1d8922e1990b2a6eb532b6ff53d3e73d2b3bbffc84116c75826bee73dfc6/rpds_py-2026.6.3-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:425560c6fa0415f27261727bb20bd097568485e5eb0c121f1949417d1c516885", size = 377811, upload-time = "2026-06-30T07:15:46.523Z" }, + { url = "https://files.pythonhosted.org/packages/b1/3d/198dceafb4fb034a6a47347e1b0735d34e0bd4a50be4e898d408ee66cb14/rpds_py-2026.6.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a550fb4950a06dde3beb4721f5ad4b25bf4513784665b0a8522c792e2bd822a4", size = 395382, upload-time = "2026-06-30T07:15:47.955Z" }, + { url = "https://files.pythonhosted.org/packages/1f/f1/13968e49655d40b6b19d8b9140296bbc6f1d86b3f0f6c346cf9f1adddf4b/rpds_py-2026.6.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:4f4bca01b63096f606e095734dd56e74e175f94cfbf24ff3d63281cec61f7bb7", size = 543832, upload-time = "2026-06-30T07:15:49.33Z" }, + { url = "https://files.pythonhosted.org/packages/ac/ab/289bcb1b90bd3e40a2900c561fa0e2087345ecbb094f0b870f2345142b7c/rpds_py-2026.6.3-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ccffae9a092a00deb7efd545fe5e2c33c33b88e7c054337e9a74c179347d0b7d", size = 611011, upload-time = "2026-06-30T07:15:50.847Z" }, + { url = "https://files.pythonhosted.org/packages/1e/16/5043105e679436ccfbc8e5e0dd2d663ed18a8b8113515fd06a5e5d77c83e/rpds_py-2026.6.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1cf01971c4f2c5553b772a542e4aaf191789cd331bc2cd4ff0e6e65ba49e1e97", size = 572431, upload-time = "2026-06-30T07:15:52.394Z" }, + { url = "https://files.pythonhosted.org/packages/85/ed/adab103321c0a6565d5ae1c2998349bc3ee175b82ccc5ae8fc04cc413075/rpds_py-2026.6.3-cp313-cp313-win32.whl", hash = "sha256:8c3d1e9c15b9d51ca0391e13da1a25a0a4df3c58a37c9dc368e0736cf7f69df0", size = 201710, upload-time = "2026-06-30T07:15:53.894Z" }, + { url = "https://files.pythonhosted.org/packages/7b/ed/a03b09668e74e5dabbf2e211f6468e1820c0552f7b0500082da31841bf7b/rpds_py-2026.6.3-cp313-cp313-win_amd64.whl", hash = "sha256:9250a9a0a6fd4648b3f868da8d91a4c52b5811a62df58e753d50ae4454a36f80", size = 219454, upload-time = "2026-06-30T07:15:55.25Z" }, + { url = "https://files.pythonhosted.org/packages/27/17/b8642c12930b71bc2b25831f6708ccf0f75abcd11883932ec9ce54ba3a78/rpds_py-2026.6.3-cp313-cp313-win_arm64.whl", hash = "sha256:900a67df3fd1660b035a4761c4ce73c382ea6b35f90f9863c36c6fd8bf8b09bb", size = 215063, upload-time = "2026-06-30T07:15:56.573Z" }, + { url = "https://files.pythonhosted.org/packages/b6/36/7fbe9dcdaf857fb3f63c2a2284b62492d95f5e8334e947e5fb6e7f68c9be/rpds_py-2026.6.3-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:931908d9fc855d8f74783377822be318edb6dcb19e47169dc038f9a1bf60b06e", size = 344510, upload-time = "2026-06-30T07:15:57.921Z" }, + { url = "https://files.pythonhosted.org/packages/ba/54/f785cc3d3f60839ca57a5af4927a9f347b07b2799c373fc20f7949f87c7e/rpds_py-2026.6.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:d7469697dce35be237db177d42e2a2ee26e6dcc5fc052078a6fefabd288c6edd", size = 339495, upload-time = "2026-06-30T07:15:59.238Z" }, + { url = "https://files.pythonhosted.org/packages/63/ef/d4cdaf309e6b095b43597103cf8c0b951d6cca2acce68c474f75ec12e0c7/rpds_py-2026.6.3-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bcfbcf66006befb9fd2aeaa9e01feaf881b4dc330a02ba07d2322b1c11be7b5d", size = 369454, upload-time = "2026-06-30T07:16:01.021Z" }, + { url = "https://files.pythonhosted.org/packages/96/4a/9559a68b7ee15db09d7981212e8c2e219d2a1d6d4faa0391d813c3496a36/rpds_py-2026.6.3-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:847927daf4cffbd4e90e42bc890069897101edd015f956cb8721b3473372edda", size = 374583, upload-time = "2026-06-30T07:16:02.287Z" }, + { url = "https://files.pythonhosted.org/packages/ef/75/8964aa7d2c6e8ac43eba8eb6e6b0fdda1f46d39f2fc3e6aa9f2cb17f485d/rpds_py-2026.6.3-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aca6c1ef08a82bfe327cc156da694660f599923e2e6665b6d81c9c2d0ac9ffc8", size = 492919, upload-time = "2026-06-30T07:16:03.723Z" }, + { url = "https://files.pythonhosted.org/packages/8f/97/6908094ac804115e65aedfd90f1b5fee4eebebd3f6c4cfc5419939267565/rpds_py-2026.6.3-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ae50181a047c871561212bb97f7932a2d45fb53e947bd9b57ebad85b529cbc53", size = 383725, upload-time = "2026-06-30T07:16:05.305Z" }, + { url = "https://files.pythonhosted.org/packages/d1/9c/0d1fdc2e7aba23e290d603bc494e97bd205bae262ce33c6b32a69768ed5e/rpds_py-2026.6.3-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc319e5a1de4b6913aac94bf6a2f9e847371e0a140a43dd4991db1a09bc2d504", size = 367255, upload-time = "2026-06-30T07:16:07.086Z" }, + { url = "https://files.pythonhosted.org/packages/c4/fe/f0209ca4a9ed074bc8acb44dfd0e81c3122e94c9689f5645b7973a866719/rpds_py-2026.6.3-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:e4316bf32babbed84e691e352faf967ce2f0f024174a8643c37c94a1080374fc", size = 379060, upload-time = "2026-06-30T07:16:08.525Z" }, + { url = "https://files.pythonhosted.org/packages/c6/8d/f1cc54c616b9d8897de8738aac148d20afca93f68187475fe194d09a71b9/rpds_py-2026.6.3-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8c6e5a2f750cc71c3e3b11d71661f21d6f9bc6cebc6564b1466417a1ec03ec77", size = 395960, upload-time = "2026-06-30T07:16:09.989Z" }, + { url = "https://files.pythonhosted.org/packages/fb/04/aafff00f73aeca2945f734f1d483c64ab8f472d0864ab02377fd8e89c3b2/rpds_py-2026.6.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:4470ce197d4090875cf6affbf1f853338387428df97c4fb7b7106317b8214698", size = 545356, upload-time = "2026-06-30T07:16:11.816Z" }, + { url = "https://files.pythonhosted.org/packages/fd/cc/e229663b9e4ddac5a4acbe9085dd80a71af2a5d356b8b39d6bff233f24b0/rpds_py-2026.6.3-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:ea964164cc9afa72d4d9b23cc28dafae93693c0a53e0b42acbff15b22c3f9ddd", size = 612319, upload-time = "2026-06-30T07:16:13.586Z" }, + { url = "https://files.pythonhosted.org/packages/e3/7a/8a0e6d3e6cd066af108b71b43122c3fe158dd9eb86acac626593a2582eb1/rpds_py-2026.6.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:639c8929aa0afe81be836b04de888460d6bed38b9c54cfc18da8f6bfabf5af5d", size = 573508, upload-time = "2026-06-30T07:16:15.23Z" }, + { url = "https://files.pythonhosted.org/packages/87/03/2a69ab618a789cf6cf85c86bb844c62d090e700ab1a2aa676b3741b6c516/rpds_py-2026.6.3-cp314-cp314-win32.whl", hash = "sha256:882076c00c0a608b131187055ddc5ae29f2e7eaf870d6168980420d58528a5c8", size = 202504, upload-time = "2026-06-30T07:16:16.893Z" }, + { url = "https://files.pythonhosted.org/packages/85/62/a3892ba945f4e24c78f352e5de3c7620d8479f73f211406a97263d13c7d2/rpds_py-2026.6.3-cp314-cp314-win_amd64.whl", hash = "sha256:0be972be84cfcaf46c8c6edf690ca0f154ac17babf1f6a955a51579b34ad2dc5", size = 220380, upload-time = "2026-06-30T07:16:18.108Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e7/c2bd44dc831931815ad11ebb5f430b5a0a4d3caa9de837107876c30c3432/rpds_py-2026.6.3-cp314-cp314-win_arm64.whl", hash = "sha256:2a9c6f195058cb45335e8cc3802745c603d716eb96bc9625950c1aac71c0c703", size = 215976, upload-time = "2026-06-30T07:16:19.654Z" }, + { url = "https://files.pythonhosted.org/packages/79/9c/fff7b74bce9a091ec9a012a03f9ff5f69364eaf9451060dfc4486da2ffdd/rpds_py-2026.6.3-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:f90938e92afda60266da758ee7d363447f7f0138c9559f9e1811629580582d90", size = 346840, upload-time = "2026-06-30T07:16:21.268Z" }, + { url = "https://files.pythonhosted.org/packages/e9/44/77bcb1168b33704908295533d27f10eb811e9e3e193e8993dc99572211d3/rpds_py-2026.6.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ec829541c45bca16e61c7ae50c20501f213605beb75d1aba91a6ee37fbbb56a4", size = 340282, upload-time = "2026-06-30T07:16:22.875Z" }, + { url = "https://files.pythonhosted.org/packages/87/3c/7a9081c7c9e645b39efe19e4ffbeccd80add246327cd9b888aecffd72317/rpds_py-2026.6.3-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afd70d95892096cdb26f15a00c45907b17817577aa8d1c76b2dcc2788391f9e9", size = 370403, upload-time = "2026-06-30T07:16:24.415Z" }, + { url = "https://files.pythonhosted.org/packages/f7/69/af47021eb7dad6ff3396cb001c08f0f3c4d06c20253f75be6421a59fe6b7/rpds_py-2026.6.3-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:29dfa0533a5d4c94d4dfa1b694fcb56c9c63aad8330ffdd816fd225d0a7a162f", size = 376055, upload-time = "2026-06-30T07:16:26.111Z" }, + { url = "https://files.pythonhosted.org/packages/81/fc/a3bcf517084396a6dd258c592567a3c011ba4557f2fde23dceaf26e74f2e/rpds_py-2026.6.3-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:af05d726809bff6b141be124d4c7ce998f9c9c7f30edb1f46c07aa103d540b41", size = 494419, upload-time = "2026-06-30T07:16:27.596Z" }, + { url = "https://files.pythonhosted.org/packages/c9/eb/13d529d1788135425c7bf207f8463458ca5d92e43f3f701365b83e9dffc1/rpds_py-2026.6.3-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9826217f048f620d9a712672818bf231442c1b35d96b227a07eabd11b4bb6945", size = 384848, upload-time = "2026-06-30T07:16:29.183Z" }, + { url = "https://files.pythonhosted.org/packages/8e/f4/b7ac49f30013aba8f7b9566b1dd07e81de95e708c1374b7bacc5b9bc5c9c/rpds_py-2026.6.3-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:536bceea4fa4acf7e1c61da2b5786304367c816c8895be71b8f537c480b0ea1f", size = 371369, upload-time = "2026-06-30T07:16:30.912Z" }, + { url = "https://files.pythonhosted.org/packages/31/86/6260bafa622f788b07ddec0e52d810305c8b9b0b8c27f58a2ab04bf62b4f/rpds_py-2026.6.3-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:bc0011654b91cc4fb2ae701bec0a0ba1e552c0714247fa7af6c59e0ccfa3a4e1", size = 379673, upload-time = "2026-06-30T07:16:32.486Z" }, + { url = "https://files.pythonhosted.org/packages/19/c3/03f1ee79a047b48daeca157c89a18509cde22b6b951d642b9b0af1be660a/rpds_py-2026.6.3-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:539d75de9e0d536c84ff18dfeb805398e58227001ce09231a26a08b9aed1ee0e", size = 397500, upload-time = "2026-06-30T07:16:34.471Z" }, + { url = "https://files.pythonhosted.org/packages/f0/95/8ed0cd8c377dca12aea498f119fe639fc474d1461545c39d2b5872eb1c0f/rpds_py-2026.6.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:166cf54d9f44fc6ceb53c7860258dde44a81406646de79f8ed3234fca3b6e538", size = 545978, upload-time = "2026-06-30T07:16:36.45Z" }, + { url = "https://files.pythonhosted.org/packages/d3/f2/0eb57f0eaa83f8fc152a7e03de968ab77e1f00732bebc892b190c6eebde7/rpds_py-2026.6.3-cp314-cp314t-musllinux_1_2_i686.whl", hash = "sha256:d34c20167764fbcf927194d532dd7e0c56772f0a5f943fa5ef9e9afbba8fb9db", size = 613350, upload-time = "2026-06-30T07:16:38.213Z" }, + { url = "https://files.pythonhosted.org/packages/5b/de/e0674bdbc3ef7634989b3f854c3f34bc1f587d36e5bfdc5c378d57034619/rpds_py-2026.6.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:ea7bb13b7c9a29791f87a0387ba7d3ad3a6d783d827e4d3f27b40a0ff44495e2", size = 576486, upload-time = "2026-06-30T07:16:39.797Z" }, + { url = "https://files.pythonhosted.org/packages/f2/f6/21101359743cd136ada781e8210a85769578422ba460672eea0e29739200/rpds_py-2026.6.3-cp314-cp314t-win32.whl", hash = "sha256:6de4744d05bd1aa1be4ed7ea1189e3979196808008113bbbf899a460966b925e", size = 201068, upload-time = "2026-06-30T07:16:41.316Z" }, + { url = "https://files.pythonhosted.org/packages/a6/b2/9574d4d44f7760c2aa32d92a0a4f41698e33f5b204a0bf5c9758f52c79d5/rpds_py-2026.6.3-cp314-cp314t-win_amd64.whl", hash = "sha256:c7b9a2f8f4d8e90af72571d3d495deebdd7e3c75451f5b41719aee166e940fc2", size = 220600, upload-time = "2026-06-30T07:16:43.091Z" }, + { url = "https://files.pythonhosted.org/packages/08/ae/f23a2697e6ee6340a578b0f136be6483657bef0c6f9497b752bb5c0964bb/rpds_py-2026.6.3-cp315-cp315-macosx_10_12_x86_64.whl", hash = "sha256:e059c5dde6452b44424bd1834557556c226b57781dee1227af23518459722b13", size = 344726, upload-time = "2026-06-30T07:16:44.5Z" }, + { url = "https://files.pythonhosted.org/packages/c3/63/e7b3a1a5358dd32c930a1062d8e15b67fd6e8922e81df9e91706d66ee5c8/rpds_py-2026.6.3-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:2f7c26fbc5acd2522b95d4177fe4710ffd8e9b20529e703ffbf8db4d93903f05", size = 339587, upload-time = "2026-06-30T07:16:46.255Z" }, + { url = "https://files.pythonhosted.org/packages/ec/64/10a85681916ca55fffb91b0a211f84e34297c109243484dd6394660a8a7c/rpds_py-2026.6.3-cp315-cp315-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a3086b538543802f84c843911242db20447de00d8752dd0efc936dbcf02218ba", size = 369585, upload-time = "2026-06-30T07:16:48.101Z" }, + { url = "https://files.pythonhosted.org/packages/76/c2/baf95c7c38823e12ba34407c5f5767a89e5cf2233895e56f608167ae9493/rpds_py-2026.6.3-cp315-cp315-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8f2e5c5ee828d42cb11760761c0af6507927bec42d0ad5458f97c9203b054617", size = 375479, upload-time = "2026-06-30T07:16:49.93Z" }, + { url = "https://files.pythonhosted.org/packages/6a/94/0aad06c72d65101e11d33528d438cda99a39ce0da99466e156158f2541d3/rpds_py-2026.6.3-cp315-cp315-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ed0c1e5d10cdc7135537988c74a0188da68e2f3c30813ba3744ab1e42e0480f9", size = 492418, upload-time = "2026-06-30T07:16:51.641Z" }, + { url = "https://files.pythonhosted.org/packages/b5/17/de3f5a479a1f056535d7489819639d8cd591ea6281d700390b43b1abd745/rpds_py-2026.6.3-cp315-cp315-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8c2642a7603ec0b16ed77da4555db3b4b472341904873788327c0b0d7b95f1bb", size = 384123, upload-time = "2026-06-30T07:16:53.622Z" }, + { url = "https://files.pythonhosted.org/packages/46/7d/bf09bd1b145bb2671c03e1e6d1ab8651858d90d8c7dfeadd85a37a934fd8/rpds_py-2026.6.3-cp315-cp315-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e4320744c1ffdd95a603def63344bfab2d33edeab301c5007e7de9f9f5b3885", size = 367351, upload-time = "2026-06-30T07:16:55.241Z" }, + { url = "https://files.pythonhosted.org/packages/a3/ea/1bb734f314b8be319149ddee80b18bd41372bdcfbdf88d28131c0cd37719/rpds_py-2026.6.3-cp315-cp315-manylinux_2_31_riscv64.whl", hash = "sha256:a9f4645593036b81bbdb36b9c8e0ea0d1c3fee968c4d59db0344c14087ef143a", size = 378827, upload-time = "2026-06-30T07:16:56.841Z" }, + { url = "https://files.pythonhosted.org/packages/4b/93/d9611e5b25e26df9a3649813ed66193ace9347a7c7fc4ab7cf70e94851c0/rpds_py-2026.6.3-cp315-cp315-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e55d236be29255554da47abe5c577637db7c24a02b8b46f0ca9524c855801868", size = 395966, upload-time = "2026-06-30T07:16:58.557Z" }, + { url = "https://files.pythonhosted.org/packages/c3/cb/99d77e16e5534ae1d90629bbe419ba6ee170833a6a85e3aa1cc41726fbbc/rpds_py-2026.6.3-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:24e9c5386e16669b674a69c156c8eeefcb578f3b3397b713b08e6d60f3c7b187", size = 545680, upload-time = "2026-06-30T07:17:00.164Z" }, + { url = "https://files.pythonhosted.org/packages/59/15/11a29755f790cef7a2f755e8e14f4f0c33f39489e1893a632a2eee59672b/rpds_py-2026.6.3-cp315-cp315-musllinux_1_2_i686.whl", hash = "sha256:c60924535c75f1566b6eb75b5c31a48a43fef04fa2d0d201acbad8a9969c6107", size = 611853, upload-time = "2026-06-30T07:17:01.962Z" }, + { url = "https://files.pythonhosted.org/packages/68/86/0c27547e21644da938fb530f7e1a8148dd24d02db07e7a5f2567a17ce710/rpds_py-2026.6.3-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:38a2fea2787428f811719ceb9114cb78964a3138838320c29ac39526c79c16ba", size = 573715, upload-time = "2026-06-30T07:17:03.693Z" }, + { url = "https://files.pythonhosted.org/packages/29/71/4d8fcf700931815594bce892255bbd973b94efaf0fc1932b0590df18d886/rpds_py-2026.6.3-cp315-cp315-win32.whl", hash = "sha256:d483fe17f01ad64b7bf7cc38fcefff1ca9fb83f8c2b2542b68f97ffe0611b369", size = 202864, upload-time = "2026-06-30T07:17:05.746Z" }, + { url = "https://files.pythonhosted.org/packages/eb/62/b577562de0edbb55b2be85ce5fd09c33e386b9b13eee09833af4240fd5c4/rpds_py-2026.6.3-cp315-cp315-win_amd64.whl", hash = "sha256:67e3a721ffc5d8d2210d3671872298c4a84e4b8035cfe42ffd7cde35d772b146", size = 220430, upload-time = "2026-06-30T07:17:07.471Z" }, + { url = "https://files.pythonhosted.org/packages/c8/95/d6d0b2509825141eef60669a5739eec88dbc6a48053d6c92993a5704defe/rpds_py-2026.6.3-cp315-cp315-win_arm64.whl", hash = "sha256:6e84adbcf4bf841aed8116a8264b9f50b4cb3e7bd89b516122e616ac56ca269e", size = 215877, upload-time = "2026-06-30T07:17:09.008Z" }, + { url = "https://files.pythonhosted.org/packages/b7/bf/f3ea278f0afd615c1d0f19cb69043a41526e2bb600c2b536eb192218eb27/rpds_py-2026.6.3-cp315-cp315t-macosx_10_12_x86_64.whl", hash = "sha256:ae6dd8f10bd17aad820876d24caec9efdafd80a318d16c0a48edb5e136902c6b", size = 346933, upload-time = "2026-06-30T07:17:10.762Z" }, + { url = "https://files.pythonhosted.org/packages/9d/29/9907bdf1c5346763cf10b7f6852aad86652168c259def904cbe0082c5864/rpds_py-2026.6.3-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:bdbd97738551fca3917c1bd7188bec1920bb520104f28e7e1007f9ceb17b7690", size = 340274, upload-time = "2026-06-30T07:17:12.266Z" }, + { url = "https://files.pythonhosted.org/packages/6f/2c/8e03767b5778ef25cebf74a7a91a2c3806f8eced4c92cb7406bbe060756d/rpds_py-2026.6.3-cp315-cp315t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b95977e7211527ab0ba576e286d023389fbeeb32a6b7b771665d333c60e5342", size = 370763, upload-time = "2026-06-30T07:17:14.107Z" }, + { url = "https://files.pythonhosted.org/packages/2e/e1/df2a7e1ba2efd796af26194250b8d42c821b46592311595162af9ef0528d/rpds_py-2026.6.3-cp315-cp315t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:d15fde0e6fb0d88a60d221204873743e5d9f0b7d29165e62cd86d0413ad74ba6", size = 376467, upload-time = "2026-06-30T07:17:15.76Z" }, + { url = "https://files.pythonhosted.org/packages/6b/de/8a0814d1946af29cb068fb259aa8622f856df1d0bab58429448726b537f5/rpds_py-2026.6.3-cp315-cp315t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a136d453475ac0fcbda502ef1e6504bd28d6d904700915d278deeab0d00fe140", size = 496689, upload-time = "2026-06-30T07:17:17.308Z" }, + { url = "https://files.pythonhosted.org/packages/df/f3/f19e0c852ba13694f5a79f3b719331051573cb5693feacf8a88ffffc3a71/rpds_py-2026.6.3-cp315-cp315t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f826877d462181e5eb1c26a0026b8d0cab05d99844ecb6d8bf3627a2ca0c0442", size = 385340, upload-time = "2026-06-30T07:17:18.928Z" }, + { url = "https://files.pythonhosted.org/packages/e2/ae/7ec3a9d2d4351f99e37bcb06b6b6f954512646bfdbf9742e1de727865daf/rpds_py-2026.6.3-cp315-cp315t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:79486287de1730dbaff3dbd124d0ca4d2ef7f9d29bf2544f1f93c09b5bcbbd12", size = 372179, upload-time = "2026-06-30T07:17:20.539Z" }, + { url = "https://files.pythonhosted.org/packages/d3/ac/9cee911dff2aaa9a5a8354f6610bf2e6a616de9197c5fff4f54f82585f1e/rpds_py-2026.6.3-cp315-cp315t-manylinux_2_31_riscv64.whl", hash = "sha256:808345f53cb952433ca2816f1604ff3515608a81784954f38d4452acfe8e61d5", size = 379993, upload-time = "2026-06-30T07:17:22.212Z" }, + { url = "https://files.pythonhosted.org/packages/83/6b/7c2a07ba88d1e9a936612f7a5d067467ed03d971d5a06f7d309dff044a7e/rpds_py-2026.6.3-cp315-cp315t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1967debc37f64f2c4dc90a7f563aec558b471966e12adcac4e1c4240496b6ebf", size = 398909, upload-time = "2026-06-30T07:17:23.66Z" }, + { url = "https://files.pythonhosted.org/packages/97/0b/776ffcb66783637b0031f6d58d6fb55913c8b5abf00aeecd46bf933fb477/rpds_py-2026.6.3-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:f0840b5b17057f7fd918b76183a4b5a0635f43e14eb2ce60dce1d4ee4707ea00", size = 546584, upload-time = "2026-06-30T07:17:25.264Z" }, + { url = "https://files.pythonhosted.org/packages/55/33/ba3bc04d7092bd553c9b2b195624992d2cc4f3de1f380b7b93cbee67bd79/rpds_py-2026.6.3-cp315-cp315t-musllinux_1_2_i686.whl", hash = "sha256:faa679d19a6696fd54259ad321251ad77a13e70e03dd834daa762a44fb6196ef", size = 614357, upload-time = "2026-06-30T07:17:26.888Z" }, + { url = "https://files.pythonhosted.org/packages/8b/71/14edf065f04630b1a8472f7653cad03f6c478bcf95ea0e6aed55451e33ea/rpds_py-2026.6.3-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:23a439f31ccbeff1574e24889128821d1f7917470e830cf6544dced1c662262a", size = 576533, upload-time = "2026-06-30T07:17:28.546Z" }, + { url = "https://files.pythonhosted.org/packages/ba/76/65002b08596c389105720a8c0d22298b8dc25a4baf89b2ce431343c8b1de/rpds_py-2026.6.3-cp315-cp315t-win32.whl", hash = "sha256:913ca42ccad3f8cc6e292b587ae8ae49c8c823e5dce51a736252fc7c7cdfa577", size = 201204, upload-time = "2026-06-30T07:17:30.193Z" }, + { url = "https://files.pythonhosted.org/packages/8c/97/d855d6b3c322d1f27e26f5241c42016b56cf01377ea8ed348285f54652f0/rpds_py-2026.6.3-cp315-cp315t-win_amd64.whl", hash = "sha256:ae3d4fe8c0b9213624fdce7279d70e3b148b682ca20719ebd193a23ebfa47324", size = 220719, upload-time = "2026-06-30T07:17:31.788Z" }, +] + [[package]] name = "rubicon-objc" version = "0.5.4" @@ -854,6 +1049,47 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/74/31/b0e29d572670dca3674eeee78e418f20bdf97fa8aa9ea71380885e175ca0/ruff-0.14.10-py3-none-win_arm64.whl", hash = "sha256:e51d046cf6dda98a4633b8a8a771451107413b0f07183b2bef03f075599e44e6", size = 13729839, upload-time = "2025-12-18T19:28:48.636Z" }, ] +[[package]] +name = "scipy" +version = "1.18.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a7/25/c2700dfaf6442b4effaa91af24ebce5dc9d31bb4a69706313aae70d72cd0/scipy-1.18.0.tar.gz", hash = "sha256:67b2ad2ad54c72ca6d04975a9b2df8c3638c34ddd5b28738e94fc2b57929d378", size = 30774447, upload-time = "2026-06-19T15:01:43.456Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/05/52/9c0136c2de7ae0779b7b366447766cec6d9f0702c56bb8ffeb04c8fd3af4/scipy-1.18.0-cp313-cp313-macosx_10_15_x86_64.whl", hash = "sha256:09143f676d157d9f546d663504ef9c1becb819824f1afc018814176411942446", size = 31036107, upload-time = "2026-06-19T15:00:14.03Z" }, + { url = "https://files.pythonhosted.org/packages/02/73/0291a64843270f4efb86cdcf2ee0f2048631b65ec6b405398b2b4dbf11bf/scipy-1.18.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:5efe260f69417b97ddae455bfb5a95e8359f7f66ad7fa9522a60feb66f169520", size = 28663303, upload-time = "2026-06-19T15:00:16.819Z" }, + { url = "https://files.pythonhosted.org/packages/d3/0f/10ffa0b697a572f4e0d48b92a88895d366422f019f723e7e14a84c050dac/scipy-1.18.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:68363b7eaacd8b5dd426df56d782cc156468ac79a127a1b87ca597d6e2e82197", size = 20404960, upload-time = "2026-06-19T15:00:19.635Z" }, + { url = "https://files.pythonhosted.org/packages/7e/d2/e896cea21ba8edd6c81d4c55b1ffcc717e79698dcbebf9641b4cfb4c6622/scipy-1.18.0-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:c5557d8be5da8e41353fcd4d21491fdbab83b062fc579e94dc09a7c8ab4f669b", size = 23034074, upload-time = "2026-06-19T15:00:22.107Z" }, + { url = "https://files.pythonhosted.org/packages/ea/b2/e83ea34279a52c03374477c74006256ec78df65fc877baa4617d6de1d202/scipy-1.18.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0d13bca67c096d89fb95ced0d8921807300fce0275643aef9533cc63a0773468", size = 33942038, upload-time = "2026-06-19T15:00:24.964Z" }, + { url = "https://files.pythonhosted.org/packages/f6/af/e8fe5fb136f51e2b01678b92cb4106d10d8cd68ec147ead2e7cb0ac75398/scipy-1.18.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a46f9273dbd0eb1cefba61c9b8648b4dfe3cbc14a080176f9a73e44b8336dc7f", size = 35266390, upload-time = "2026-06-19T15:00:28.059Z" }, + { url = "https://files.pythonhosted.org/packages/3a/49/2c5cbb907b56695fc67517811d1db234dfd83381a84814ec220aded2794d/scipy-1.18.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5aba46108853ddfc77906b6557aac839d2b52e900c1d72a1180adaaab58d265f", size = 35551324, upload-time = "2026-06-19T15:00:31.014Z" }, + { url = "https://files.pythonhosted.org/packages/bb/73/eda39f7a2d306ff0ffc574afd13c0bbb6d10a603d9a413998ee269487a80/scipy-1.18.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b6f758e35f12757b5d95c00bc6de2438e229c2664b7a92e96f205959d9f2dfa4", size = 37404785, upload-time = "2026-06-19T15:00:34.072Z" }, + { url = "https://files.pythonhosted.org/packages/b7/d2/ae881ee28d014f38e0ccbfd974a06a919ba9af34f1f74bf42b5301891d63/scipy-1.18.0-cp313-cp313-win_amd64.whl", hash = "sha256:1afac4a847207c7ff8efd321734a50b06d0280b3b2a2c0fc2f413101747ad7c7", size = 36554943, upload-time = "2026-06-19T15:00:36.903Z" }, + { url = "https://files.pythonhosted.org/packages/70/3a/21154e2d54eb3639c6bf4dbae2e531c68356bfe95990daa30df33b30d556/scipy-1.18.0-cp313-cp313-win_arm64.whl", hash = "sha256:c5dbddf60e58c2312316d097271a8e73d40eaf2eabfa4d95ed7d3695bbf2ce7b", size = 24350911, upload-time = "2026-06-19T15:00:40.062Z" }, + { url = "https://files.pythonhosted.org/packages/78/b5/915a19b3de2f7430062b509653563db1633ddbb6f021b06731521115d4e2/scipy-1.18.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:4c256ee70c0d1a8a2ace807e199ccd4e3f57037433842abb3fb36bc17eaa9578", size = 31036253, upload-time = "2026-06-19T15:00:43.216Z" }, + { url = "https://files.pythonhosted.org/packages/d7/88/b72def7262e150d16be13fca37a96481138d624e700340bc3362a7588929/scipy-1.18.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:2ef3abc54a4ffc53765374b0d5728532dfdd2585ed23f6b11c206a1f0b1b9af8", size = 28673758, upload-time = "2026-06-19T15:00:46.663Z" }, + { url = "https://files.pythonhosted.org/packages/91/02/2e636a61a525632c373cf6a9c24442a3ffb79e364d38e98b32042964ac32/scipy-1.18.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:f2a6af57bd9e4a75d70e4117e78a1bbee84f79ae3fbb6d0111005d6ebcc4cb8d", size = 20415514, upload-time = "2026-06-19T15:00:49.399Z" }, + { url = "https://files.pythonhosted.org/packages/c9/b6/2135974442f6aba159d9d39d774a1c8cb19947016725d69fecc685df45bf/scipy-1.18.0-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:3f1ac564d3bf6c03d861d2cd87a1bea0da2887136f7fb1bf519c05a8971452d6", size = 23034398, upload-time = "2026-06-19T15:00:51.941Z" }, + { url = "https://files.pythonhosted.org/packages/f6/e6/ba89ec5abf6ee9257c0d1ec985573f3ae32742c24bc03e016388a40b1b15/scipy-1.18.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:40395a5fcd1abee49a5c7aaa98c29db393eedc835138560a588c47ec16156690", size = 33998032, upload-time = "2026-06-19T15:00:54.838Z" }, + { url = "https://files.pythonhosted.org/packages/7f/c4/bc41eb19b0fd0db868f4132920879019318d80cc522ad8f2bca4611af808/scipy-1.18.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8ca01e8ae69f1b18e9a58d91afead31be3cef0dd905a10249dac559ee15460a0", size = 35283333, upload-time = "2026-06-19T15:00:58.152Z" }, + { url = "https://files.pythonhosted.org/packages/53/a4/cbdeef6eb3830a8462a9d4ada814de5fc984345cc9ecf17cbec51a036f1e/scipy-1.18.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7a7f3b01647384dbc3a711e8c6778e0aabbe93959249fef5c7393396bcac0867", size = 35610216, upload-time = "2026-06-19T15:01:01.155Z" }, + { url = "https://files.pythonhosted.org/packages/80/4d/b2b82502b65f661d1b789c1665dcdf315d5f12194e06fc0b37946294ebae/scipy-1.18.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6aa94e78ec192a30063a5e72e561c28af769dc311190b24fe91774eff1969709", size = 37418960, upload-time = "2026-06-19T15:01:04.155Z" }, + { url = "https://files.pythonhosted.org/packages/93/3e/902d836831474b0ab5a37d16404f7bc5fafd9efba632890e271ba952635f/scipy-1.18.0-cp314-cp314-win_amd64.whl", hash = "sha256:2d8bbdc6c817f5b4006a54d799d4f5bab6f910193cbb9a1ff310833d4d270f61", size = 37288845, upload-time = "2026-06-19T15:01:07.822Z" }, + { url = "https://files.pythonhosted.org/packages/b6/43/8d73b337a3bdb14daa0314f0434210747c02d79d729ce1777574a817dcf6/scipy-1.18.0-cp314-cp314-win_arm64.whl", hash = "sha256:18e9575f1569b2c54174e6159d32942e03731177f63dce7975f0a0c88d102f5b", size = 24988971, upload-time = "2026-06-19T15:01:11.076Z" }, + { url = "https://files.pythonhosted.org/packages/b4/b4/f11918b0508a2787031a0499a03fbe3546f3bb5ca05d01038c45b278c09a/scipy-1.18.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:f351e0dd702687d12a402b867a1b4146a256923e1c38317cbc472f6372b94707", size = 31399325, upload-time = "2026-06-19T15:01:13.723Z" }, + { url = "https://files.pythonhosted.org/packages/7b/d1/1f287b57c0ff0ee5185dff3946d92c8017d39b0e431f0ae79a3ff1859512/scipy-1.18.0-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:7c7a51b33ce387193c97f228320cf8e87361daa1bba750638677729598b3e677", size = 29092110, upload-time = "2026-06-19T15:01:16.908Z" }, + { url = "https://files.pythonhosted.org/packages/ff/1a/7b74eb6c392fdcb27d414c0e7558a6d0231eb3b6d73571f479bb81ea8794/scipy-1.18.0-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:84031d7b052a54fae2f8632e0ec802073d385476eb9a63079bce6e23ef9283d4", size = 20833811, upload-time = "2026-06-19T15:01:20.488Z" }, + { url = "https://files.pythonhosted.org/packages/7c/ad/f3941716320a7b9cb4d68734a903b45fe16eff5fb7da7e16f2e619304979/scipy-1.18.0-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:56abf29a7c067dde59be8b9a22d606a4ea1b2f2a4b756d9d903c62818f5dacce", size = 23396644, upload-time = "2026-06-19T15:01:23.364Z" }, + { url = "https://files.pythonhosted.org/packages/22/22/1446b62ffe07f9719b7d9b1b6a4e05a772833ae8f441fe4c22c34c9b250f/scipy-1.18.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ad44305cfa24b1ba5803cbbebf033590ccbac1aa5d612d727b785325ab408b0", size = 34079318, upload-time = "2026-06-19T15:01:26.002Z" }, + { url = "https://files.pythonhosted.org/packages/56/3b/b87da667098bb470fa30c7011b0ba351ee976dd395c78798c66e941665a3/scipy-1.18.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:945c1761b93f38d7f99ae81ae80c63e621471608c7eeead563f6df025585cd58", size = 35324320, upload-time = "2026-06-19T15:01:28.881Z" }, + { url = "https://files.pythonhosted.org/packages/f8/a1/c7932f91909759b0267f75fdea34e91309f96b895757534b76a90b6b4344/scipy-1.18.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1a4441f15d620578772a49e5ab48c0ee1f7a0220e387110283062729136b2553", size = 35699541, upload-time = "2026-06-19T15:01:31.968Z" }, + { url = "https://files.pythonhosted.org/packages/f7/86/5185061a1fcc41d18c5dc2463969b3a3964b31d9ac67b2fb05d4c7ff7670/scipy-1.18.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9aac6192fac56bf2ca534389d24623f07b39ff83317d58287285e7fbd622ff76", size = 37472480, upload-time = "2026-06-19T15:01:35.136Z" }, + { url = "https://files.pythonhosted.org/packages/31/8e/f04c68e39919a010d34f2ee1367fd705b0a25a02f609d755f0bfbc0a15fc/scipy-1.18.0-cp314-cp314t-win_amd64.whl", hash = "sha256:e40baea28ae7f5475c779741e2d90b1247c78531207b49c7030e698ff81cee3f", size = 37365390, upload-time = "2026-06-19T15:01:38.091Z" }, + { url = "https://files.pythonhosted.org/packages/d5/19/969dc072906c84dd0a3b05dcf57ea750936087d7873549e408b35cfc3f97/scipy-1.18.0-cp314-cp314t-win_arm64.whl", hash = "sha256:368e0a705903c466aa5f08eefb39e6b1b6b2d659e7352a31fd9e2438365be0f8", size = 25279661, upload-time = "2026-06-19T15:01:40.817Z" }, +] + [[package]] name = "setuptools" version = "82.0.1" From 997aa3a9800befadca966cf0646d5917d261920b Mon Sep 17 00:00:00 2001 From: Rohan Pandula Date: Wed, 22 Jul 2026 15:06:12 -0700 Subject: [PATCH 22/48] feat(ls5000): ship Nikon-exact native Mac roll workflow --- NOTICE.md | 9 + build.py | 363 +++ desktop.py | 10 +- docs/COOLSCANPY_ROLL_SCANNING.md | 325 ++- docs/FAUXICE_IR_REPAIR.md | 164 +- macos/NegPy.entitlements | 8 + .../ls5000-fixed-output-lut-u16le.bin | Bin 0 -> 131072 bytes .../assets/portable_cms/cml4-stage1-clut0.bin | Bin 0 -> 262144 bytes .../portable_cms/cml4-stage1-input-lut0.bin | Bin 0 -> 393216 bytes .../portable_cms/cml4-stage1-output-lut0.bin | Bin 0 -> 393216 bytes .../assets/portable_cms/cml4-stage2-clut0.bin | Bin 0 -> 262144 bytes .../portable_cms/cml4-stage2-input-lut0.bin | Bin 0 -> 393216 bytes .../portable_cms/cml4-stage2-output-lut0.bin | Bin 0 -> 393216 bytes negpy/assets/portable_cms/lch-atan-u16le.bin | Bin 0 -> 16386 bytes .../portable_cms/lch-reciprocal-u16le.bin | Bin 0 -> 131074 bytes .../assets/portable_cms/lch-sincos-i16le.bin | Bin 0 -> 262148 bytes .../portable_cms/portable-oracle-receipt.json | 192 ++ negpy/desktop/controller.py | 59 +- negpy/desktop/frozen_entry.py | 272 ++ negpy/desktop/main.py | 170 +- negpy/desktop/view/main_window.py | 25 + negpy/desktop/view/sidebar/coolscan_roll.py | 114 +- negpy/desktop/view/sidebar/export.py | 4 +- negpy/desktop/workers/roll_worker.py | 167 +- negpy/infrastructure/roll/coolscanpy_roll.py | 45 +- negpy/infrastructure/roll/fauxice_bridge.py | 206 +- negpy/infrastructure/roll/repair.py | 202 +- negpy/infrastructure/roll/settings.py | 22 +- negpy/kernel/system/paths.py | 11 +- negpy/services/assets/crosstalk.py | 31 +- negpy/services/export/contact_sheet.py | 4 +- .../services/repair/fauxice_hybrid_runner.py | 927 ++++++- negpy/services/repair/fauxice_ir_repair.py | 121 +- .../repair/hybrid_runtime_manifest.py | 216 ++ negpy/services/roll/__init__.py | 41 + negpy/services/roll/deep_acceptance.py | 1993 ++++++++++++++ negpy/services/roll/exact_color.py | 1477 ++++++++++ negpy/services/roll/live_acceptance.py | 1015 +++++++ negpy/services/roll/live_reservation.py | 750 +++++ negpy/services/roll/live_review.py | 291 ++ negpy/services/roll/native_builder.py | 740 +++++ negpy/services/roll/nikon_icc.py | 65 + negpy/services/roll/portable_builder.py | 194 ++ negpy/services/roll/portable_cms.py | 510 ++++ .../roll/portable_oracle_evaluator.py | 354 +++ negpy/services/roll/service.py | 2437 ++++++++++++++++- pyproject.toml | 17 +- tests/repair/test_fauxice_hybrid_runner.py | 897 +++++- tests/repair/test_fauxice_ir_repair.py | 233 +- tests/repair/test_hybrid_runtime_manifest.py | 134 + tests/roll/_exact_fixtures.py | 143 + tests/roll/conftest.py | 85 +- tests/roll/test_coolscanpy_roll.py | 88 +- tests/roll/test_deep_acceptance.py | 1143 ++++++++ tests/roll/test_fauxice_bridge.py | 207 ++ tests/roll/test_live_acceptance.py | 1001 +++++++ tests/roll/test_live_reservation.py | 357 +++ tests/roll/test_live_review.py | 201 ++ tests/roll/test_native_builder.py | 1038 +++++++ tests/roll/test_nikon_icc.py | 52 + tests/roll/test_portable_builder.py | 248 ++ tests/roll/test_portable_cms.py | 292 ++ tests/roll/test_repair.py | 69 +- tests/roll/test_service.py | 2156 ++++++++++++++- tests/roll/test_sidebar.py | 239 +- tests/roll/test_stage3_replay_loader.py | 115 + tests/roll/test_worker.py | 244 +- tests/test_contact_sheet.py | 4 +- tests/test_contact_sheet_templates.py | 3 +- tests/test_controller.py | 19 + tests/test_crosstalk_profiles.py | 44 + tests/test_ls5000_frozen_packaging.py | 880 ++++++ tests/test_main_window.py | 48 + tests/test_roll_controller.py | 52 + tests/test_user_dir_resolution.py | 14 + tests/test_wheel_packaging.py | 176 ++ uv.lock | 10 +- 77 files changed, 23088 insertions(+), 655 deletions(-) create mode 100644 macos/NegPy.entitlements create mode 100644 negpy/assets/portable_builder/ls5000-fixed-output-lut-u16le.bin create mode 100644 negpy/assets/portable_cms/cml4-stage1-clut0.bin create mode 100644 negpy/assets/portable_cms/cml4-stage1-input-lut0.bin create mode 100644 negpy/assets/portable_cms/cml4-stage1-output-lut0.bin create mode 100644 negpy/assets/portable_cms/cml4-stage2-clut0.bin create mode 100644 negpy/assets/portable_cms/cml4-stage2-input-lut0.bin create mode 100644 negpy/assets/portable_cms/cml4-stage2-output-lut0.bin create mode 100644 negpy/assets/portable_cms/lch-atan-u16le.bin create mode 100644 negpy/assets/portable_cms/lch-reciprocal-u16le.bin create mode 100644 negpy/assets/portable_cms/lch-sincos-i16le.bin create mode 100644 negpy/assets/portable_cms/portable-oracle-receipt.json create mode 100644 negpy/desktop/frozen_entry.py create mode 100644 negpy/services/repair/hybrid_runtime_manifest.py create mode 100644 negpy/services/roll/deep_acceptance.py create mode 100644 negpy/services/roll/exact_color.py create mode 100644 negpy/services/roll/live_acceptance.py create mode 100644 negpy/services/roll/live_reservation.py create mode 100644 negpy/services/roll/live_review.py create mode 100644 negpy/services/roll/native_builder.py create mode 100644 negpy/services/roll/nikon_icc.py create mode 100644 negpy/services/roll/portable_builder.py create mode 100644 negpy/services/roll/portable_cms.py create mode 100644 negpy/services/roll/portable_oracle_evaluator.py create mode 100644 tests/repair/test_hybrid_runtime_manifest.py create mode 100644 tests/roll/_exact_fixtures.py create mode 100644 tests/roll/test_deep_acceptance.py create mode 100644 tests/roll/test_fauxice_bridge.py create mode 100644 tests/roll/test_live_acceptance.py create mode 100644 tests/roll/test_live_reservation.py create mode 100644 tests/roll/test_live_review.py create mode 100644 tests/roll/test_native_builder.py create mode 100644 tests/roll/test_nikon_icc.py create mode 100644 tests/roll/test_portable_builder.py create mode 100644 tests/roll/test_portable_cms.py create mode 100644 tests/roll/test_stage3_replay_loader.py create mode 100644 tests/test_ls5000_frozen_packaging.py create mode 100644 tests/test_wheel_packaging.py diff --git a/NOTICE.md b/NOTICE.md index a3b1a9c1..6973197c 100644 --- a/NOTICE.md +++ b/NOTICE.md @@ -3,3 +3,12 @@ concepts (continuous defect score, score-weighted multiscale reconstruction, original-floor writer rule, interior-radius routing with feathered compositing) from digital-fauxice, Copyright (c) 2026 Rohan Pandula, MIT License. https://github.com/rohanpandula/digital-fauxice + +The binary tables and portable-oracle validation receipt under +negpy/assets/portable_cms, the fixed output table under +negpy/assets/portable_builder, and the 492-byte output ICC profile embedded in +negpy/services/roll/nikon_icc.py are vendor-derived interoperability data +recovered from the pinned Nikon LS-5000 color path. They are data, not a +redistribution of CML4.dll, LS5000.md3, Nikon Scan, or Wine. Nikon and COOLSCAN +are trademarks of Nikon Corporation; this project is not affiliated with or +endorsed by Nikon. diff --git a/build.py b/build.py index 3cf4a25e..8dd55419 100644 --- a/build.py +++ b/build.py @@ -1,8 +1,14 @@ +import ctypes import glob +import importlib +import importlib.util import os import platform +import plistlib +import re import shutil import subprocess +from pathlib import Path import PyInstaller.__main__ @@ -24,6 +30,143 @@ is_macos = system == "Darwin" is_linux = system == "Linux" +COOLSCAN_FEATURE_ENV = "NEGPY_ENABLE_COOLSCAN" +COOLSCAN_LIBUSB_ENV = "NEGPY_LIBUSB_DYLIB" +MACOS_CODESIGN_IDENTITY_ENV = "NEGPY_CODESIGN_IDENTITY" +MACOS_BUNDLE_IDENTIFIER = "org.negpy.NegPy" +MACOS_ENTITLEMENTS_FILE = str(Path(__file__).resolve().parent / "macos" / "NegPy.entitlements") +MACOS_RUNTIME_ENTITLEMENTS = { + "com.apple.security.cs.allow-unsigned-executable-memory": True, +} +MACOS_CS_RUNTIME_FLAG = 0x10000 +COOLSCAN_LIBUSB_DESTINATION = "coolscanpy/_native" +COOLSCAN_LIBUSB_FILENAME = "libusb-1.0.dylib" +CAPTURE_HELPER_FLAG = "--ls5000-capture-helper" +LIVE_ACCEPTANCE_FLAG = "--ls5000-live-acceptance" +PACKAGING_SMOKE_FLAG = "--negpy-packaging-smoke" +PACKAGING_SMOKE_OK = "NegPy frozen scanner packaging smoke passed" + + +def _coolscan_support_enabled() -> bool: + """Enable the optional frozen scanner seam when its build group is present.""" + + configured = os.environ.get(COOLSCAN_FEATURE_ENV) + available = importlib.util.find_spec("coolscanpy") is not None + if configured is None: + enabled = available + elif configured.strip().lower() in {"1", "true", "yes", "on"}: + enabled = True + elif configured.strip().lower() in {"0", "false", "no", "off"}: + enabled = False + else: + raise RuntimeError(f"{COOLSCAN_FEATURE_ENV} must be one of 1/0, true/false, yes/no, or on/off") + + if enabled and not available: + raise RuntimeError("Coolscan support was requested but coolscanpy is unavailable; sync the coolscan-roll build group first") + if enabled and importlib.util.find_spec("portable_digital_ice") is None: + raise RuntimeError("Coolscan support was requested but portable_digital_ice is unavailable; sync the fauxice build group first") + return enabled + + +def resolve_macos_libusb_dylib() -> str: + """Resolve one absolute, architecture-compatible libusb 1.0 dylib.""" + + override = os.environ.get(COOLSCAN_LIBUSB_ENV) + if override: + if not os.path.isabs(override): + raise RuntimeError(f"{COOLSCAN_LIBUSB_ENV} must be an absolute path") + candidates = (override,) + elif platform.machine() == "arm64": + candidates = ( + "/opt/homebrew/opt/libusb/lib/libusb-1.0.dylib", + "/opt/local/lib/libusb-1.0.dylib", + "/usr/local/opt/libusb/lib/libusb-1.0.dylib", + ) + else: + candidates = ( + "/usr/local/opt/libusb/lib/libusb-1.0.dylib", + "/opt/local/lib/libusb-1.0.dylib", + "/opt/homebrew/opt/libusb/lib/libusb-1.0.dylib", + ) + + rejected: list[str] = [] + for candidate in candidates: + # Preserve the stable alias instead of resolving Homebrew's symlink to + # libusb-1.0.0.dylib: PyInstaller uses the source basename at the + # requested destination, and the frozen resolver requires this exact + # package-owned filename. + path = Path(os.path.abspath(candidate)) + if not path.is_file(): + rejected.append(f"{candidate} (missing)") + continue + if path.name != COOLSCAN_LIBUSB_FILENAME: + rejected.append(f"{path} (filename must be {COOLSCAN_LIBUSB_FILENAME})") + continue + architecture = subprocess.run( + ["/usr/bin/lipo", str(path), "-verify_arch", platform.machine()], + check=False, + capture_output=True, + text=True, + ) + if architecture.returncode != 0: + rejected.append(f"{path} (wrong architecture)") + continue + return str(path) + + details = "; ".join(rejected) + raise RuntimeError( + "Coolscan support requires an architecture-compatible libusb 1.0 dylib. " + f"Install it with Homebrew or set {COOLSCAN_LIBUSB_ENV} to an absolute path. " + f"Checked: {details}" + ) + + +COOLSCAN_SUPPORT_ENABLED = _coolscan_support_enabled() +MACOS_LIBUSB_SOURCE = resolve_macos_libusb_dylib() if is_macos and COOLSCAN_SUPPORT_ENABLED else None +MACOS_CODESIGN_IDENTITY = os.environ.get(MACOS_CODESIGN_IDENTITY_ENV, "-").strip() +if is_macos and not MACOS_CODESIGN_IDENTITY: + raise RuntimeError(f"{MACOS_CODESIGN_IDENTITY_ENV} must not be empty") + +_REQUIRED_COOLSCAN_API = { + "coolscanpy": ( + "DigitalIceAcquisition", + "DigitalIceAcquisitionEvidence", + "build_digital_ice_acquisition_evidence", + ), + "coolscanpy.protocol.ls5000_single_pass.density": ( + "NikonDensityEvidence", + "NikonExactBuilderEvidence", + "build_nikon_density_evidence", + "build_nikon_exact_builder_evidence", + ), + "coolscanpy.protocol.ls5000_single_pass.bundle": ("verify_capture_bundle",), +} + + +def preflight_coolscan_runtime() -> str: + """Reject a stale or unsealed Coolscan checkout before PyInstaller runs.""" + + loaded: dict[str, object] = {} + missing: list[str] = [] + for module_name, symbols in _REQUIRED_COOLSCAN_API.items(): + module = importlib.import_module(module_name) + loaded[module_name] = module + missing.extend(f"{module_name}.{symbol}" for symbol in symbols if not callable(getattr(module, symbol, None))) + if missing: + raise RuntimeError("Coolscan build API is stale or incomplete: " + ", ".join(missing)) + + bundle_module = loaded["coolscanpy.protocol.ls5000_single_pass.bundle"] + verify_capture_bundle = getattr(bundle_module, "verify_capture_bundle") + bundle_sha256 = verify_capture_bundle(require_python_sources=True) + if ( + not isinstance(bundle_sha256, str) + or len(bundle_sha256) != 64 + or any(character not in "0123456789abcdef" for character in bundle_sha256) + ): + raise RuntimeError("Coolscan capture bundle returned an invalid SHA-256 identity") + return bundle_sha256 + + # Basic PyInstaller arguments params = [ ENTRY_POINT, @@ -32,6 +175,9 @@ "--windowed", # GUI app, no console "--clean", "--noconfirm", + *([f"--osx-bundle-identifier={MACOS_BUNDLE_IDENTIFIER}"] if is_macos else []), + *([f"--osx-entitlements-file={MACOS_ENTITLEMENTS_FILE}"] if is_macos else []), + *([f"--codesign-identity={MACOS_CODESIGN_IDENTITY}"] if is_macos and MACOS_CODESIGN_IDENTITY != "-" else []), # Hidden imports "--hidden-import=rawpy", "--hidden-import=cv2", @@ -47,6 +193,11 @@ "--hidden-import=jinja2", "--hidden-import=PyQt6", "--hidden-import=qtawesome", + # The frozen app re-enters itself with this worker flag. Keep the worker + # explicit even though --collect-all also finds it, so that entry contract + # cannot disappear in a future package layout change. + *(["--hidden-import=coolscanpy.protocol.ls5000_single_pass.worker"] if COOLSCAN_SUPPORT_ENABLED else []), + *(["--hidden-import=negpy.services.roll.live_acceptance"] if COOLSCAN_SUPPORT_ENABLED else []), # Scanner support: bundle the python-sane C extension but NOT libsane.so.1. # libsane.so.1 must come from the host so SANE can find its backend plugins # in /usr/lib/sane/. See libs_to_remove in package_linux(). @@ -63,6 +214,9 @@ "--collect-all=rawpy", "--collect-all=imageio", "--collect-all=imagecodecs", + *(["--collect-all=coolscanpy"] if COOLSCAN_SUPPORT_ENABLED else []), + "--collect-all=portable_digital_ice", + *([f"--add-binary={MACOS_LIBUSB_SOURCE}:{COOLSCAN_LIBUSB_DESTINATION}"] if MACOS_LIBUSB_SOURCE is not None else []), # Data files "--add-data=negpy/features/exposure/shaders:negpy/features/exposure/shaders", "--add-data=negpy/features/geometry/shaders:negpy/features/geometry/shaders", @@ -71,6 +225,9 @@ "--add-data=negpy/features/lab/shaders:negpy/features/lab/shaders", "--add-data=negpy/features/finish/shaders:negpy/features/finish/shaders", "--add-data=negpy/desktop/view/styles:negpy/desktop/view/styles", + "--add-data=negpy/assets/portable_builder:negpy/assets/portable_builder", + "--add-data=negpy/assets/portable_cms:negpy/assets/portable_cms", + "--add-data=negpy/services/roll/portable_oracle_evaluator.py:negpy/services/roll", "--add-data=icc:icc", "--add-data=media:media", "--add-data=crosstalk:crosstalk", @@ -348,12 +505,218 @@ def package_macos(): shutil.rmtree(temp_dmg_dir) +def verify_macos_runtime_entitlements(app_path: str) -> dict[str, bool]: + """Fail closed unless the signed host has the one required JIT exception.""" + + app_path = os.path.abspath(app_path) + completed = subprocess.run( + [ + "/usr/bin/codesign", + "--display", + "--entitlements", + "-", + "--xml", + app_path, + ], + check=True, + capture_output=True, + ) + try: + embedded = plistlib.loads(completed.stdout) + except (plistlib.InvalidFileException, ValueError, TypeError) as error: + raise RuntimeError("signed NegPy app has unreadable hardened-runtime entitlements") from error + if embedded != MACOS_RUNTIME_ENTITLEMENTS: + raise RuntimeError( + f"signed NegPy app has unexpected hardened-runtime entitlements: expected {MACOS_RUNTIME_ENTITLEMENTS!r}, got {embedded!r}" + ) + return embedded + + +def verify_macos_hardened_runtime(app_path: str) -> int: + """Fail closed unless the host CodeDirectory enables hardened runtime.""" + + app_path = os.path.abspath(app_path) + completed = subprocess.run( + ["/usr/bin/codesign", "--display", "--verbose=4", app_path], + check=True, + capture_output=True, + text=True, + ) + details = f"{completed.stdout}\n{completed.stderr}" + match = re.search(r"\bflags=0x([0-9a-fA-F]+)\b", details) + flags = int(match.group(1), 16) if match is not None else 0 + if flags & MACOS_CS_RUNTIME_FLAG == 0: + raise RuntimeError("signed NegPy app is missing the hardened-runtime CodeDirectory flag") + return flags + + +def sign_macos_scanner_runtime(app_path: str) -> str: + """Sign the bundled libusb dylib, reseal the app, and verify both.""" + + app_path = os.path.abspath(app_path) + + frameworks_libusb = os.path.join( + app_path, + "Contents", + "Frameworks", + COOLSCAN_LIBUSB_DESTINATION, + COOLSCAN_LIBUSB_FILENAME, + ) + resources_libusb = os.path.join( + app_path, + "Contents", + "Resources", + COOLSCAN_LIBUSB_DESTINATION, + COOLSCAN_LIBUSB_FILENAME, + ) + bundled_libusb = next( + (path for path in (frameworks_libusb, resources_libusb) if os.path.isfile(path)), + None, + ) + if bundled_libusb is None: + raise RuntimeError( + f"PyInstaller did not place the required libusb dylib at {COOLSCAN_LIBUSB_DESTINATION}/{COOLSCAN_LIBUSB_FILENAME}" + ) + + identity = os.environ.get(MACOS_CODESIGN_IDENTITY_ENV, "-").strip() + if not identity: + raise RuntimeError(f"{MACOS_CODESIGN_IDENTITY_ENV} must not be empty") + timestamp_option = "--timestamp=none" if identity == "-" else "--timestamp" + codesign = "/usr/bin/codesign" + + subprocess.run( + [codesign, "--force", "--sign", identity, timestamp_option, bundled_libusb], + check=True, + ) + subprocess.run( + [ + codesign, + "--force", + "--sign", + identity, + timestamp_option, + # PyInstaller applies this same policy to the initial bundle + # signature. Reapply it explicitly here because changing the + # bundled libusb invalidates the outer seal; relying on ambient + # entitlement preservation made Developer ID builds fragile. + "--entitlements", + MACOS_ENTITLEMENTS_FILE, + "--preserve-metadata=requirements,flags,runtime", + app_path, + ], + check=True, + ) + subprocess.run( + [codesign, "--verify", "--strict", "--verbose=2", bundled_libusb], + check=True, + ) + subprocess.run( + [codesign, "--verify", "--deep", "--strict", "--verbose=2", app_path], + check=True, + ) + verify_macos_runtime_entitlements(app_path) + if identity != "-": + verify_macos_hardened_runtime(app_path) + return bundled_libusb + + +def smoke_macos_scanner_runtime(app_path: str) -> dict[str, str]: + """Exercise the signed frozen seams without opening Qt or enumerating USB.""" + + app_path = os.path.abspath(app_path) + + executable = os.path.join(app_path, "Contents", "MacOS", APP_NAME) + bundled_libusb = os.path.join( + app_path, + "Contents", + "Frameworks", + COOLSCAN_LIBUSB_DESTINATION, + COOLSCAN_LIBUSB_FILENAME, + ) + if not os.path.isfile(executable) or not os.access(executable, os.X_OK): + raise RuntimeError(f"frozen NegPy executable is missing or not executable: {executable}") + if not os.path.isfile(bundled_libusb): + raise RuntimeError(f"frozen libusb is missing: {bundled_libusb}") + + # Loading the library and resolving symbols does not initialize a libusb + # context and therefore cannot enumerate or claim any USB device. + libusb = ctypes.CDLL(bundled_libusb) + for symbol in ("libusb_init", "libusb_exit"): + if not hasattr(libusb, symbol): + raise RuntimeError(f"frozen libusb is missing required symbol {symbol}") + + environment = os.environ.copy() + environment.pop("PYTHONHOME", None) + environment.pop("PYTHONPATH", None) + for variable in ( + "DYLD_FALLBACK_FRAMEWORK_PATH", + "DYLD_FALLBACK_LIBRARY_PATH", + "DYLD_FRAMEWORK_PATH", + "DYLD_INSERT_LIBRARIES", + "DYLD_LIBRARY_PATH", + ): + environment.pop(variable, None) + + def run_offline(arguments: list[str], *, label: str) -> str: + if LIVE_ACCEPTANCE_FLAG in arguments: + raise RuntimeError(f"{label} attempted to enable live acceptance") + if "--live" in arguments: + raise RuntimeError(f"{label} attempted to enable live scanner access") + completed = subprocess.run( + [executable, *arguments], + cwd=os.path.dirname(app_path), + env=environment, + check=False, + capture_output=True, + text=True, + timeout=120, + ) + if completed.returncode != 0: + raise RuntimeError( + f"{label} failed with exit code {completed.returncode}\nstdout:\n{completed.stdout}\nstderr:\n{completed.stderr}" + ) + return completed.stdout + + packaging_output = run_offline([PACKAGING_SMOKE_FLAG], label="frozen import/assets smoke") + if PACKAGING_SMOKE_OK not in packaging_output: + raise RuntimeError("frozen import/assets smoke did not emit its success receipt") + + helper_output = run_offline( + [ + CAPTURE_HELPER_FLAG, + "--frame", + "18", + "--boundary-offset-rows", + "0", + "--confirm-full-capture", + ], + label="frozen LS-5000 helper dry-run", + ) + for expected in ("619458560 bytes", "dry run only; scanner was not accessed"): + if expected not in helper_output: + raise RuntimeError(f"frozen LS-5000 helper dry-run did not emit {expected!r}") + + return { + "executable": executable, + "libusb": bundled_libusb, + "packaging_output": packaging_output, + "helper_output": helper_output, + } + + def build(): print(f"Building {APP_NAME} for {system}...") + if COOLSCAN_SUPPORT_ENABLED: + bundle_sha256 = preflight_coolscan_runtime() + print(f"Verified Coolscan capture bundle: {bundle_sha256}") print("PyInstaller parameters:", params) PyInstaller.__main__.run(params) + if is_macos and COOLSCAN_SUPPORT_ENABLED: + sign_macos_scanner_runtime(os.path.join("dist", f"{APP_NAME}.app")) + smoke_macos_scanner_runtime(os.path.join("dist", f"{APP_NAME}.app")) + print("Build complete.") if os.path.exists("dist"): print(f"Contents of dist: {os.listdir('dist')}") diff --git a/desktop.py b/desktop.py index 77cfeedb..d4716488 100644 --- a/desktop.py +++ b/desktop.py @@ -1,7 +1,6 @@ import sys import io import faulthandler -from negpy.desktop.main import main def init_streams(): @@ -26,4 +25,11 @@ def flush(self): faulthandler.enable() except Exception: pass - main() + # Frozen scanner helpers and the offline packaging smoke must not import + # the Qt desktop. Keep this dispatch before the GUI module import. + from negpy.desktop.frozen_entry import dispatch_frozen_auxiliary + + if not dispatch_frozen_auxiliary(sys.argv): + from negpy.desktop.main import main + + main() diff --git a/docs/COOLSCANPY_ROLL_SCANNING.md b/docs/COOLSCANPY_ROLL_SCANNING.md index 09a52b86..249360fe 100644 --- a/docs/COOLSCANPY_ROLL_SCANNING.md +++ b/docs/COOLSCANPY_ROLL_SCANNING.md @@ -4,9 +4,8 @@ This page describes NegPy's optional integration with [coolscanpy](https://github.com/rohanpandula/coolscanpy), a standalone Python library for whole-roll scanning on a Nikon Coolscan LS-5000 with a roll feeder. coolscanpy talks to the scanner directly over USB and does not -need SANE installed. NegPy consumes it the same way it consumes -python-sane or gphoto2 elsewhere in this package: as an optional dependency -that the rest of the application never requires. +need SANE for the accepted color-negative path. NegPy consumes coolscanpy as +an optional dependency that the rest of the application never requires. ## What it adds @@ -19,11 +18,10 @@ correction, approval of any frame the transport could not place automatically, and batch scanning with per-frame receipts. This integration exposes all of it inside NegPy, including a desktop panel. -Coverage is narrower than the plain Scan panel. Only color negative film -scans through coolscanpy's roll engine end to end today. Black and white -negative previews and approves normally, but batch fine scanning it raises -`NotImplementedError` inside coolscanpy itself. That limitation is -documented in the coolscanpy README and is unchanged by this integration. +Color-negative roll scanning is the accepted path. Conventional silver +black-and-white fine scanning remains explicitly refused by this pinned +release rather than silently taking a different capture route; the current +sidebar opens rolls as color negative. ## Install @@ -39,15 +37,44 @@ gphoto2: uv sync --group coolscan-roll ``` +That group pins the reviewed immutable commit +`733151d25b8e962e3d5cb6ac1a6989315dc4e1ed`, built on the merged streaming +finalization from [PR #1](https://github.com/rohanpandula/coolscanpy/pull/1). +The pin includes exact USB-topology ownership, six-strip leading-edge +handling, Nikon density/exact-builder evidence, and Digital ICE acquisition +evidence. Its complete Coolscan suite passed before the pin moved, and +`build.py` independently rejects a stale API or capture-bundle hash before +packaging. The pin can move to a tagged release once those APIs are published. + +The local macOS build may use the default ad-hoc code-signing identity (`-`) +for on-machine integrity and testing. That is not publisher identity and does +not make the DMG a notarized release. A distributable build must use a real +Developer ID Application identity through `NEGPY_CODESIGN_IDENTITY`, pass the +same offline frozen smoke, and then complete Apple's notarization and stapling +workflow. `build.py` currently signs and verifies the app bundle but does not +claim to perform that final notarization step. + +The hardened Developer ID build carries one runtime exception: +`com.apple.security.cs.allow-unsigned-executable-memory`. Numba's llvmlite +runtime allocates anonymous read/write pages and later changes them to +read/execute; LLVM 20 does not use `MAP_JIT` for that path, so Apple's +`allow-jit` entitlement is not applicable. The build intentionally does not +disable library validation, executable-page protection, or DYLD protections. +PyInstaller applies the entitlement to the initial bundle seal, the libusb +post-processing step reapplies it explicitly, and the build reads the signed +entitlement and hardened-runtime flag back before running the offline smoke. +That smoke explicitly executes llvmlite's anonymous RW-to-RX allocation check, +so a signed build fails before release if the JIT exception is absent. + If you manage your own environment instead, install the package directly: ``` pip install coolscanpy ``` -coolscanpy has no SANE dependency for the roll workflow. See the -coolscanpy README if you also want its plain single-frame `scan()` path, -which does need SANE. +The accepted color-negative roll workflow has no SANE dependency. +coolscanpy's separate plain single-frame `scan()` path may require SANE; see +the coolscanpy README for that setup. ## Hardware support @@ -88,11 +115,27 @@ the plain-SANE scanner packages: to disk across the three output tiers described below. It never imports coolscanpy itself, only the adapter above. - `negpy/infrastructure/roll/repair.py` is the Tier-2 repair engine seam: - an `available()` check and a `register_engine()` entry point, with no - engine registered by default. See "Output tiers" below. + an `available()` check and a `register_engine()` entry point. The + `fauxice_bridge` registers the portable Digital ICE engine when its pinned + core runtime is installed. The macOS parity build treats a missing + registration as a frozen-smoke failure rather than silently disabling + repair. See "Output tiers" below. - `negpy/services/roll/positive.py` renders Tier 3. It calls `ImageProcessor.run_pipeline`, the same in-memory entry point NegPy's own export pipeline uses after decoding a file, directly on a Tier-2 buffer. +- `negpy/services/roll/exact_color.py` is the separate fail-closed seam for + portable Nikon-exact color. It contains no builder tables or CML4 math. +- `negpy/services/roll/portable_builder.py` is an evidence/replay bridge. It + applies three pre-F builder LUTs captured and validated together by the + Stage-3 Windows oracle in the pinned order + `F[B_c(i)]`, using the 131,072-byte fixed LS5000.md3 post-F table. It + hashes both the repaired source and computed CML Stage-1 input. It is not + the final macOS-native per-scan builder. +- `negpy/services/roll/portable_cms.py` is the production adapter for the + verified DLL-free CML4 Stage-1/Stage-2 evaluator. It loads nine pinned + binary tables (2,506,760 bytes total), verifies every SHA-256 before use, + and evaluates full frames in bounded chunks. Its integer evaluator is a + byte-identical copy of the independently validated oracle source. - `negpy/desktop/workers/roll_worker.py` defines `RollWorker`, a `QObject` moved to its own thread, mirroring `CaptureWorker`. It opens a device's roll extension lazily the first time a preview or batch scan names it, @@ -105,8 +148,9 @@ the plain-SANE scanner packages: A batch scan can produce up to three tiers of output per slot, plus one receipt. Each tier is a separate on/off setting in the sidebar, and any -combination is valid. See "Output tiers" below for what each tier is, why -the defaults are set the way they are, and what happens when a tier cannot +combination is valid. A new profile enables all three, selects Hybrid repair, +and selects Nikon-exact color; existing saved choices are preserved. See +"Output tiers" below for what each tier is and what happens when a tier cannot be produced. The filename pattern is the same Jinja2 template the plain Scan panel @@ -119,11 +163,54 @@ copy beside them. This holds for every tier. Only a written Tier 1 (unrepaired) RGB TIFF is handed to NegPy's asset discovery once a batch scan finishes. Tier 2 and Tier 3 are written to disk when selected, but neither is opened as a NegPy asset automatically. -The receipt is not opened as an asset either. The infrared confidence mask -coolscanpy also returns per frame is not written to disk at all. Nothing -downstream currently reads it, and adding a file format for it before -there is a consumer would be guessing at a convention rather than -following one. +The receipt is not opened as an asset either. The scanner-native Digital ICE +prepass and infrared-validity array are not part of that Tier-1 TIFF pair. +They are consumed from the frame-bound acquisition while the scan is being +written and retained in the movable acquisition archive described below. +Hybrid output retains its disclosure mask and verified receipt, but that is +provenance for the result, not a replacement input from which a later repair +can be reconstructed. + +### Movable Digital ICE acquisition archive + +When Tier 1 includes infrared, NegPy also tries to retain the complete input +set needed to reconstruct that frame's `RepairAcquisition`. The frame receipt +records the result under `outputs.repair_acquisition_evidence`. A successful +retention has `retained: true`, `replayable: true`, and schema +`negpy.dice-acquisition-replay-v1`. + +The retained set contains: + +- `acquisition-binding.json`, represented by a `binding` object with `path`, + `bytes`, and `sha256`; +- `prepass.rgbi16.npy` and `ir-validity.npy`, listed under `artifacts` as the + scanner-native prepass and validity map; and +- the Tier-1 RGB and infrared TIFFs, listed under `sources` in + `upright-storage` orientation. + +Artifact and source rows include relative paths as well as their content and +layout bindings. The binding's `replay.requires` list names all five required +inputs: `storage_rgb_tiff`, `storage_ir_tiff`, `prepass_rgbi`, `ir_validity`, +and `acquisition_provenance`. Replay stacks the upright-storage RGB and +infrared planes, then applies `rot90(k=-1)` to reconstruct the scanner-native +main RGBI array. The loader re-derives the producer acquisition identity and +evidence hash before returning it. + +`load_repair_acquisition_evidence` accepts the retained binding file. Relative +source paths make the archive movable when the whole output archive—including +the Tier-1 TIFFs and its hidden evidence directories—is moved together. Moving +only the binding directory, or only the TIFF pair, is incomplete. After a +move, pass `acquisition-binding.json` at its new location; the capture-time +absolute `binding.path` in the outer frame receipt does not relocate itself. +The contract describes its authenticity as `integrity-bound-not-signed`: +hashes and canonical producer bindings detect changed or mismatched inputs, +but do not claim a cryptographic publisher signature. + +Retention is fail-soft for the irreplaceable Tier-1 output. If any unique +input cannot be validated or retained, the RGB/IR TIFF pair can still be +published while `retained: false`, `replayable: false`, and an `unavailable` +status explain why repair replay was withheld. That TIFF pair remains useful +as the scanner master, but is still insufficient by itself for parity repair. ## Output tiers @@ -138,63 +225,154 @@ scratch repair applied, still scanner-linear and still a negative. It is written as `_repaired.tif` plus `_repaired_IR.tif`. That infrared sidecar is Tier 1's own infrared plane, unchanged, not a repaired version of it. Repair consumes infrared to find defects; it does -not produce a new infrared image. Keeping the original lets a later repair -pass, run under a different mode, start from the same evidence. - -Repair runs in one of two modes. Exact mode heals only the pixels the -infrared channel confidently flags as a defect. Hybrid mode additionally -routes severe zero-signal regions, places where the infrared channel gives -no usable reading at all, to an inpainting model. Both modes are -deterministic: the same frame and the same mode always repair to the same -result. That is what makes Tier 2 worth caching. Once a frame has been -repaired under a given mode, repairing it again produces an identical -file, so there is no need to redo the work. Exact mode is expected to take -about 10 seconds per frame. Hybrid mode is expected to take 70 to 210 -seconds per frame, since inpainting is far slower than the exact-match -heal. - -Tier 3 is the positive: Tier 2 inverted through NegPy's own -negative-to-positive rendering pipeline, the same pipeline a freshly -imported negative reaches the first time it is opened in NegPy. It is -written as `_positive.tif`. Tier 3 always derives from Tier 2's -result in memory, never from a Tier 1 or Tier 2 file already on disk, and -never from Tier 1 directly. Selecting Tier 3 without Tier 2 still runs -repair; the repaired result is just not written to disk on its own. Tier 3 -defaults off. +not produce a new infrared image. Keeping the original infrared plane is +still useful archival evidence, but it is not sufficient for a later parity +repair without the bound prepass, validity data, and acquisition provenance. + +Repair runs in one of two modes. Exact mode uses the pinned portable Digital +ICE runtime over the bound scanner-native main pass, 285-dpi prepass, and +infrared-validity evidence. Hybrid mode additionally routes severe +zero-signal regions, where the infrared channel gives no usable reading, to +the separately pinned inpainting runtime. The receipt records the selected +backend, resolved mode, hashes, and (for hybrid) disclosure evidence. Runtime +depends heavily on backend, frame geometry, and the hybrid routing/model, so +the integration does not promise a fixed time or make a blanket +byte-identical claim for every hybrid environment. + +Tier 3 is the positive. The Roll Scanning service and its persisted +**Positive color** sidebar choice default to `nikon-exact` for the Nikon C-41 +parity workflow. `negpy-approximate` runs only when explicitly selected and +remains visibly labeled as a preview path: Tier 2 is inverted through NegPy's +own negative-to-positive rendering pipeline, the same pipeline a freshly +imported negative reaches the first time it is opened in NegPy. It is never +claimed or labeled as Nikon Scan parity. + +The service also exposes a fail-closed `nikon-exact` mode. NegPy ships two +separate DLL-free adapters: `PortableStage1Builder`, which applies either a +validated Stage-3 replay receipt or a freshly derived native receipt, and +`PortableCMSOnEvaluator` for the two captured CML4 stages. Neither uses a Wine +process, CML4 DLL, scanner, VM, or external repository at runtime. + +The native receipt boundary accepts the 97-dpi density result only as an +explicit pair on the returned frame: `Frame.nikon_density_evidence` plus +`Frame.nikon_density_ownership`, mirrored by +`Frame.receipt.nikon_density_ownership`. The ownership receipt binds the same +reservation and batch, preview bytes and preview identity, transport table, +reviewed and fresh registration fingerprints, frame attempt, one-based batch +index, and selected slot. A new preview or reservation, re-registration, film +movement, eject/refeed, changed transport identity, missing field, or mismatch +between the frame and its public receipt makes exact color unavailable. There +is deliberately no generic Roll/session evidence cache that can be promoted +later. + +In the current local reviewed overlay, a C-41 frame also carries +`nikon_exact_builder_evidence`, which binds its settled 285-dpi analyzer +raster and final exposure triplet to the same ownership pair. `Frame` +construction revalidates the ownership, density, builder, and Digital ICE +bindings before NegPy can consume them. The published streaming-only Git pin +does not yet produce these fields; a clean build therefore fails its Coolscan +API preflight instead of silently publishing an app that cannot complete the +native exact path. The Stage-3 replay route remains independently usable with +an explicit validated replay receipt. + +The Stage-3 replay bridge is deliberately not presented as the production +builder architecture. The macOS-native path derives fresh pre-F LUTs for each +acquisition from coolscanpy's settled, frame-bound meter and analyzer evidence. +Reusing a previously captured Windows pre-F set is useful for evidence-bound +replay and regression testing only. + +The builder receipt can only be created by the file-backed trusted loader. It +uses stable, non-symlink reads and requires the complete PASS summary, an empty +error list, the exact 15-file artifact inventory, the pinned LS5000.md3 module +and resource, and complete observer source/executable provenance. The immutable +builder envelope binds the raw Stage-3 PASS report and its +SHA-256, the three 131,072-byte pre-F LUT blobs and their hashes, and the +pinned fixed post-F LUT identity. The builder computes the Stage-1 input only +after Fauxice repair; that future full-frame hash is output evidence in the +builder-application receipt, not something the earlier prescan receipt is +asked to predict. The CMS receipt separately binds that computed Stage-1 +input and final output. NegPy independently checks each receipt and content +binding before writing. Missing, malformed, unattested, or tampered evidence +produces an `unavailable` receipt entry; it never falls back to the +approximate renderer under an exact label. + +The frozen app and wheel also carry the original 4,014-byte portable-oracle +validation receipt, not only a copied summary of its result. Startup checks it +against SHA-256 +`edf6f3f89158810f1de4ce3b4ff8938326bc50e1b3035af59af472258e7d95e8` +and a closed schema before the CMS adapter can exist. The production CMS +receipt derives and binds the verified totals: 12 events, 265,440 active +16-bit values with zero mismatches, and 698,880 full-payload bytes with zero +mismatched bytes. + +Native same-acquisition builder evidence is an acquisition artifact, not a +side effect of rendering Tier 3. Whenever a frame carries it, NegPy validates +and stages the canonical density receipt, frame-ownership receipt, analyzer +raster, combined builder-evidence JSON, and three derived pre-F blobs before +attempting repair or the positive. The frame sidecar records that result under +`outputs.native_color_evidence`, including when Tier 3 was not selected or +failed. A successful native exact positive points at that already retained +artifact. A successful replay exact positive instead retains its raw Stage-3 +JSON and three pre-F blobs as part of the exact result. Every retained path, +byte size, and SHA-256 is recorded, and the frame receipt is published last. +The transaction rolls back errors observed by the process; it is not described +as a power-loss durability guarantee. These color-builder artifacts do not +replace Digital ICE's bound 285-dpi prepass, infrared-validity data, or repair +provenance, so Tier 1 TIFF and IR files alone still cannot reconstruct a later +parity repair. + +A successful exact positive also embeds Nikon Scan's exact 492-byte +`Nikon Adobe RGB 4.0.0.3000` profile in TIFF tag 34675. The source-embedded +profile is checked against SHA-256 +`a8d0d753bd6129357cc2647435ce675e8637a679eb526fa180fba460874ce1d3` +before use, and the sidecar binds its name, byte size, and hash. Unrepaired, +repaired, and `negpy-approximate` TIFFs keep their existing untagged behavior. +The exact sidecar also records the finished TIFF's file hash, decoded-pixel +hash, geometry, bit depth, planar layout, and embedded-profile hash after +reopening it. Here “exact Nikon color” means the color-stage RGB values and +ICC identity are exact; it does not claim that NegPy's TIFF compression, +secondary pages, or unrelated application metadata are byte-identical to a +Nikon Scan-written TIFF container. + +The positive is written as `_positive.tif`. Tier 3 always +derives from Tier 2's result in memory, never from a Tier 1 or Tier 2 file +already on disk, and never from Tier 1 directly. Selecting Tier 3 without +Tier 2 still runs repair; the repaired result is just not written to disk +on its own. Tier 3 is enabled in the first-run parity defaults; a saved user +choice to disable it is preserved. Repair, when it can run, always runs before inversion: capture, then repair, then invert. That way Tier 3 benefits from whatever Tier 2 was able to fix, rather than inverting an uncorrected frame. -The defaults follow from an asymmetry between the tiers. Tier 1 is the -only tier the scanner can produce, so losing it is permanent. Tier 2 is -expensive to compute but, once computed under a given mode, never needs -recomputing. Tier 3 is cheap to compute, and NegPy's color rendering is -still being tuned, so a Tier 3 file written today is expected to look -different from what the same negative renders to later. Treat a Tier 3 -file as a current preview, not as a finished edit. Tier 1 defaults on -because turning it off risks losing data that only the scanner can -reproduce. Tier 2 and Tier 3 default off because both can be regenerated -from Tier 1 at any time, once a repair engine is registered. - -No repair engine ships with this integration today. `write_frame` checks -whether one is registered before attempting Tier 2, and records a plain -status in the receipt instead of raising when none is available. Since -Tier 3 always depends on Tier 2's result, Tier 3 degrades the same way -whenever repair is unavailable, even if Tier 2 itself was not selected for -writing. Tier 1 still writes when selected, regardless of what happens to -Tier 2 or Tier 3. If NegPy's own rendering pipeline fails for any reason, -Tier 3 degrades on its own, and Tier 1 and Tier 2 are unaffected. +The first-run parity defaults enable all three tiers, choose Hybrid repair, +and choose Nikon-exact color. Tier 1 remains enabled because it is the archival +scanner output. Repaired and Positive run while the frame-bound 285-dpi +prepass, infrared-validity data, acquisition provenance, and native +color-builder evidence are still available. Existing saved settings continue +to win over these defaults, and users may opt out of derived tiers to reduce +compute or storage. A later Tier-1-only reprocessing job must not invent the +missing evidence or label its result exact. + +The macOS parity build includes the pinned portable Digital ICE core and the +bridge auto-registers it. Hybrid additionally needs its separately pinned +companion interpreter/model manifest. `write_frame` still checks registration +and evidence before attempting Tier 2, and records a plain unavailable status +instead of losing lower tiers. Since Tier 3 depends on Tier 2's in-memory +result, it degrades the same way whenever repair is unavailable, even if Tier +2 itself was not selected for writing. Tier 1 still writes when selected, +regardless of Tier-2 or Tier-3 failure. The receipt records, for every tier, whether it was written, and a status explaining why not when it was not. A successful Tier 2 write also records the repair engine's name and version, along with which mode ran. A -successful Tier 3 write also records which rendering path produced it. It -records the process mode and render intent that were used, and whether -auto exposure was on, so a later regeneration or audit has something -concrete to compare against. A Tier 3 receipt entry also carries the Tier -2 provenance that fed it, so it stays self-contained even when Tier 2 was -not itself written to disk. +successful approximate Tier 3 write records its color-mode label, rendering +path, process mode, render intent, and whether auto exposure was on. A +successful exact write instead records the input/output content hashes, the +bound ICC profile identity, and both embedded receipt payloads plus their +SHA-256 bindings. Every Tier 3 +receipt entry also carries the Tier 2 provenance that fed it, so it stays +self-contained even when Tier 2 was not itself written to disk. Writing every tier is not free of storage cost. At 4000 dpi, one frame's three tiers together take up roughly half a gigabyte on disk. A long roll @@ -289,12 +467,11 @@ past the window that used it. coolscanpy's roll reservation has no equivalent failure mode, so there is nothing to protect against by closing it early. -The panel does not expose a material picker. It always opens a roll as -color negative, matching the one material coolscanpy's roll engine -fine-scans end to end today. A black and white picker would only ever -support preview, not the batch scan, as the coverage note above already -explains, so adding one now would be a control with no working action -behind it. +The panel does not yet expose a material picker and opens a roll as color +negative. Coolscanpy's B&W batch route is implemented and covered offline, +but its first live macOS validation with conventional silver B&W film remains +outstanding. Exposing that choice in NegPy waits on that hardware acceptance, +not on a missing fine-scan implementation. A contact sheet thumbnail is a raw scanner preview frame, not a NegPy pipeline buffer, so it is converted to a displayable image with a small diff --git a/docs/FAUXICE_IR_REPAIR.md b/docs/FAUXICE_IR_REPAIR.md index 1a67d2cc..bb61a46f 100644 --- a/docs/FAUXICE_IR_REPAIR.md +++ b/docs/FAUXICE_IR_REPAIR.md @@ -1,15 +1,19 @@ # Optional IR dust repair (digital-fauxice) -`negpy.services.repair` adds an optional post-import step that repairs -infrared-flagged dust and scratches in a scanned frame. It calls +`negpy.services.repair` provides the repair runtime used by the roll workflow +and by the optional post-import file helper. It calls [digital-fauxice](https://github.com/rohanpandula/digital-fauxice), an -independently reverse-engineered, byte-exact reproduction of the Nikon Super -Coolscan's Digital ICE processing. NegPy does not implement any dust repair -logic itself here; it hands a frame to the engine and writes back what comes -out. - -The feature is off by default and stays off unless a caller explicitly -enables it. Nothing in the normal import or render path touches it. +independently reverse-engineered implementation whose pinned exact path was +validated sample-for-sample against Nikon output. Hybrid is a separate, +explicitly disclosed model-assisted path and is not labeled byte-exact Nikon +output. NegPy does not implement the repair math itself; it validates and +binds the acquisition, runtime, output, and receipts around the engine. + +The standalone post-import helper is off unless a caller explicitly enables +it. The LS-5000 roll sidebar is different: its new-user Color + DICE workflow +enables Repaired and Positive and selects Hybrid by default while preserving +any previously saved choices. Nothing in the normal image render path runs +repair implicitly. ## What it needs @@ -20,12 +24,53 @@ carries per-frame calibration the main pass depends on. It is not a downsampled convenience copy and cannot be reconstructed from the main scan after the fact. -NegPy's current scanning pipeline captures one pass per frame and keeps no -prepass. On a frame imported that way, this module reports a skipped status -rather than inventing one. The calling contract exists so that a future -acquisition path, such as one built on -[coolscanpy](https://github.com/rohanpandula/coolscanpy), can supply a real -prepass and have repair start working without any change to this module. +NegPy's generic single-frame/import pipeline captures no paired prepass. On a +frame imported that way, this module reports a skipped status rather than +inventing one. The current LS-5000 roll workflow can supply a real, frame-bound +prepass, main pass, infrared-validity array, and acquisition provenance from +the reviewed coolscanpy overlay. The two Tier-1 TIFFs alone do not retain all +of those inputs and cannot later be promoted to an exact repair acquisition. + +## Retained roll-acquisition replay archive + +For an infrared-bearing Tier-1 roll frame, NegPy attempts to retain those +otherwise transient inputs at write time. The frame receipt exposes the +result as `outputs.repair_acquisition_evidence`. Success is explicit: +`retained` and `replayable` are both `true`, and `schema` is +`negpy.dice-acquisition-replay-v1`. + +The archive adds three files to the RGB/IR TIFF pair: + +- `acquisition-binding.json`, represented by a `binding` object with `path`, + `bytes`, and `sha256`; +- `prepass.rgbi16.npy`, the scanner-native RGBI prepass; and +- `ir-validity.npy`, the scanner-native per-pixel validity map. + +The receipt lists the two NPY inputs under `artifacts` and the Tier-1 RGB and +infrared TIFFs under `sources`. Each row binds its expected layout and content +and includes a relative path. The binding declares the source TIFFs as +`upright-storage` and its `replay.requires` contract names five requirements: +`storage_rgb_tiff`, `storage_ir_tiff`, `prepass_rgbi`, `ir_validity`, and +`acquisition_provenance`. + +To reconstruct scanner-native main RGBI, replay stacks the upright-storage +RGB and infrared planes and applies `rot90(k=-1)`. The +`load_repair_acquisition_evidence` loader stable-reads the binding and every +referenced input, verifies their hashes and geometry, and re-derives the +Coolscan producer acquisition identity and evidence hash. Relative paths let +an operator move the whole output archive and replay it in the new location; +the TIFFs and hidden evidence directories must stay together. Copying only +the TIFF pair or only the binding directory does not satisfy the contract. +After a move, pass `acquisition-binding.json` at its new location; the +capture-time absolute `binding.path` in the outer frame receipt is not a +relocation pointer. + +The replay advertises `integrity-bound-not-signed`. Its hashes and canonical +producer binding detect substitution or corruption, but are not a publisher +signature. Retention is fail-soft: if validation or writing fails, Tier 1 can +still survive while the receipt records `retained: false`, +`replayable: false`, and an `unavailable` reason. That state must never be +treated as replayable or upgraded from the TIFF pair later. ## Install @@ -37,10 +82,10 @@ uv sync --group fauxice # core engine only, exact mode uv sync --group fauxice-hybrid # adds the hybrid companion ``` -Neither package is on PyPI. Both install from a digital-fauxice GitHub -release, the same way `python-sane` is an optional group rather than a hard -dependency: NegPy runs without either installed, and every function in this -module reports a clear status instead of raising when they are missing. +Neither package is on PyPI. Both install from a pinned digital-fauxice GitHub +release. The local macOS parity app bundles the core runtime and its frozen +smoke fails if the roll repair bridge does not register. A source installation +without the optional group can still run NegPy, but repair reports unavailable. Hybrid mode additionally needs a separately installed IOPaint 1.6.0 runtime and the `big-lama.pt` weights, in their own virtual environment. Neither is @@ -50,7 +95,10 @@ paths once they exist. ## Two modes -The setting offers `exact` and `hybrid`. `exact` is the default. +The setting offers `exact` and `hybrid`. The standalone post-import helper +defaults to `exact`; the LS-5000 roll sidebar's first-run Color + DICE workflow +defaults to `hybrid` and visibly labels its model-generated fill. Existing +saved roll choices are preserved. `exact` reproduces Nikon's own Digital ICE output value for value. The engine's validation compared two complete frames against Nikon's real @@ -60,46 +108,39 @@ output, 68,447,316 16-bit samples per frame, with zero mismatches. engine's own defect signal maxes out, to a LaMa inpainting model, and composites the result back into the exact output. It needs the separate `fauxice-hybrid` package and its IOPaint runtime. If hybrid is requested but -either is missing, or the run fails for any reason, repair falls back to -`exact` and the sidecar records why. Hybrid mode never fails the whole -repair; it degrades to exact. - -Exact is the default because of the time difference, measured by -digital-fauxice on its own validation frames on an Apple M4: - -| | Frame 1 (lightly marked) | Frame 2 (badly marked) | -|---|---:|---:| -| Exact repair, cpu-fast | about 10 s | about 10 s | -| Hybrid, whole frame | 72 s | 210 s | - -That is roughly seven times the exact path on a lightly marked frame and -twenty times on a badly marked one, because the model runs once per routed -region on CPU and the routed area tracks how damaged the frame is, not how -large it is. Over a 36-frame roll the difference is about six minutes -against something between forty minutes and two hours. The sensible pattern -is to run exact across a whole roll and reach for hybrid only on the few -frames that still show visible ICE residue afterward. +either is missing, or its validated run fails, repair falls back to `exact` +and the receipt records why. Explicit cancellation remains cancellation; it +does not silently start a potentially long exact repair afterward. + +Do not use the old whole-frame timing numbers as a promise: runtime varies with +selected backend, frame geometry, routed damage, model runtime, and host +configuration. Hybrid can be materially slower because it invokes the +separately pinned inpainting environment for routed regions; the desktop +reports progress and supports cancellation instead of estimating a fixed +completion time. ## Progress and cancellation `repair_ir_dust` and `repair_frame_files` accept an optional `progress` -callback and `cancel` event, in the same shape -`ScannerService.run_scan` already uses. Both only cover the exact path: the -engine supports cooperative progress and cancellation natively there. Hybrid -mode is one blocking call to an external CLI, so `progress` is never called -during a hybrid run, and `cancel` is only checked before that call starts, -not while it runs. +callback and `cancel` event, in the same shape `ScannerService.run_scan` +already uses. Exact forwards cooperative progress and cancellation to the +engine. Hybrid reports coarse verified-run checkpoints and polls the external +process; cancellation terminates its process group and returns a cancelled +result instead of leaving the CLI running in the background. ## Output and provenance A repair never rewrites the source master. NegPy keys stored edits by the source file's content hash, so silently changing that file would orphan -every edit already saved against it. A successful repair instead writes a -new companion, `_FAUXICE.tif`, next to the untouched original. The -`_IR.tif` companion is only ever opened for reading. +every edit already saved against it. The post-import file helper writes a new +companion, `_FAUXICE.tif`, next to the untouched original; its +`_IR.tif` companion is only ever opened for reading. The roll workflow instead +uses its Tier-2 names and frame receipt described in +`COOLSCANPY_ROLL_SCANNING.md`. -Every call also writes `_FAUXICE.json`, whether or not the repair -was applied, so a caller can show why. It records: +Every post-import file-helper call also writes +`_FAUXICE.json`, whether or not the repair was applied, so a caller +can show why. It records: - the status: applied, skipped, or unavailable, and a plain-text reason - the mode requested and the mode actually used @@ -114,15 +155,11 @@ was applied, so a caller can show why. It records: Hybrid mode does not claim its filled pixels are recovered film information. They are generated by a model. Every synthesized pixel is recorded in a mask PNG written alongside the output (`_FAUXICE_SYNTH.png`), where a -white pixel marks a synthesized one. Outside that mask, the hybrid output is -byte-identical to the exact output; digital-fauxice's own receipt proves -this pixel by pixel before NegPy ever sees the result. - -The routing that decides what gets filled is conservative on purpose and -fails closed: if the routed regions would cover more than 2% of the frame, -digital-fauxice refuses to run hybrid mode on that frame at all rather than -fill an unbounded area. On its own two validation frames the tool routed -0.07% and 1.36% of the frame respectively. +white pixel marks a synthesized one. NegPy stable-reads and validates the +canonical hybrid receipt, output and mask hashes, geometry, routing counts, +synthesis accounting, runtime manifests, model identity, and the receipt's +within-budget verdict. It does not turn those checks into a broader claim that +the entire hybrid image is byte-identical Nikon output. ## Limits @@ -137,7 +174,8 @@ Digital ICE Normal at the resolution metrics used in digital-fauxice's own validation. An acquisition outside that profile is rejected before any processing runs. -Until NegPy's scanning pipeline captures a paired prepass, this module has -nothing to repair on ordinary imports and will report a skipped status. The -config, the sidecar format, and the tests in this repository exist so that -gap can close without another round of adapter work. +Ordinary imports and generic one-pass scans still have no paired prepass and +therefore report a skipped status. The LS-5000 roll acquisition path supplies +the required pair and binds it to one frame. If that binding, validity data, +runtime, or receipt is missing or inconsistent, repair fails closed; it does +not reconstruct calibration from a main scan or from Tier-1 TIFFs. diff --git a/macos/NegPy.entitlements b/macos/NegPy.entitlements new file mode 100644 index 00000000..7b677ae2 --- /dev/null +++ b/macos/NegPy.entitlements @@ -0,0 +1,8 @@ + + + + + com.apple.security.cs.allow-unsigned-executable-memory + + + diff --git a/negpy/assets/portable_builder/ls5000-fixed-output-lut-u16le.bin b/negpy/assets/portable_builder/ls5000-fixed-output-lut-u16le.bin new file mode 100644 index 0000000000000000000000000000000000000000..8914b4f2b076ffac89212bb7a882924258a36970 GIT binary patch literal 131072 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 m@=F-`0%_7ew)1I5AOP;rU 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%0A$Ty68PQZ=r*m3})^r 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 literal 0 HcmV?d00001 diff --git a/negpy/assets/portable_cms/cml4-stage1-input-lut0.bin b/negpy/assets/portable_cms/cml4-stage1-input-lut0.bin new file mode 100644 index 0000000000000000000000000000000000000000..35ec089fa3d88d458cb6b9bd731a2a2400c49d6d GIT binary patch literal 393216 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%0DX`?L?5P)&`0TG^l|zG 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

hKZj3NS8l#NS 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

&_ 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 literal 0 HcmV?d00001 diff --git a/negpy/assets/portable_cms/cml4-stage1-output-lut0.bin b/negpy/assets/portable_cms/cml4-stage1-output-lut0.bin new file mode 100644 index 0000000000000000000000000000000000000000..a53409bdd130e9c1af996d1eb297f7cdb0f91fa9 GIT binary patch literal 393216 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%0D#6mnIKq4eV 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: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

TV6Urd!Lc?bdPYy7k=pZUeWW+sJL~HgTJ}&D`d0 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

gPAaFA)5;m;ta45{uUt?rDwmYY$`$3R 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≠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

TV6Urd!Lc?bdPYy7k=p zF5!|c<9*9T&Q;>$$!gxS<=l$i;5#HgFrd zjoij=6St|`%x&(ra9g^q+}3Uzx2@aGZSQt)JG!0R&TbdCtJ}@(?)Gqdy1m@qZXdU= z+t2Oq4sZv$gWSRH5O=6M%pLBIa7VhM+|lkBcdR?k9q&$XC%Ti|$?g<)syoe{?#^&$ zy0hHb?i_cnJI|f(E^rsRi`>QT5_hS)%w6uTa96sk+|}+Hcdfh5UGHviH@chL&F&U= ztGmtJ?(T4Ry1U%n?jCoqyU*S49&iu3hup*N5%;Kj%suX&a8J6Y+|%wE_pE!)J?~y{ zFS?i9%kCBTs(a17?%r^3y0_fh?j858d(XY^K5!qpkKD)Z6Zfh6%zf^@a9_Hw+}G|K z_pSTReeZs7Kf0gX&+ZrZKliKq&He8FaDTeL+~4jW_pkfUP2eT;5_yTeBwkW4nU~y4 z;idFad8xfLURp1mm)^_ZW%M$6nY}DtRxg{E-OJ(S^m2K*y*yrCFQ1p+E8rFM3VDUS zB3@Cim{;5@;g$4Cd8NHFURkf4SKh1ORrD%(mAxunRj-;?-K*i%^lEvvy*gf9ubx-m zBRtZhJlbPC*5f?h1D@cCp5)1%;;EkI>7L=4p5@t|<3Z2$JkR$6FZ3c0dDx4+23|w2 zk=NL3;x+Y}dCk2RUQ4f)*V=32we{M0?Y$0ON3WCD+3VtU^}2c8y&hgqub0=`>*MwH z`g#4m0p37wkT=*H;tlnNdBeRC-binhH`*KHjrGQPQn$k+;}e;w|--dCR>O-b!zkx7u6dt@YM<>%9%$MsJh1+1ui6 z^|pE2y&c|8Zy&K+5@0NGlyW`#U?s@mU2i`;Pk@whp;yv}AdC$EU-b?S5_u6~o zz4hLC@4XM+NAHvO+56)C=Y93QdEdPs-cRqB_uKp9{q_EN3H*e9B0sU8#82uc^OO52 z{FHtwKeeC6PwS`i)B73xjD99Rv!BJ!>Syz_`#Joael9<^pU2PZ=kxRX1^j}3A-}L+ z#4qX>^Naf>{E~htzqDV*FYA}{%lj4lihd=(vR}oo>R0ot`!)QUel5SYU&pWO*YoT9 zgirdEPy39|`kc@Ez!!Ycmwee*eAU-{-8X#Gw|v`oeCWHr=lg!(hkoQEAN#T2z;Ea` z@*De2{HA_0zq#MSZ|S%4Tl;PNwthRmz2Cv_=y&ov`(6C5emB3n-^1_e_wsxDef++D zKfk{}z#r%j@(24v{Gt9Zf4D!wAL){xpBOKf|Bt z&+=#cbNspfJb%8wz+dPu@)!F{{H6Xff4RTHU+J&%SNm)Hwf;JPy}!ZV=x_2j`&<02 z{x*NRzr)|@@A7y1d;GorK7YS|z(438@(=q*{G>I8LzdO`hw z2*`j6=zs~>fD8Bl1VSJNQXmIPpaxo?2S#89R$vEC00TGh0zU|XFo*&a;2;hf1Py~m zLF1rF&@^ZkG!I$?ErV7;>!3~0HfR^L4>|-LgHA!`pi9s-=oWMjdIUX#UP14mPtZ5$ z7xWJX1OtOX!Qfy>FfR?TklFgH6HaU`wzy z*cNOLb_6?vUBT{PPp~)G7wiuX1P6md!QtRYa5Oj;91l(eCxcVL>EKLoHaHiY4=w~3 zgG<5X;7V{cxE5RwZUi@jTfyz%PH;E47u*jX1P_Bp!Q)=iB zHh34j4?YARgHOTd;7jmd@HO}rd=GvEKZ9Sv@8D1HH~1GM2or{h!o*>cFlm@9Odh5P zQ--O+)M1)1ZI~`hA7%(MhMB_5VU{p!m@Uj6<_L3!xx(CGo-l8iFU%hn2n&XV!op#Z zuxMB;EFP8!ONOPw(qWmfY*;QVA65t}hLys~VU@6ISS_p`)(C5cwZhtAov?0LFRULD zAsJF39Wo&sav>jrPzc3P3gu7<)ldud&4u9gYddhU3EV;e>EvI4PVQP6?-m)57WDjBsW+ zE1Vt93Fn6M!ujEXaACM8TpTV5mxjy2<>88OWw!h;f8QyxGCHmZV9)B z+rsVPj&NtVE8HFK3HOHk!u{ca@L+f-JRBYgkA}y>lRrpN7xE=i!U+W%w$59liQs8UopsuER=szueK8d1%tR#ZEx6V;9CMfD>h zA|ooIBPL=aF5)8)36U5{ksK+J8flRp8Ic)TksUb^jNHhJ{3wXRD2h;oqd001HH;cX zjiV+})2Lb0JZcfOj9Nvlqc&07s9n@P>JW8|Iz^qME>YL0Thu-35%r9EMZKdwQQxRv z)IS;!4U7gwgQFqQ&}djRJQ@*=j7CMHqcPFgXk0Wtnh;HlCPkB@DbdtuS~NYH5zUNd zMYE$h(cEZWG(TDpEsPdLi=!pc(r8(929-tSPbdo3}O+7 zcm$AuL?j^@DM&>c(vg8oWFZ?l2qG7G$VUMRQG^h}C`JuXL(~X0Momyt)C@I8El^9; z3bjUUP+QavwMQLLN7M;*MqN->)D3kmFVIW$3cW^e&|CBly+=qvh$zM~)LC;Ekcqd({` z`iBzWgg6mSjFaG`I2lfkQ{a?16;6%Q;Iud$PLDI-j5rg{jI-dZI2+E6bKsmf7tW3I z;Ji2=&W{V=g18VajEmr+xEL;uOW=~Y6fTX+;Ig!VhYok!7S!5j{z32h$Spz1*=%YIySJ0Eo@^4L+oM?`#8WMjxfR) z$G8D*h#TR?xCw5Go8jiT1#XF3;nuhfZj0OD_P7J?h&$oVxC`!zyW#G*2kwb`;oi6p z?u+~3{&)Z$hzH@pcnBVfhvDIP1RjY;;n8>u9*f7}@puBBh$rF6cnY41r{U>%2A+v$ z;n{c&o{Q(<`FH_dh!^3-cnMyLm*M4j1zw3);njEzUW?b^^>_o`h&SQQcnjW&x8d!0 z2i}Qy;oW!--i!C){rCVrh!5ez_y|6VkKyC^1U`vR;nVmGK8w%c^Y{Y3h%e#G_zJ#? zui@+X2EK`J;oJBQzKieS`}hHVh#%p{_z8ZBpW)~D1%8QN;n(;Lev9AX_xJ<;h(F=a z_zV6If5qSMcl-nY#J})w{0INV|8RmhVVo#V94CpB#>wL3af&!)oGMNor-{?X>EiTp zhB#xKDb5^ciL=Jp;_PvbIA@$I&K>88^Tzq&{BeP}U|c9J92bd;#>L{|af!HOTq-Ud zmx;^9<>K;jg}7o|DXtt>iL1ud;_7jYxMo}{t{vBj>&Ers`Y{ocF%{D>6SFZF^D&5p zSd67uj+I!AwOEgh*o>{%j-43BZtTT=9K>N9#VE#c95;v?#*N~}ag(@d+$?S$w}@ND zt>V^ko49S9Gh$qIA;>q!pcxpT?o*vJLXU4PQ+3}osZagoZA1{a( z#*5;`@sfCHyewWGuZUO1tK!x1ns{xzE?ysRh&RTY;?41vcx${Z-X8CWcgDNo-SM7y zZ@e$wA0LPh#)sm=@sapwd@Md5pNLP!r{dG`nfPpcE+=s_-cGDz8>F* zZ^pOc+wqh(E@k;?MDy z_8X_%`jz~{rATkn}h|EM5A}f)N z$WG)SauT_S+(aHCFOiSPPZS^u5`~DuL=mDWQH&@~lpsnHrHIl*8KNvvjwnx5ASx1- zh{{A2qAF31s7}-%Y7(`G+C&|qE>VxDPc$GJ5{-z)L=&PZ(Tr$Lv>;j%t%%k{8=@`I zj%ZJGAUYDAh|WY8qASsj=uY$?dJ?^e-b5dwFVT-62$G-(nqUZ)00c+y1SAAPBqTy6 z5TOt%p%FS^5GH{Mi?9iYa0!p_iGT=+h=_^)!~kL-F^CvU3?YUR!-(O;2x25LiWp6d zA;uEpi1EY(Vj?k#m`qF|rV`VL>BJ0TCNYbcP0S(Y67z`p!~$X=v4~hqEFqQ>%ZTN~ z3SuR(idapoA=VP>i1ow%CUJ|nP23^w68DJv z!~^0X@rZa#JRzPE&xq&53*sg5ig-=DA>I=2i1)+?;v?~i_)L5uz7pSv@5B${C-IB; zP5dGL690$025@boT6j_=qLzX4Wk>$w>WJR(P zS(&UtRwb*E)yW!UO|lkQo2*0DCF_y($p&OYvJu&sY(h3An~}}Q7Gz7Z71^3>L$)Q` zk?qM2WJj_S*_rG@b|t%!-N_zgPqG);o9sjOCHs*CNs<&vlMKm{faFM?grq=>eU7&)9AL5?Ixk)z2m z%qFPgJsJ2u) zsy)?#>PU5>I#XS!u2eUwJJo~gN%f+7Q+=quR6mNKNQ$CpilJBvP#ncmkP;}7k|>!% zltQVLM(LD6nG~ih%BCF3r98@~0xF~;DyI5V1E_)2AZjo*gc?c>qlQx>sFBntYBV*5 z8cU6%##0lhiPR)&GBt&oN=>7tQ!}WU)GTT?HHVr@&79j(jnpP;Gqr`NItRI!m3S&Qlkti_|6RGIfQzN?oI_Q#Yua)Gg{Zb%(l3-J|YP z52%OKBkD2rgnCLnqn=YQsF&0$>NWL-dP}{d-cui_kJKmXGxde~N`0fgQ$MJm)Gz8c z^@sXP{i71l3F-gnM08?037wQqMkl9J&?)IubZR;cot92Vr>8T}8R<-PW;zR0ESfIuD(f&PV5`3(y7WLUdue2wjveMi-|`&?V_obZNQ_U6w9Km!~Vx73oTJ zWx5Jom99otr)$tP=~{Gcx(;2Ju1D9W8_*5uMs#Dk3Eh-#MmMKh&@Jg!bZfc|-Ii`g zx2HSM9qCSVXSxgBmF`A&r+d&n>0Weix)0r#?ne_eNmDdUGc-#BnxlCd(gH2g5-rn+ zR%n&hXq`4_lg6|~+q6Twv`71NK!*s06mZ%L=UEi&_n5A^l*9vJ(31p(IdImj{o<+~5=g@QMdGvgG0lko3L@%b7&`arM^m2Ly zy^>x236OdI!Ce-bL@G_t1Okee{0%0DX`?L?5P) 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%0DX`?L?5P) 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 MIL!Wr@hJW8_dPIGq0nw0X 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

rV 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%0DH6nov!o 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

6=3(=QdDJ{+9yd>zC(TplY4ePE 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 V5u33U+p!b7u^0Ps5QlLT{|^K(D^>si literal 0 HcmV?d00001 diff --git a/negpy/assets/portable_cms/cml4-stage2-output-lut0.bin b/negpy/assets/portable_cms/cml4-stage2-output-lut0.bin new file mode 100644 index 0000000000000000000000000000000000000000..49836f48f9febc110ce84c9034b9eac21feede38 GIT binary patch literal 393216 zcmeFt<6>M3qX5t}O`E1|+9Zu_O*qQfwr$VG>^(iTZQG|-YWLK(ZTG8f-p9CqvL0c@ z#I$G`Xc=f3Xc=f3Xc=f3Xc=f3Xc=f3Xc=f3Xc=f3Xc=f3Xc=f3Xc=f3Xc=f3Xc=f3 zXc=f3Xc=f3Xc=f3Xc=f3Xc=f3Xc=f3Xc=f3Xc=f3Xc=f3Xc=f3Xc=f3Xc=f3Xc=f3 zXc=f3Xc=f3Xc=f3Xc=f3Xc=f3Xc_qbA4rSA#IR!YF<{J~nCUUiG5ce##Jr4&X_eLr z(~8wf-wJFssMYjV&8_ygy3*=ptC-ldSWGM{Rv!z-4vL)~+Z?+;_Dbx_*qGL7tud`x zt@W+J)`MD4Z{6H_f9or)U$%~kON+zAvEuY`VBDa%>2b|*`{S;}y^M=#lhy{)hSf&j z25d8^&Ga_SZT7dh(&lBGn6_zcF>P6G^=-kngW67S+uU}4+beBfwvCBTi^s&X;`Q-h z{Gj;h@y+r3riLEi!FFsQ@y z4$U3*cev8wWrvuKX&o^gSsnEq!H$DEPVd;-Isbg^+xK`)DNkDQ#+<*rIn>&(->*eG-H}C z4NdExHacy3+TyfzY5%1iO}m(OH|=HG*RES>r}s`Do<1pk zUi!-P&FTBnPo`f@e~|t<{abomM(2#|jPi`e3|fXTL!05q2xoN57??38V|vEIjOL7O z83!^>XI#s8nDIK}TSjbVVrFJ$NoH*(DU+Ki&opIvGvUl$nL{$iXU@!AoVg}*d**@6 z)0tN@?`OWu{G9nWGa)NAD?h6;3!6pH5@xBhtXcjnB&&DU(5&%UGqVr&R8tp8=b%leTOm)$8lGrK4olTFBGWQ(#j+16}-Hj>>ddr0=!?CIGHvR7tr%-)rK zIQwk&wd@DkFS0*o|ITikla!N{Q=EgzA>=S}ggNRQOO7`O%ITgnAZJw0+dFqi?%3RE zx$|A*8`4977f{6t)3KkSBFIZQwwO~)dk%H3& zmkVweJSuou@S)&GL2O|{VRB(+VPRoqVSQm!A)`=GC@<6(+6sMzVBtT7eF}#ZjxL;B zIID1B;flg_h1&}E6dox&U3jVRR^h|K=Y{VJzZL#1j4$d`lvb2iR9b{7!WB`9*hQiu zRgtmCQRFXzin zY;i(yQgKFcL2-F;Z85%>TFfaH7psbm#g1ZsF<9KSxOefO;*rG@il-OPD_&B(x_D#p zj^cgAM~Y7uUn;&?{Gj+*@!R6h#lMPMm$WZQF3Bj#FDWajDZ!PHOIRhs5=Duw#9HDh z36~%xJxcnQ3@sT`GPz`C$%2w)CCw$9N_Lj)D>+(nrsPt|&5{Qt|0{V@@~Px!No;Am z($1x6rMacWrB$U3rNmNtDYsNoswy>>+Dm<5kI9rAJCnm0l>lUV69mN$Jbd_od%T|CF{V>rj?lmRXixR$7KBYbE+yVak;WwUv4dTmj}z?@^0mQ$_JH?C?8io zrF>TTg7T&1tIIc(Z!6zjez5#_`PuTzN?#o&q&72_(VRLrVaP_eXPb;X8?trfc} z4pbbgI8$+{;(Ep1ipLc%D&AFmuJ~EesxrPZu`;DHvogQ3w6dzQzLHQ$tz=gUDrJ?L zN@JzH(pwp>j8t~3>{B_Ya(Ly~%E^^8D(6)$u3S;MwsLdjj>^52hbvE1o~yi4d9(6< z<SJ zo>l#;hE$EJ8ecWFYF5>PswGvcs@7F)soGh!uj+8s$*OZzSE_DS-LHC5^|I<+)t9QD zRjsPqR(GgQu1>GctuCsrsIIBTRyS4CsyWrdYI(J$+E{I`_Ed+e;p(o{y{h|H53L?m zJ-&Ks^{ndo)k~^ZRW|gms()9v#go$E$V)|hQV@6=cVJ2f{VCG^L zVwPi?F&i*jG5=xqV~$`>V$NYMV{T&ZVIE_iW8PpsV!mVkU|QF-t4XX$smZ9xt0}Ij zsHv%GtRdD=YuGh{8flHHMqgv8an|^1!Zk=ux0>EH18RoWjH(%5Go@x`&AggLHOp(7 zYc|$wtJzhvzvf8I$(nODmuqg++^u<3^Q`7|&4-$=HNR?F)wZqeP@7bnR-0X0P+MAC zRa;k!t0mRaYdN*TT3M~S)=+D$b=CT7fm)=tTW#;!0kuPGN7at6ol-lqc5dy$+GVw? zYuDFqsohz-xAsu&@!B)B7i+K9-m1M{`=s_o?c3TXQ^}6`Rc-Ta9!8Bo^}1|2G@!x&?KM z>sHjQsoPk$t?s|NeRYTHPSl;PyI6Ov?snaSx+irn>fY9Us{2;=yDqjqzP@99Qhi!| zc6~v8NquE~ZGB@sv7TDbs^`^<>lO9ddSkt<-d*pn2kMdfZuPzD`_~VtA5lNHep3Ck z`q}jh>KE6qs9#gRp?+)qfA#z757i&9KV5&J{%ZZr`g`?{>z~!XuK!T~rT%AqOha5l zLPMv9l!lCkoQA@NvWBXLx&~ZBQv@U191qh8?G0wA8rtCIBqm^>25t^+0d6sF1+E#l z9=92{1GgKuA9omc0(S;?0e1y=19unq5cd@K0`~^@5%(4M6ZaPvhi`}Ph)=?&;bbm;^3CNRSeg1TDcxuo9dEFCj<(2`Hf( zp%~)VHRN?VIg5DVI^S=VFO_cVJBe^;Q-+X;RN9f;R4|b;RfLj z;Q`?Z;W^hr6Xz2b5tk8H5!Vnm5VsI_ z5O)*z6Au%Q6HgP*6E7356K@mm6CV?w5nmDC5kC^Y5`Pl^5?eRLH+5+0+|;EhqbaAU zpsA#(ys5gWt_j;jY@#$VnmA2@CP|Z`N!_GxGB??q+)e(ba1-1VZR+0CtEpeppr&C> zqngGwO=_CfG_z@L)4xqinpQM5H?42l+_bIfzoxxS2b+#IooqVWbfM`=(~YJ(O%Ix$ zG(B&6)%33EQ`5JmUrjNjI8r>R1F18q3n_z?L&_%=lgdfeq&gCoL?Dq#bP}7yCy7Wh zl8U4wnMgL0ljJ1@NgxR!btUy6^&t%)4IvFDjV6sJO(snz%_7YsEhH@^tt2&*){{1q zwv+xN?Ij%~9VMM4ogrNyT_N2d-6q{9JtjRPy&}COeI$J){UrS%#gf~S+mjQ?$>cP0 z7CDz(NG>H;k}>3ZGLGCtrjQwA4p~4JljUSJSx+{RZDbeOM-GxfGD7Z3?m_ND9zY&U z9!?%j9!H);o<^QUo=g6hyo9`*yqdg@yotP(ypz0#yq|oSe4KoWe2#pHe2sjQe3$%? z{Dl0R{F?lZ{E7UH{FD5b+?o%F4P~}uLRYx^ZZB!@KOASx~YJ?i4 zcBl5D_M;A@4yBHyj-gJVPNq($&Z5qv{!3j#T|r$q+ZF8$cUO8%`TV8%LW+ zn@XEOn?svVTSQw*TS;rCt*33GZKLg^?V;_b9iknhour+mU7%f|U8miq-J?CCJ*B;% zy{5gVeWHD%{iOY&#nRi*6X+f3N%SuC40<*_pI$^SqgT=~^m;m$PNb9RbUKU9qYLR0 zx`M8z>**%Cjqap-=>d9}4%1P3H+oNcU-|(0VES1Nvk7Gx|&VTlxq3XZm;gFM154 zHKQ$~JtL8k%t&QqFmf3Aj3P!Eqmog>sApgqLnLWBg+LWyCVuG832`nMuqp z%yecpGmlxwEM-12AD0cMy9F;V6} z%pS}>%>K+l%wf!t%rVUI%*o7Y%$dx&%mvIv%%#kg%x2~~<|gJ=<__jA=04^@<`L!z z=4s|R=0)aJ<_+d;<~`;k=2PZ#=4<9V=11li<`3p?W(=z}E1uP!mB>nBrLr7nL3uGazuB`5?UaWqs zfvh2{;jGcDajc1~DXbZ+*{pf2e_4xJ%UP>fYgp@9n_1gfJ6XF~`&frqM_DIWr&;G& zmsnR>H(0k>_gRlvPg&1duUPL`A6Z{m-&wy{G3?gtw(JCUM|Kjs3p<^i#m;3HvP;?(E*yMc{k6WL@ojm=_n*#fqhEn}lD&q#p1p~^mA!+#i@lG1kbQ)GoPCOYmVJ?Z zg?*iUi+z{LjQx`RhW(!XiT#!RgZ+oyiWA3)=d|Y}a*{ZyoD5DjCy!IeDdCiJ zsyVfs1`duxm~ zXBcNBXAEZoXEJ9RXC`M3XFg{kX9;IHXEkRnX9H(5XB%fHXE$db=OE_@=LF|8=N#uE z=L+XK=N9KK=K<$2=NacE=MCpQ=M(2E=LhFECx+Xa+m_p&o5)S#cHyRTv$(n30&X$4 zj9baYaO=5PE`dwpQn?H+o6F-0xe~6NtKw?82Cju`=eoFFZh#x+LR^H~mD`=$i`$nw zfIFBwj60G$mOFtvnLCX;lRJkypSzH|gu9%(io1rpp1X;=mAiwxi@TS5fO~{{oO_CU zmV1GFnR|_UlY57IpZkdWl>40fiu;!Pf%}>Jjr)`Phuex5$BXB+=OyxzcwKnuylh?` zuaH;5E9X`5YIyZLERVn=@u)lokIm!pgggmP&QtNUJOj_nv+CTZt>tatZRTy`?d0v|?c*Kf z9pN44o#LJ4UEp2jUE|&4-QnHmJ>os(J?FjRz2$x2edc}R{p9`Owc^L|r~VnLapQcx|Z6*LHN0-}H{pb3})j({%^38VtKKqb%$ z3<9&jCU6Qo0>2<600poB75pRUA?PjWCm1LgA{Z_hB^V`>?Z6X>@Dmk94H(j94;Iw93vbr zoFtqooFSYooGV-)TqIm7Tp?U7Tq9gB+$7v8+#%d0+$-EKJS03SJRv+SJSV&$yezyX zyeYgRyf1txd?I`%d?|b*d@uYc{384={3ZM=j1|R+;zjL6iJ~M?7g3rhQu=&0y~=(OmZ=z{37=$h!J=#J=~=%MI|=$Yt+=(Xsb=%eV1=$q)L=#QwCI8NMF zoFMKf?kr9gr;0Pg+2TBLfw)*)Cax4$i)+OVVw{*DCW)zHhL|npiUne^SSD78)nc94 zC^n02VyD<6_KSmJKn#mfaaVD7aW8RSaewh3@lf#y@hI_F@dWW?@l^2)@oe!t@dEK8 z@lx>$@hb5e@p|zl@fPuR@qgky;(g+S;v?eY;*;Vt;`8E5;w$3o;#=ao;``!9;-}*0 z;+Nt#;`icD;xFRw;$PxF;#QJ4NxURM(oxb`k|If!WJt0ld6EK2v7}5=A*q(sN*W|s z2|+@VP$hH;OTv}#B_fGbqL8R0T8Tkol2|1UiCf~81SDYzD2Yg-l5Ub7lHQVjk^z#z zl3|jOlF^cJl8KVZl4+8ek~xxjl7A(OB}*kMB&#KBCF>=dBwHohCI3nGNcKq%N{&d5 zOHNA8NX|NuEfaNnT1`OWsL7N%cYf4jI>VLD8)&MQnHjPWk}glu2diuOQlkUR4vs?4N|kz zDs@QRQm-^14NF03L>iU;BkdvWE$u5EARR0nDjgvmEgdVJAe}6oDxD#nEuAY}AYCL~ zB3&+BC2f|jlWvr5mTr^ol18IFMP`>dWgeMd7L)-pScb^D%DTyV%KFIq z$p*@X$cD*A%0|n^$tKDs%cjX@%4W;v$ri{K$(G8N%T~$O$kxd=$~Mck$#%+i$@a<) z$PUSl%8tuU$U3ofOH6R7JWXOOd0< zR}?Br6y=Ia1x8V;Xi#7k1VxjAqM$373XX!O5Guq9nL?pZD|8Bj!mO|=9154htMDsA z3P1rXP(@cocSTP{A4NaKK*eCiFvSSPXvH|i1jS^Ke8ob=V#PAWO2ul$ zTE%+BCdC%TcEwJ`ZpB{30mUK3QN;J;g)CW5xdz&lRr} zZxrtppA=sd-xWUJT0Dwiu)DVvq+lpBpX4N>#P0MpdtBRN+-b6As-J3rYOrdkYJ_T( zYK&^UYNBe2YMN@MYPM>gYJqBzYKdyOYNe`KwN|xWwMn%_wOzGSwOh4UbwG7UbyRg+ zby9Ukbxw6rby;;ybwhPqbyxL3^+@$Z^-T3b^;-2-^+EMX^;Pv<^-J|f6{BvgZli9e zZm&*McUGsUQ`PC}EOm}LUtOp!QJ1MJ)z#`6b-lV#jaL)ZWHnXIP_xt=HD4`MOVl#8 zQmt0&)CRRlZB^UVF11JPR|nN$HKdNHqw0UuJ=DF_eboKc1Jy&+!_*_yqt)Zo6V#K` zQ`OVev($6c^VR>V7pa%3m#bH)o7HR88`PWBTh-gu|EYJY_o@%552=r;kE>6qF- zFRCxAuc>dSZ>jI9@2elFpQxXyU#MTH->ToMKdHZ{zo~z!f2(6Ov6?oTcuj()qo$K4 zS<^+6rpeS~Yw|P&nj%f9rd(5{!D#9<4H~S5plQ-jG&BuE!`5&$0*y!`(a1GQjYgx> z7&K;$RpZdOG#-s#6Vw12NE6XSHUDUOXnJY-X!>ggYKCZrX+~&9YsP9OXeMc8+*u1bf|)#(~^SRFyvq$BHSI);v=(Z-Co^(-67o(-7(!s-D%xf-38qx z-4)$+-A&zX-Cf-S-6P!--80<_-7DQ&-Fw|f-51?A-4ESw-CtcReQSMNeLH=7eWJdz zK3SitPuFMabM$%o0)3IbR9~*I)MNCu`g%Q9kJl6RWIa_+*E97TJx?#ti}g~yT(8n= z^g6vkZ`ND&cD+mQ(fjm4eOM3bVLhtvs_&-nsqd}ttM9KLq#vRmrXQ&vtsko&ub-r! zqMxRpsh_Q%t6!jBs9&sKs$ZdBrEk`+({Ipk(r?jk*YDKt((l#p*B{g$)*sWK(4W$u z)t}d2)L+(L)8EkF(%;qJ*FV%h*8flcT>n!4TK`V}LH|krRsUW8Q~z5ZV~91x8R88I zh7N{ALy{rIkZMRbWEpY{d4@tmv7yvZZm2R~47G-O1J-~y5DjDl)j&5e4IBf{ATWpw zQiI%}G^hGW0R@Hw-ikHVie4FpM&c zF^n@zG)y*3HB2|mGR!f|Gb}JHGAuDHGpsPIHmotMGi)$yHf%L)H|#X*HtaR*Hykt^ zF&s0TFq|@+HJmqGG+Z`ZGu$xTGTbrTH#{^vHas;vH@q~wHoP@_Fnlt6F?=)pH2gOF zHMBCu8QU7$8QU8Zjh&6j#xBNmW2Q0Nm}@LB78y&7WyVTlwXw!nXKXa$j09tok!++H z8Ag_oW8@o!MzK+9R2Wr8jZtSb8qG$l(Qb4aJw~4~UsCV&gL73gaqcvvI9)gK?8_i*cKA zr*W5Yk8z*zfbp>LsPVY*r16aLobiJ3lJSc1y78v*w(+j?iSd8N=f+pYH^z6y z55~{Nug34jpT^(D7*nh%&eYb_&eXw_XzFZAHgz$jn=(z=rd(5jsmN4fDl=7>s!cVf zI#YuQYa*DMOk@+)L^rWa923tZFo{f3liZ{^Tc zgqpgVx|@2MdYk&1`kMxshM0z#MwmvK#+t^PCYmOjrkZA$W|`)g=9w0l7MhlrmYG(V zR+*Yj>r5L=n@n3w+e|x6yG(mb`%DK+hfPOK$4w_qr%mTf7fhE-S4`JTH%zxpcTM+A z4^59v|1&)|y)?Zxy)}I>eKLJ9eKY+q{WASEwKBIhw=u_?+nYO@JDHQrDdtpjhB?ce zW6m=dn2XJ&=5lkTx!PP~t~WQDab|+K$xJcR%nUQj%rW!KLbKQ`HOtLPv)ZgP8_XuN z#cVS>%x<&S>^BF^VKZcom{D_Ab2oDjb8mBBbAR(d^I-E(^9b`O^BD6u^91uG^HlS6 z^Gx$>^IY?M^Fs4t^HTG2^Gfq-^IG$I^G5S#^H%eA^MB^u=Dp_q=7Z+L=A-5l=2PY~ z=5yu?=1bgw$xbaEDaW{1#cl*$QG)FZed#3 z7Oq8L5m_V_nMGkySu_^C#b_~GtQNb)X>nV8mVhN>0W6RuVnHqcSh`z!T6$ahTKZcC zT83DLSw>h!S;kn#SteK}S*BX1TV`5jTjpBkTmH2ywk)+Qx2&|Rwyd$Nvuw0%wrsU* zx9qg+vh1wj8w_x16+`ww$$Guw1fSv0SsjtSQzsYlbz;nq$qg7Fdg{CDw9l zrM22xW396`Sg}^TwaH4hQmu3=)5^ATt$eG{DzVC}3aiShvFfY_tI2Ay+O1Bj+v>IY ztwC$p3R)vp)Y{eB&Dz7-%i71<&pN<5*gDiY+&a=a+B()c-a64b**eWS!#c}4$2!lt zz`D@7*t*oZ+`7uzY+Y+zZ{29!Y~5;4WHoq-s3)>)D#D?0s+Pc|#*m~Le*!tN9*aq2# z*oNCi+D6;P+Q!=^+9unk+NRrP+GgA4+7{Rr+7{cE+Lqf^+E&}v*w)!L*f!a=*tXer z+IHFY*!I~D*bdo_*pAsw*iPBb*v{E5*e=_y+OFGf+HTwK+V0yP+8*1U+Md~7*k0M* z*xuVd+CJO9+P>R<+J4*q+FIFL+uPXV?Fse{_D=RBdx|~Po^H>yXWMh_`SwD4vAxt@ zZm+ag+iUIh_C`C-POvxG$#$xpZfDxrcCMXo7uv;ksa;`L*)?{Z-C#G_Eq0sTVRzX* zcAq_957_}bWRKXR_J8c%?LFj?NjYD?6d51 z?DOmk>>KQx>|5;H>^toL+4tD@*$>zc*^k(d*-zL{+0WR| z*)P~H*{|5I*>Biy+3(oz*&o;+*`L^-*QFdT4vjIG1oEQ@vmc%V~Jy#V})atquH_6 zvEH%KvDvZJvE8xLvCFZ?vCnb9amaDRam;bTamsPVan5nUamjJTam{hVam#VXanJG4 z@!0Xy@yzkU@yhYW@y_wV@yYSU@y+qW@yqeY5#x+?#yQ(M+d11iJ32c#lbk8eRA;&~ z)0yqeb>=$@oyE>lXSuV|S?#QG);SxTSSQ{|bdsDDC(X%lvYZ?z&na+C z8mG=_aGIPJr_Jebx||-T&lzxroPZN@Mx3a#tFxQ4hqITnkF%e1fOC*@h;x{8gmaX0 zjB}iGf^(8{igTKChI5v4j&q)KfpejAv2&?&xpSp+wR4SgopXb8lXHu6n{$WrKj&`e zUgv)2LFZxTQRi{zN#|+jS?77@MdxMbRp)i*P3LXrUFUu0L+4}XQ|B}13+F568|ORc z2j?f}7w0$U59crEA7_j!))nV!>uTp}@9OC4{b3##QHPaA93|7tuv>QCu_^!^LuOTs)V+C2~nzGMB=oa%o&Tm%(LnSzI=k!{u^$ zTs~L86>^1LpbK^(uBhuDS9e!WS8rEeSAW+)*I?IB*KpTJ*J#&R*Lc@N*JRgJ*L2rR z*KF5Z*L>H%u0^gTu4S$ju2rsP*IL(l*GAW7*H+hd*G|_i*B;ki*M8SQ*J0OD*KyZL z*J;;T*Ll}P*JamL*LBxT*KOBb*L~MR*JIaH*E81(*DKc>*E`n-*C*E(*EiP>*Du#^ z*I!pFcWZYWcf32n-NBvc?(9x>cX6k=Gu&D39Cx0(z+L1nahJI(+*NLjyVhOrZgk_^ zcsJ2aa#P$iH^a?xbKE?)z%6o1+%mVqt#WJJI=8`Xa$DRsx5Mpnd)!{P-yL*^-JlzG zBkrjCA9r_mPj_#3Uw41^K=)wxQ1@{6NcU*>Soe7M1otHO6!$du4EHSe9QQo;0{24q zV)s(_a`#I2YWEuVI`;`C@?@uYb&JXxL`Po5{=Q|Kx7lzPfNm7Z!(ji=7j;K6$E9-@cjp?GK> zx`*jud$=CHN9YlIq#n6P=}~*M9=*rtF?%c?o5$gCc|0DUC*TQr01xDecu-GQPd86@ zPft&8PhU@e&p^*$&rr{B&q&W`&sfiR&qU8;&lJxz&kWBj&m7M@&jQav&tlI~&vMU7 z&uUMzXRT+wXQOAcXRBwsXQyYEXOCx}=YZ#s=dkCf=eXyj=d|an=e*~l=d$Oj=ep;n z=eFmL=bq<*=aJ`$=YO8(o|m52p0}R&o{yf-o-dwno*$lHo%@Y+t%C8+uqyJ zo9ONAP4;&2rg<~GS>7CPp0~hT*Wq<}Jzk&J?+tpxUeF7B5pUG{kGH$Gr?;24 zkGG$9fOn90h~zV~16LhoYlQtxu_O7Cj# z8t*#q2Ja^C7VlQ?cJEH_F7F=iKJNkVA@33IG4FBjN$+XzS?_u8Mek+rRqu804eu@Q z9q&Ew1Meg66Yu}L&%H0bue@)(@4O$ppS)kZ-@HG(zr26Ef4!}It$l5L@xBCK2VbJE zvoFb);!E|V`!apmzFc3vuh3WIEAf^2DtuKwjIY*L?`!m7eRvy_1$|*3=!1QTFY5cp*WK5{*UQ(( z*UvY=H^?`{H_SKOH_|uSH`X`aH_6x4^g1x7fGTx6HS~x60S- zTkBiz+vwZu+v3~i+u{4qx7)YZx8HZrci4Btcg%OfcglChcg}agcgc6zchz^@chh&< zch`5{_t5vq_r&)<-*ewf-)rAn-#gz2-zVP}-#6b6-!I=E-(O!Ve`|jmf4o1z-@%{g z@8nPNr}$I->HbWAwm;XO=P&RV`Ahs|{tADUzuI5puk$zfv3|Ut=qLHfeyX4DXZqQG zuAlE0_(gt+U*=c%Rep_M=hyp`Y-#h`mgzK z_;2~|`0x22_#gQn`=9!s`Cs^7`QP~8`QQ6L`ak=>`oH^s`hWTV_+tXGfw(~1K)XOf zphF-r&^eGC=n_Z^qz5ts*@4_ZexNW=6etOl1u6nn0ZgDaP#0(jU<3F7F+d8C1JnRL zzznbh+yE~i2#5lbfGnU0r~>MMHlPm}1LlA=U<)_`u7D@t3j_keKsW#f-~bYc2L1_j z3-k!|3iJu|3k(Ph3=9qo4Ga&A42%wp35*L&2uuo02}}!256leA4$KYA5BwWg7+4%w z8dx4!8CV@?4y+BV4{Qu<4r~o<3+xE|7uX%x8`vK>5I7V#5;zt(5jYh%9XJ~}AGjE} z9Jm^|7Pt|(6}S_)7kChO7x#2NQxFf*pgMf=R)YU}`Wum=Vkh<^=PC1;L_Vaj-O49;^&j2Qk6gV12MLhzk;e z#2_h13DSa$AS=iYa)bP!Fena6g0i3@s0wO=x}ZL244Q-1pgrgax`LjdFBk}hg5e+- zgo8*h8tfYE7VHu173>r27wjJ#7#tiN8XO)R5gZj96C4+u5S$d89Gn`Q9-JAR9h?)K z7hDir7+f4&5?mHs5nL5)4z3NZ3vLK*3T_E*3vLhY4DJf<3GNH-4;~C24jv6251tI3 z3Z4m`3tk9b3SJIg4PFo44Bigj3Em4n2tEov2|f)z3%&@x3cd-x3%(D241NxN4So;) z2>uHG3C4tCL#;z?Lh+%5P=`>*P^VB*C?%8{N(*I#vO+nbyik6qFjO2W4V8x~LRBG5 zs5Vp|Y6xLN_z*Ef3Xwz95Iw{Uu|u2?FC++wLXwa)Bo8S=>X0_13mHPDkR@ab*+b5d zJLC=dLxE5z1cabaB!q;bp?^Z%Lp?*iLVZI0LIXmBLW4s?L&HNOL!(1uLgPXcLX$#M zLQ_N2Lo-9OLvus(LJL9*LyJR8Ld!xcLaRc}p*5j(p$(x;p)H}Uq3xlap zhTerfgg%A7guaEohkk~BhyI3Q!m;7FaNBUZa6-64I5FHgoE%OGr-swRnc=K(PB<@I z5H1QAhfBld;mUAT7!$4y*M}R!*f2g!43ol?Ff~jMGsEmKC(H{A!lJMwEDg)U%CI`D z3G2d!uqkW_Tf_FSGwcp~!oF}I90~(rFbs#0a5UUC+%4QA+$-EC+&A1mJTN>sJS03U zJR&?QJUTo!JU%=zJUKiiJS{vUJS#jqJU2W){BL+scyV}XczJkbcvZMLyf(Z(ydk_P zyd}IXyd%6byeqsXyf3^zd@y`Cd^CJ4d?I`*d?tJ@d_H_Jd^vnId@Xzd^dbQ z{4o4D{51T(@bmD?@ayoK@VoGb@Tc(S@YnG7@XzqC@SkuD5DUZsZGd`d%76->3aAEZfI6T7XasNo0cZk900p1{41fu+0WQD? z1b_&T05U)hC;>H~1@wRcFaZ|82G{{7;0C;a4+sDu002M$1`r?$bOpKrJ%C<7Z=f&G z9~cM>0)_y?fDyn*U^Flm7!OPUCIM4`X~1+~CNLYA3(Nx+01JV|z!G2?umV^GtOnKq z>wpcwMqo3r71$2!0R97Z1ABpezyaV8a0ECC90yJUr-3uTIp6|t3AhYg1+D`(fm^^G z;2!V*cnCZOo&wK+=fF$gHSiXA2YdiN0bhWxz<1y$@EiC8#DKA29M}eo2NS>!U`MbM zm;|PPUBEOj1Iz-m!CWvOEC7qZ60i&`2P?s9um-FJ>%m442jW2@NCGJ!6{LeqkPUJ` z9w-1spcs^ba!>)PKn?g96K2f#z%Velw;96Sl00?&Zw!1Lfm@G^K6yawI? zZ-IBfyWoBBA@~S<0{#zt4!!_ifp5Tf;Ct{R_!;~Hegl7izrf$%U$7Mv3&lZgp>|LL z)B#F_IzvfN3X}?^K^agMlmq2L`A{KL43$7-Pz6*8RYNsU9aIlBLO2Ky5g`&pfv6B2 zVnQs41MwgMB!t9}6p}*!3oFoZx+s4LVB z>H+nHdP9Ao{?Gtu5HtiD3Jr%wLZhKE&^TxUG!dE%O@*dIGoV?}9B3{yANm(s1TBV^ zLd&5Q&?=}IS_`d%Hb9%8&Cph8JG2w}584gwh4w)QphM6R=qPj?ItiVE&Oqm&^Uy`; zGISNX2Hk*eLARm1(0%A3^ay$a{SSHuy?|apZ=ko(d*~zd3HkzkgT6yQq2JJ7C2M~T4d=pnZ~pzlGn!AK}mN7x)|e z1O5sBhX2Abk=RIFq)j9~k`QSh=@{u0=^ROpbcv)z(j%FX>_|=|FH#UGj1)&oBW00_ zNL8ddQWL3*)JGa4xCkLajF2Lf2sJ{FFe9u8C&G*HBf^L{B8kW%iik3zj%Xvgh#_K% zSR&SlJ>rbGBA$pZ;*SI);Rq0cB9RCZiAMg3bc^(e^osP3^o{h742TSh42cYl43CVA zjE;aG* zY>8}(Y>(`W?27D;?2YV?9Eco>9Elu_9FLrgoQj-@oQs@~T#Q_fT!~zZ+=$$Y+>YFh z+>bnnJc>MtJdHeyyokJvypFt$yo-E@e2RRIe2sjM{D}OD{E7UHv_e`VaY$RF9g=`_ zKoXHoND`8QbV1UP3?viDMskrnqyQ;Gijh*J9H~I65DZd-)FBN>BZ5N+2oWJ66oiV< z5hlVyI0z5nBSJ)kNDvt!N0f*f(I7g+fEW=oVnu9-192g4#EbZm01`p~1Vms2K~SVC z(hcd3^hA0ieUN_00AwIC7#WHTLq;H@kkQCkWIQqfnS@M1rXtgknaC_;4l);+kNk@) zL>41Uk!8pVWEHX+S%a)Y)*~B{&BzvH8?po0iR?o5AbXMh$U)=~as)Ys97j$fr;szq zIpjQY5xI<9L9QV;kekSDEhYGzZN^^U*@I2rWU&&~mgAtwu3u zEn0^*pjZ@#63`};gi=r%N=KO}8|9!pRDcRmF)Bf2r~*}@YE+BrPy=d0&8QW%qYl)C zx=}CcM+0aG1yB%$Q3OTNu4p&3JK7WNh4w-Fq5aW;=wNgRIt(3wjzmYJW6*Kv1au-g z8J&twLua6~(Anr*bUwNOU5GA1m!Qkg<>*RuHQJ1>Mc1Pn&`s!ObSt_Y-GTmx?nd{Z z`_KdELG&pij|f=yUWX z`Wk(MzC%BtAJNa~7xWwY1O18qM*pHQ(b#BQv`sWV+Ai8Y+A*3K?Ho;xrbJVtY0->m zRx~@B8_kavM2n&&(b8ynv?5v+#YAhOb4~kMS&<3g`-F`8toeG7VRGG z8SNeI6YUor5FHpD932uJ799~C`F{k#0{|Nd0sz3;wr$(C-F|I5PIj}l&D6GSTy5L7 zt)B^S0U;m;;0!F|L*a0`-1yX@Ifq8)ifrWv^fu(_Effa$3fz^SvfpvimfsKL9 zfvtgUfgOQef!%?rf!l#Q zfqQ}bfro*|fhU1yf#-pjf!BdIfp>uqfscXDfiHn?fggdNf!~2Yfq#Lt!F0h4!A!x- z!K}e-!5qO{!Q8>T!F<62!9u~p!J@(9!4knz!P3F9!ScZh!Ail(!K%UP!5YC@!P>#P z!TP}l!A8L*!KT6H!4|<*!8XCR!S=xp!A`+0!LGsX!5+b0!9Kyh!T!O4!9l^n!6Ct6 z!4bic!O_7n!EwO}!HL1i!70IM!5P7s!P&t;FcgdgqrrGE5d?!!5Dub2JV*qoART0b zd{78VK{==f^`H^7f_Bgi`oUCiZg5_3L2yxUad2sHS#U*gRd981ZE#(1LvT}Yb8u^L zTX08kS8#W5Z*X7mK=4rTaPUa*Xz+OOWbjn*Oz>>*eDGrMQt(RfYVdmSX7E<Pw;OrO(aU*Yp8puXQ)@GPpEIGe`sK6P-t*yNN8AS zcxYs3bZAUyTxfh~VrX(`N@!YWdT3^7b|?@Eg~Fj|C>~0Lz)&&-hma5!B0^+{4lyAv zB!t9}98yAB$OxGsJLH7CkRO^8nirZMS{PasS`u0oS{_;%S`}IoS{GU$+8Ejt+7j9t z+8){&+7;Rp+8f#*Iv6?>`Y&`ObS!i{bTV{0bS88zbUt)3bUAb-bS-o}bTf1-bSHE# zbU*Yk^eFTs^epr|^fL4+^d|H+^gi@4^eOZu^fmN7^fUA;^e6N;lqQ@uoIac}oGF|o zoHd+1oFkkooF|+&oIhM3Tqs;5Tr^xfTq0a5TsmAfTs~YOTq#^RTs2%RTq9g7TsvGh zTrb=p+$h{Q+%()Q+#=jE+&bJg+%DW9+%eoa+$G#C+#}pG+&kPS+%MceJTUyf@Zj*! z@UZZR@W}A!@YwLU@PzQh@Z|86@U-xZ@XYY+a3CBCN5au?Je&xF;ba&NqhTyegvl@+ zX2M)p2#aAktc10&9yY^v*a>@KKRhQqH#|SQFuW+dB)l}dJiH>jD!e+pHoQK(A-pNP zIlMKzExaSVE4(|rH@q)=Abc=|XJ=BK$J^I{YU5F8n_HG5k6FCHyV?J^VBLEBq(?FPtWlE|NZy zF_I~gC6YCgJ(4q$E0QOYHIMO82EYdvEGSWKICekj_KGHGLDbgj6G0jD#bRNGuYM01+?(Mc@b; z!6QV3iqH`@!bOCL7?C4tM2i>^Gh#=ah!^oAb0YI1^CJr*iy})ROC!r8DmwT?ndJ0iOxyCZue`yvM-2P20g|3!{QjzvyHPDM^f&PL8fE<`RyE=R6J zu0?J{ZboiL?nLfI?nfR*9z~u+o<^QWUPfL;-bCI;-bX$}K1Du9zDB-Benfsneni$_aFOGV2>%SOvbD?}?r zD@UtFt4C`@Yej2E>qhHE8$=sM8%LW&n?+khTSi+)+eF(%+ebS_J4L%hyGFZ5dq#Uj z`$YRj`$q>v2SxuE9TFWH9UdJS9TgoD9UC1Voe-TAogAGSofe%Dof(}S4Ms!JNHiLa zM-x#nnvB9xB#K3eC>f=rOq7fAQ86k-m8crkqej$Chd9}{9yOo^#6J!ZtLm>qLtUMv-x z6Pp*CA6pn(6k8Ho8e1M)5nB~o9a|e)7uyiq6x$rz8rv4z5!)Ht9orMz7uz2@7&{dE zFLoq$EOtD0GIlCTG+r!TB3?3HI$kDTE?z!fF58;bK>*j^WzKSi{eY-OXJJqE8?r-tK)0q>*5>Y8{?bfTjJZ| z+v7XqyW)G|d*l1#2jYj~hvP@$N8`uiC*r5#r{ibi=i(RQ7vq=XSK`;=*W)+ix8ir= zcjNct58{vFkK<3{&*CrQFXOM{Z{qLb@8ciipWPi9TJ@qofBOX-4Z<#JrlhXeG>f= z{SyNdgA#)iLlVOh!xJMDqY`5hV-w>O6B3gWlM_=D(-JchGZV8DfkY?~PDB&2L?Qts zk_jk*B+vw&AQDu9POu3sAtc0voKO;4LQj|pE8!&Egr7(y<|gJP7945Y=Mj#WA1;`3y2XX+pfZRY{ARkZwCc0F{8sKoy`GP#vfV)B@@Nb%FXo1E3Mm7-$MK16lwrf!079pdHX2=m>NI zx&U2)?m!Qq7tkB%3-kl}0|S9Uz+hkqFbo(Dj08pjV}P;1cwhoB378B_1*QQrfSJH- zAOM7bFc1Y|Kmq^&5P$#}KmiOO01}`92H*f55CI8L02R;y1F!%aZ~+fU0ds)4z(l+a11yO91l(eCxMf}so*ql z1~?O(4F(U%;>6ckl=J3;YfK1^X+lBJVnlI4=+lNFPdl2wvblhulYNr?lKqndlY^3jlS7ijlEafDlcSQO zlVg+Pk`s~>larHElGBpYlQWaElCzV+WGER)Mw9VmA_*pwNhpaV(IlQEl2npTvPmu} zB*mnZRFY~^PZ~)pX(!#Jm-Lf!l5>;ulM9lIl8ci|lgpCJlPi;}l53J{lk1ZklADs7 zlUtM9lG~Fzle?08l6#Z;lLwNAl82N3C66YLB~K(zCQm2NB+n(!Cod!~B`+thCa)!L zByT2fC+{ThChsR7Bp)RoC!Z#tC0`_8CSN7rB;O|ACqE=VB|j&>Cch=WCx0e?CI2M< zCeuJ^q4ZD&C?k{^$^vDBvO_tcTu>e;FO&}|02PD^Lq(usP;saPR0=8$m4(Vd6`+bx zWvB{N6{-%^fNDXtp}J5#r~%XvY6LZbnnKN?7EmjwHPi-b2epSfLY<&4P*>Ljoj0GNeKpq(dfTK@Q|XK9qvyK=Yva&_ZYtv;5C(XeYEA+5_!{_Cp7tL(pO92y_%W2AzOTLZ_iK z&^hQlbOE{qU52hg*Pt8FP3RVM2f7R0haNzWpvTY?=o$1JdI`OP-av1mchCpuBlH>i z0)2zNLqDKj&~NB3^bbl4r-ReO8R1ND7C0-M9nJyggmc4r;Cyg?xFB2zE({lii@_z} zl5lCb3|tm24_AOI!Ij~va5cC(TobMZ*MaN8_2C9^L%1>A1a1a5hg-s};MQLvR?5!ZA1w1270fFbpFw2IDXZQ!oRwFb4~;2+ObnYp@O*um#(&3wv-1 zo&(Q?=fex&Met&H3A_wm4zGk)!E4~P@H%({yb<0EZ-KYM+uL^2^+kgP~HBnOfc$&KVe z@*(+=0!SgGFj5pLh7?CiBBhWrNLi#jQUR%mR7R>G)sX5)4Wt%Q8>x%bL+T?9kw!=p zq$$!IX@RsvS|e?cc1U}q1JViUjC4i1A>EOlNH3%h(iiEE3_u1V|3d~NLy=*~aAYJh z3K@fpMaCf$kcr4-WC}73nU2grW+AhYAQD2tNEC@72?Rhu1VUg0MKA>MqI>0d}IzX7nzSNKo%m4ktN77WI3_|S%s`d)*|bW^~gqK6S4)_ zifl)AAUl!W$R1=LvL88s96}BwN06h)G2{et5;={WLCzxQkqgKr!S_PMraeXDcTHefwn|jp>5E%XnV8++7a!Hc0s$L-O(OsFSIw> z7ww1kM+c&V(81^sbSOF;9f6KQN26oVap-t-B0343j7~+Tp)=5#=qxmV2GKAYL8E9K zO`sr}L?IMGQ4~iBltgKiK{=F11yn+1R6#XVM@`g1ZPZ0QG=^~U;Q{jmPnKx_~;7#o5O#fD=eu#wnkYz#II8;?!E zCSjAYDcCe@IyMuVh0Vr-SO|+?Q7ncfFaQHF2!k;c!!R5pF$!Za7UM7h6EO)>Fcs4= z12Zujb1)b4u@p8Jn}^NE7GjIACD>AI8MXpjiLJucU~94U*amDPwi(-kZN;`@JFs2Y zZfp;>58IC&zz$)Du_M?~>=hr{*lp|%b`QIc zJ;WYikFlrNGweC`5_^Tc!QNu;un*Wr>=X6{`-*+XeqcYb-`F4QFO~*Ri>Jpk;2H7E zcosY>o*mDD=fZR2dGLIAe!Ku)2rrBm#f#y^@sfBcyfj`GFNasaE8>;#DtJ}A8eRjh ziPy&K;C1o(cmup4-WYF!H^ZCbE$~)&YrGBK4sVZl#5>`g@ve9`ygS|#?}hiq`{MoZ z0r)_C5Iz_mf)B-q<0J4<_-K3#J`Nv`PrxVPlkuteG<-Tf6Q708#)EhW593ihh9__U z2XP38aRkS394B!Kr*RhNa2^+N372sd*Kh+jaSL~F7x(ZKJ_nzN&&L3||k)6mvji4sI9qBK#KC`XhhDiW25%0yM78d067Nz@{06LpDtL<6EB(THe5G$oo5Er^yx zE20h2mS|6OAUYDAi7rG}qC3%p=t=Y@`Vf7I{=@)c5b-}^Ffo)EMhquL5~GOG#8_e+ zF`k%6Od_TbQ;BKB3}PlRiwF=wB1A-pC=nwP1VAJSh=2){zzCcm35uWzmf#4U5DAG; z2$j$XgD?q;a0r+1h!io0m`ltj77z=G#l#X~8L^yLL98NH6KjZd#Cl=_v5DABY$3K0 z+ligTE@C&am)J+_Ck_&ah{MDY;wW*9I6<5wP7!B_v&4Dg0&$VJOk5$Z64!|v#7*Kh zafi4|+$SCo4~fUb6XGfHoOnUJB3=`3hH}Qw~N2DRs zk?F|{WF|5*nT5fzS)Xi3HX<97P040tbFwAbiflu+CEJl5$c|(uvJ2Uj>_+w= zdy>7#K4f3AA31;=NDd+glS9a%d9vL$V}K3ZzKNq(Z8sP8y_1+N49eq)(>EIpjQY zKDmHgL@p+mkju#BnW8?|)BzcNFL!Kqikr&8|UGg6JfP6?kBA<{?$!FvX z@+J9-d_%q^-;p24kK`xv3;C7&M*bjwlE275dicuw~l2j?G3{{pYM^&IIQkAGGR8^`PRfDQY)uQT9b*Xw( z1F9j_h-yMLrJ7MKsFqYKstwhaYDaaTI#QjeE>u^l8`XpAN%f-oP<^R>)BtKAHHaEa z4WWio!>JL}NNO}Sh8jzarzTJnsmat7YAQ9InnBH^W>WzwNQJ2g6{X@-f&!={1yL}C zQW%9(Bt=m)#Znx_Qz9i%GNn=)rBfzlQ8wjL9_3SWsJYZUY5}#7T0||OmQu^871T;< z6}6gLORb~UQyZyG)MjccwT;?N?WA^5yQ#g@K59R8kUB&irjAfYsbka$>LhiFIzyeM z&QTYri_|6R3U!sbM%|!pQn#o()LrTx^?-UvJ))jaPpN0r3+g5Hih4u6rQT5=sE^bq z>I?Oi`bPbrep0`v-_&2~AC-nqN2jMV(3$AWbQU@rot@4>=c04ddFXs}e!2i%h%QVQ zp^MSQ=@N7)x-?yeE=QNAE6|nb%5)XF8eN^PLD!^f({<>&bbY!3-H>ifH=&!-&FL0& zOS(1PhHgu@r#sLc>CSW)x+~qC?m_pYd((aBzI1Q$1!*qm>(lI(g12jlOG)yBjM&mR=Q#4I8G)MEa zKufetE3`)Iv_V_6O*^zp`*ez)L(ik<(+lWD^kRAmy^LN?ub@}atLZiLI(j|5f!;)K zrnk^r>Fx9mdMCY`-b3%D_tOXHgY;qgKl%uLj6P1Epij}K=`-|M`aFGszDQrDuh3WN z>+}u!CViW}L*J$E(+}u}^ke!7{gi%2zo1{zujn`QTlyXSf&NH;qQB5z>2LH8`X~L1 z{!Ra-|IulfbWD0C1Cxo#%w%D*G1-|MOfDujlZVO68ca>57E_0*%hY2UFb$bTOcSOl(~N1(v}9T_t(mq=JElF; zk?F*AX1X%nnC?tZrWezj>BID6`ZEKVLCpV{!OT!*7&Dw1$&6w~Gh>)>%y?!3Gl`kZ zOkt)m)0r8}EM_(nV1i7Ti7-(n&LkLsNiq-vGbn>GI72WLLo*D+F+3wM5+gGTqcJ*T zFeYO&4&yRDlVavD^O*U}0%j4jm|4OsWtKB5n3c?GW(~8JSHZq%-EzDMC8?%Gi z$?RhGFngJO%mL;gbBH<29AScm_N*4<{y)m zO~p7GaCB#n}>UNwzdwhAqpMXDhH3 z*-C5`wklhVt-;o0Yq53Mx@_m1FJB6LfPGe`V zGuc_}Y&OV-*f1MqV{DuSSddM!5R0%Vi?IYtvJ^|REX%PxE3y(Rvns2xI%}{NYqJjP zu|At(=dknG`RoFA5xbaO!Y*T%vn$w@>}qxmyOv$gZeTaEo7gSvR(2b^gWbvQV)w9n z*?sJO_8@zRJ}B=}dyT!$-e7OBx7j=FUG_fv zfPKh5W}mQ6*=Otv_9gp@eZ#(G-?1OqkL)M*Gy9eO#(rmivcK5h>>u_Yn}$ovrROqm z8M#bc7A`B7jmyF1)?6jz!n!>Jxn^8*t|ixsYt6Oc+Hvi<4qPX$GuMUd%5~>@ za6P%+TpzA4*N+>(4de!KgSjExP;MAEf*Z+=;>K`expCZhZX!2{o6Jq+rg77`8Qd&x zHW%PRT$qb+Q7+CUIDkuX5C?MzhjBPZa1=*#499XjCvYMsa|)+&8fS1OXK@baavtY% zbGW(OJZ=HEkXytp=9Y5HxaHhRZWXthTf?p6)^i)UjofB#3%8Zq&h6lKa=W-a++J=U zcYr&{9pVmiN4TThG42F+k~_tn=FW2Gxbxga?h<#IyTV=Lu5&lIo7`>g4tJNk&pqHC za*wzt+*9ru_kw%Lz2aVTZ@G8ed+sCmiTlib;l6R-xgXq5?l<>``^%-_)AH%~^n6A> z6Q7yS%4g%V^Evojd~QAupO??i7vKx>h4~_UQN9>of-lLJ;>+-5`Eq=Dz9L_Vugq8B ztMS$O8hkCjHeZLY%h%@{@D2IKd=tJY-;8g;x8z&#t@*ZmJH9>Nk?+KJ=DYCS`0jiU zz9-+C@5A@y`||_%f&3tTFh7JJ$`9v9@FV$A{1|>LKaL;IPvj@@lliIqG=4figP+CE z<^z0?5AzW|%E$Qx5AYxl@i33@D39|5Px3U+@GQ^q0x$9sFY_v|@j7qv7H{(o@9{pL z;^*-5`1$+-ei6TzU&1ftm-8$5mHaAx4ZoIO$8X>_@|*b0{8oM&zn$N~@8Wm!d-#3) ze*OS|kUz}-#~4e}+HHpW`p^7x_#4W&SFEjla&{;BWD_`8)hQ{yzVJ zf5<=PpYTulXZ#EPCI54SLi477X}D}g#QVHg(1Q)VYo0t7%7Yv#t37Dal!;)qA*FAB1{#g3Dbp{ z!YpC75D-E_ScnKwAuc2YKmY|ufCWTA1zaEmQlJG!U)V1Y2+f zSMY_DFh`gt%oi323x&nP5@D&ZOjseT6jlkVg|)&uVZE?H*d%Ngwg_8=?ZOUWr?5-d zBkUFS2?vCO!Xe?Xa6~vN921TUCxuhOY2l1;PB<@I5H1Rrg)72U;hJzmxGCHcZVPvX zd%}I;q3}p}EIbjO3D1QW!b{<`@J4tmyc0eMAB9iCXW^^xP53VS5Pk{2g+IbyA&r<; zOedxnGm4qS%wiTXo0wh9A?6fwi+RMnVt%oJSWqk^77>ez#l+%bNwJhzS}Y@$6U&Pg z#EN2Nv5Ht#tR~hFYl^kR+G1U?o>*UOAT|;ki%rC)Vso*D*ivjIwh`Nk?ZozCN3oOG zS?nTq6T6E&#GYbrv5(kS>?aNo2a1El!Qv2cs5ndDnqi-bstw8)68$cus~ijt^^s;G&& zXo{9-i;n1tzL*l{i1Wnx;sSA@xL8~wE)|!FE5w!JDsi>AR$M2p7dMET#LeOsajUpp z+#&81cZqw%z2ZJ`zj#nQBpw$36OW3=#N*-#@sxO4JR_bJ&x;qti{d5mig;DLCSDhB zinqku;vMm>cwc-VJ`^8`PsFFa#DGzf>cqeELD-JO4XzqQcbCrR9mVm)syN=4Wvd=W2uSMRBA4@kXlNuq&8Ap zsh!kb>L_)RI!j%oZc=xthtyN*E%lN5O8un%(m-jD^gn5^G*lWU4VOkpqomQ&7-_6D zUYa0HlqN}2q^Z(0X}UC1nkCJa0#Zl{OA#q5#iWD;NT8IIU36ltklqiXoSc#K( zNsuH-mJ~^qbjgrR$&zfzl|0FpQqo*$o-|)tAT5#>OG~7s(sF5qv{G6nt&!GB>!kJ4 z25FPDS=u6Pm9|Seq@B_(X^*s5+9&Oo4oZil!_t4!QR$d;Tsk40luk=$q_fgF>4J1o zx+Gneu1eRW>(UMBmULUXBi)tmOAn-n(j)1y^i+B#J(pfcucX(~8|kg|Uiu(?ls-vc zq_5I9>AUnp`X&9A{z!kNG;&%wot$3IC})y0%UR@Xa&|d~oKwyv=aKWu`Q-d^LAj7z zSS}(LlZ(qGLHCJW3udkCDg98Ilnh zl`$EY37L{zQ>mrY zR_ZAAl=?~orJ>SDX`(b$nkmhdmP#w7wbDjutF%`-C>@nfN*AT8(oN~E^iX;!y_G&n zU!}h?KpChEQvRn5QHCnRl;O%qWt1{n8KaC<#w!z)iOM8piZWH1rc766D6^E=Na#gve+)!>Rx0Kt;9p#>K zUwNQBR30l&l&8uw<+<`wd8NEo-Y9RC_sR$5qw-1lqI^}pDc_YJ$}i=&@<;ir{8Q4Z z>D2UU1~rqKSMC`$x>jAMu2(mx z8`aI~7Imw-P2H~URClSn)jjH7b-#K*J*Xa1535Jiqv|pBxO!4OrJh#LsAtvl>IL

NoX=`cwU-{#O5}|I{>ES}mQHUdyOu(lTpVw5(coEr*s<%cbSk@@o0C{8|C6 zpjKEbq7~JOX~ngYS}CoxRz@qUmDeh06}3uQWv!}KO{=ce&}wS6wK`f|t)5n2Yp6BS z8f#6ordo5Yh1ODQrM1@DYVEZ4S_iG8)>-SKb=A6Q-L;-tFRi!MN9(Kg*9K?21=e3L4CGE0yMZ2n9*KTMxwOiV4?XGrDyRSXa9%_%Z zC)!i(nf6?JslC!(Yj3o-+I#JT_EGz!eb&Be-?Z=A5ACP+OZ%h!)&6N|^t5_G}17dLg~AUPLdd7uQSZCG}EzX}yeIPA{)l&@1Yd z^eTE)y_#NKuc6n{YwLCNx_Ujmf!M& zMenM2)4S_E^oheVx8u-=J^O zH|tyUt@<{7hrUzarSI1F=zI13`T_l*en>y8AJLEM$Moa+3H_9QT0f(o)z9hY^^5u? z{jz>Vzp7u?Z|FDmTl#JNu6|FyuRqWq>W}m%`cwUx{#<{dztUgpZ}hkNJN>=>QU9cW z*1zaq_3!!*{ips*|E>Sk|LJLrv_?83y^+z#WMnq77+H<%Mh+vVk;}+!yG#uj6nvEA5V>@;>6dyKutK4ZUe zz&L0eHvTh?7)Om`#&P4MamqMtoH5QC=Zy=-MdOlj*|=g{Gp-vqjGM+S8UyQHDH{*x#)A(ilHvSm@j5KCi zGo6{<%wT3RGn-k=tY$VdyP4C>W#%^Xn0d{7W&yLHS;#DG7BP#N#my3CNwbt$+AM39 zGs~M5%!+0uvx-^OtY%g>YnV07+GZWIu368lZ#Fa=nT^dRW>d47*}`mTwlZ6rZOpc2 zd$WVt(d=Y)HoKVJ%}~cn`t+kU7{KVh%NjnZwNy<|uQtImR4o zjx#5i6U|BHWOIr+)tqk5FlU;x%-LqZ44GjwVn)rF88-nFG?OM|!X{#3CT6xzt={E;m=0E6vsB z8gs3=&RlP9G&h->%`N6ubDO!t+-dGIcbj|6z2<)NfO*h7WF9vEGmo0b%;V+>^Q3vo zJY$|U&za}V3+6@hvU$b4YF;z1n>Wl`=56zidDpyW-ZvkZkIcvB6Z5J0%zSRXG+&vo z%{S&-^PTy@{AhkMKbv37ujY62hxya|W&Sq*nE%W)R$42amEOu=WwJ6`S*)y9HY>Z8 z)5>M#w(?kct$bDitDsfLDr^<8idx035>`p8lvUa)W0kYYTNSK|Rwb*lRn@9yRkvzb zHLY4!ZL6+T&#G@Vuo_y8tR_}dtC`i@YGJjsT3cSY54dR(GqX z)ywK_^|AU|{j34jKx>foKWnfx#2RW1w?E=Sph3(g{-g@v7%PoN?3peT1gACU<VmDXx&jkVTVXRWt3SR1X)))s54 zwawaY?XY%PyRALeUTdGV-#Ta=vJPASSx2m+)-mgZb<#Rzowm+cXRY(r1?!@9$+~P^ zv94OztsB-&>y~xfx?|n5?pqJ6ht?zOvGvq?W<9rFSTC(t)@$pn_0D>4eXu@SpR6y| zSL>Vg-TGnuw0>ECtiRSjD~+AjPG@JZGuoN#%yt$#tDVixVdu1S*}3gJc3wNbUBE7A z7qSc6MeL$>al3?F(k^9}w#(S%?DBR6yP{pmu54GatJ&4<8g@;)mR;MfYuB^u+YRi7 zb|bs7-PCSoH@92ZE$voz8@sLD&TemaushnF>@Id!yPMtJ?qT<|d)s~NzIH#mzdgVn zX#dY1Y!9)A+QaPO_DFk_J=z{)kG03y=u~8edahtR$o3sUTiP1m)gth<@O4DrM=o-W3RQ>+3W2M_C|ZNy~W;YZ?m`CJM3NdZhMct z*WPFEw-4Bd?8Ekd_7VH2eat>?pR`Zer|mQLS^J!Q-o9vGvM<|L?5p-Q`-XkfzGdIG z@7Q?O*n9 z`;YzC{%5Ck(mCm!3{FNTlas~C>SS}WJ2{-3PA(^plh?`TLic>C|%SICY(RPJO3=)6i+;G;x|b&79^= z3#X;i%4y@Yb=o=YoeoY%r<2pg>FRWIx;s6bo=z{PkJH!b=k#|5I0KzQ&R}PVGt?R8 z40lF2Bc0LC7-y_A&Kd7aa3(sFohi;#XPPtJnc>WIW;+2V=!BfG6LF$W+(|fq13F0u za$pB_Fb8)Chjb{1c36jVct>zVM{;CGbu>qJ499dV$97!Db9^V|%yH&A^PKt4LT8b) z*jeH%b(T5Hot4fiXSK7&S?jEG);k-WP0nU#i?h|)=4^L%I=h_R&K_s4v(MS@9CQvj zhn@eNBhFFhm~-4Y>6~&-J7=7;&N=72bJ4lvTz0NFSDkClb?2sY%en2`aqc?zocqp0 z=aKW+dEz{Eo;lB*7tSl^we!Y#>%4Q`J0F}+&S&R~^VRw0e0P30zntIBALp<0&q?E^ zbCA}H?y0?&FW@zbGSL(TyAbRkDJ%c=N51ax`o`rZV|VrTg)xtmUK(GrQI@a zS+|^9-mU0Xax1%4+^TLhx4K)?t>xBs>$r8@dTxEUf!oM!>^5>LGEC8h&$9B<_>p9xFg-s z?ihEhJI)>NPH-o>liVrpRCk&?-JRjibZ5B%H|U1kup4osZp@9lfD5`w7jj`2aZwj{ z372#!mv$MKbvajXMOSiVS8-KWa}C#YE!TD(*L6MDcjvfs-FfbOcY(XmUF0rym%7W` zq?XGp#x$E5x?nZZ$yV>31ZgaQ0JKUY_E_b)P$KC7hcMrG+-9zqS_doZD zd(1uVo^VgPr`*%-8TYJv-o4;nbT7G=-7D@@_nLddz3JX^Z@YKgyY4;rzWdO95x_qY4U{p}Bz?dfB|}UJfspm)pzZ<@NG;`Mm;OL9eh^#4G9*^NM>VypmoiuZ&mLE9aH> zDtHyWN?sMOs#ndc?$z*WdbPaTUR|%ASKn*kHS`*JjlCvbGq1VV!fWZZ@>+XsytZC@ zuY=do>*RIzx_Di^Ze9l%Vo9@lRt1$dpEqB-YxI8cgMTu-S-}N54}g;WABOg z)O+T=@Lqbayw~0v@2&UFd+&YpK6#(LFWy)0oA=%O;r;S{dw;yY-ajvmpVm+3r}s1Z znf%Ot7C)<>&Cl-V@N@aO{XBkNKcAo9FW?vS3;9L-qJA;IxL?9A>6h|L`(^!detExw zU(v7RSN5y;RsHII4Zo&e%dhR%@$35a{04qQzmebAZ{j!goB7TC7Je(gwco~X>$mgU z`yKp_erLan-_`HtclUevJ^fyOZ@;hK&+qRK@CW*X{Qvob{UQD^f4D!wAL)!?%3fcYN3PeBV#`bNspfe1Czz&|l;)_Lule{bl}g zf2F_5U+u5)*ZS-H_5KEbqrchT;&1i0`P=;+{!V|FzuVvI@ALQj2mFKnA^))dpMS(Z z<{$S@_$U2S{%QY=f7UA&({`)~Yr{(Jv}|Iz>CfA+ulU;S_X5C5nC%m3~F@&Ee&{4}Ywsr0D~ zsf?*ism!S?sjR7NsqCqosa&btsXVE?seGyYsRF5jslurusiLW3sp6>;sgkKusnV&k zsdB0EsS2rzsYBsV1qWsb;C>sTQf0 zsaB~rskW(hsrIQ3sg9{msm`e`scxz6sUE4Gsa~nxsXnQ`seY;cse!3MssE)0r-r14 zriP`4r$(ekrADX5q{gPkrN*Zwq$Z{&rKY5&rlzH)r)H#Pre>vPrvj;9Dx8X>qN!La zo=T*E6qrh;;1rTVQ&n$l86%1l`)JLRO@l$Y{T zsnnd*ywv>Eg4Dv)qSWHllGM`Fveb&y%G9dV{~5XmZo8Ez0Km7lZQHhO+qT`l+BQ$@ zWMbpgwr$+ncHgY;FU(42&lZTuF(szPw3r?6+0a}6FVC_7ds!j5W5(=9J>;`8oL&|9=j2{8M_s`9lH~| z7rP&O5PKMV6nh+d5=)FdjXjIKh`o%xioK4#iM@@zi@lG1h<%KGj(v%JjeU!KkNt@K zjQxuJj{S}OizOgQkfca5Bsr1-Nr|LF(jaM(bVzz61CkNRgk(mtAX$;@NDd??k_*X= z40=ZIw75rE=X6T8`2%= zf%HOpBYlv*NI#@MG5{Hf3_=DYLy=*~aAX8B5*dYzM#dmxk#WcbWFj&NnT$+9rXtgj z>BtOZCNdkDgUm(dA^#!skp;*?WD&9$S%NG>mLn^WmB=b&HL?a-i>yP|BO8%T$Yx{< zvK85eY)5t=JCR+;9wde!2#R0`jt~fmPza4M2#4^9fQX2M07OOJ^jM|Yq*(Ou|nbPpOs5fnu+6h}#vLTQviS(HP0R6s>kLIJ9v zDypG6YM>@+p*HHEF6yB^8ln*zNB5%p(EaEE^dNc&J&YbfkDFmJ7>`<-zh|`LO(00jwZa7%PGm#fo9Yu@YEGtQ1xn zD}$BA%3&3-idZGAGFAnvidDm^V>PgvSS_qJRu`*>)yEoO4Y5X8W2_0*6l;bx$68>m zu+~@`tS#0KYmarnI%1u$&R7?$E7lF`f%U|CVZE_FSYNCk)*l;y4a5dvgR!C5Fl;zB z0vm~q!bW3bu(8-UY&!Pa8yu=UsmY$LV_+l+0&wqo0`?br@%7q%PQgT*ieLop1)F#;no z3ZpRw<1ii*FcFh5fXSGGshEc8n1NZCjX9W$d6PudvtH8|*Fi4ttM%z&>K1u+P{R>?`&S`+@z$eqq0{KiFUFAC`b8 z!IR?2@Z@+3JQbcAPlKn$)8Xmy40uL76P_8*f@j6E;o0$=crH9Qo(Ip1=fm^k1@MA+ zA-phN1TTsg$4lTP@ltqcybN9zFNc@ME8rFJN_b_wDqan*j@Q6z;jqoOTQ@k189B+ZQ#9QI5@iur{ydB;i?|^s2JL6sOu6Q@RJKh8DiTA>L<9+bH zct5;9J`f*-55|Y!L-AqwaC`(l5+8++#>e1e@p1SBd?G#xpNvnzr{dG_>G%wMCO!+F zjnBd7;{W0E@dfxod=b7FUxF{im*LCt75GYg6}}o@i?74i;~Vge_$GWaz6IZkZ^O6a zJMf+OE_@Fj!x0?CF&xJUoWv=d#u=Q&Ih@BuT*3h^;|i|g8m{98ZsHbh;|}iPJ|5s9 z9^r9(FTM}oj~~Df;)n3V_!0alehfc>pTtk$r|~oRS^OM+9>0KJ#4q8O@hkXM{5pOE zzlq<%Z{v6HyZAl)KK=lIh(E#~<4^EJ{2Bfne}TWmU*WIuH~3rp9sVBwfPch4;h*uZ z_&5AJ{saGs|H6OcfAGKfKRkg*LL?=U5y^>^L@FXRk%mZ1q$AQ38HkKTCL%MDg~&=| zBeD}YiCjc(A`g+5$VcQS3J?W}LPTMr2vL+MMieJX5~YaJL>ZziQI052R3Iu6m59nj z6{0Fpji^r4Bx(`0i8@4Gq8?G7Xh1Y18WD|&CPY)B8PS|*Nwgwb6K#mLL_4BA(Shhl zbRs$vU5KtkH=;YygXl%{Ci)P4iGDgT1Vl%OY*h*|8wi7#uoy2Zp4-q2}0wpj4CkTQhD1s&!f+aYDCj>$w z03j0!p%NOQ69!=t7GV<(;SwI<69EwtabhpAkJwKfAPy3Th{MDY;wW*9I8K}(P7ov`fOtqeA|4Y@h(zKk@r-y* zydYi@uZY*g8{#eTp7=m~Bt8+Ji7&)g;v4au_(A+6ei6TkKg3_+ACW*NC6kfK$rNNt zG8LJcOhcw6(~;@P3}i+!6PcOJLS`eglR3zoWG*r{nTO0v<|Ffy1;~PAA+j)8ge*!H zBTJAa$x>u#vJ6?4EJv0nE07h*N@QiS3R#t`Mph?lkTuELWF4|DS&ytwHXs|4jmXAi z6S67UjBHM}AX}2H$kt?AvK`r;>_B!TJCU8qE@W4-8`+)gLG~njk-f=2WM8r$Ie;8U z4k8DWL&%}zFmgCKf*eVXB1e;B$g$)&ay&VKoJ3A0r;t<0Y2U%-CAo@RO|Bu=lIzIz-Nrq%ej^s&^lt@6zq(Z8sM(U(NnxsYAq(i!-NBU$yhGd-F zOYS50lLyFy4tbZnN8TqNkPpd6BQVKn22?|;5!IM#LN%qDQO&6qR7Pz*b`cng_fz%*s2sM-%Mh&M%P$Q{P)M#o9HI^Dj zji)A16RAnmWNHdEm6}FPr)E&IsM*vUYA!X8`j47VEua=si>Sra5^5>6j9N~upjJ|= zsMXXOYAv;%+CXijHc^|YE!0+O8?~Ll)@;SA}ErgDTZPxj^ZhS z5-EuSluRj$}g;YevslC)bYCm;=I!GO&4pT>{qtr3#ICX+L zNu8ojQ)j5N)H&(`b&H+nTdPF^@o=}O@ zQ|dYOf_h24qFz&PsJGNR>OJ*=`bd4EK2u+)uhci{JN1M5N&TXJQ-7#`R05rZPD&@E zlhY~alyoXOHJye|OQ)mL(;4WDbS648orTUyXQQ*zIp~~pZaNR0m(EA$rwh;p=|Xg2 zx(HpAE=CupOVB0hQgms$3|*EkN0+B7&=u**bQQWPU5&0z*Pv_CwdmS(9l9=EkFHNQ zpc~SS=*Dytx+&d^ZcewLThguQHgsFM9o?SpKzF1&(VgiobXU3?-JR}1_oRE#z3D!5 zU%DUNpB_LDqzBQ1>7n#6dN@6T9!Za)N7G~IvGh24JUxM)NKc|C(^KfF^fY=pJ%gS} z&!T73bLhGBfAoBM0lko3L@%b7&`arM^m2Lyy^>x2355dMCY$-c9eJV>CjeG)ChzL6bB^(=D{hj_n|D=D>zv(~pU-}=Nz$9UkGRc?}OiCsdlbT7xq-D}E>6r{n zMkW)JnaRRrWwJ5ZnH)?`CKr>N$;0Gj@-g|D0!%@s5L1{b$`oUYGbNakOev-`Q-&$a zlw-;>6_|=lC8jb{g{jI^W2!SXn3_y2rZ!WDsms)3>N5?PhD>9o3DcBm#x!SIFfEx@ zOlzhM)0Sz+v}Zan9hpu{XQm6&mFdQGXL>L_nO;n9rVrDX>BsbE1~7w|!ORe5C^L*1 z&WvD2GNYK$%ot`YGmaV0OkgH5lbFfO6lN+jjhW8OU}iG2nAyx6W-c?2`Hz{;EMyij ziW*xJh*}!aMHZhx-EzDMC8?&9+!R%yqF}s;POpHMo zl))K-AsLFH8HQmQj^P=B5gCaAjLayE%4m$v7>vnSjLkTV%Xo~>1Wd?8Oq|)v>|^#b z2bn|6Vde;PlsU#6XHGCDnN!SZ<_vR|Imeu5E-)9FOUz~F3UigY#$0D^FgKZ7%x&fl zbCSW4<## zn4ioq<~Q?)`OExc64)ecQZ^ZzoK3-|WK*+g*tBdqHa(kx&B$hAGqYLPtZX(mJDY>e z$>w5nvw7IOY(6$WTYxRd7GevtMcATjF}65cf-T9GVoS4S*s^RnwgOv`t;AMltFTqs zYHW4323wP@#nxu)uyxsbY<;!?+mLO-GuwgcOd z?ZkFwyRco^?raaXC);QHkJBS_34q=C~!`R{M2zDepiXF|4VaKxL z*zxQHb|O28oy<;Qr?S)7>Ff-4COeCr&CX%xvh&#a>;iTnyNF%PE@79l%h=`Y3U(#C zie1gFVb`+j*!AoNb|brq-OO%bx3b&V?d%SAC%cQ?&F*1iEW)BJ#^Nl&k}Sp2EW@%a z&kC%_N-SVyR$*0EV|CVGP1a&<)?r=NV|_MYLpEaL>|S;syPrM49%K)(huI_SQT7;n zoISywWKXfD*)!}}_8fbjy}({%FR_=|tL!!QI(vh?$=+gbvv=6L>^=5A`+$AOK4KrU zPuN8EDf^6l&c0w@vai_J>>Kti`;L9jeqcYcpV-gr7xpXrjs4F4V1Kf|*x&3Q_AmR7 zP2iGnNx9@)3N9s=ic8I<;nH&Hxb$2GE+dzT%gklrvU1tD>|72mCzp%M&E?_pa{0LY zTmh~iSBNXj72%3<#kk^J39ckpiYv{P;mUI5xbj>Dt|C{7tISp5s&du1>Rb)3CRdxQ z!`0>LarL%w*Ax^dmP z9$ZhZ7uTEX!}aC*as9ag+(2#+H<%m34dsS$!?_XMNNyB2nj6E7<;HR2xe44vZZbE8 zo61e&rgJm6ncOUHHaCZx%gy8dLT3xqaMz?f`d?JH#F4j&Mi0W887>1b31<#hvEP zaA&!5+8rb@#*;td`3PKpPA3XXXUf;+4&rNPCgf(o6p1N<@53R`2u`Fz7SuS zFTxk)i}A(z5`0O%6knPz!W_>KG~ zelx#?-^y>}xAQyro%}9-H@}CE@d%Id7?1M=Px2H`^9;}O9MAJ2FY$nvd4*Sbjn{dD zH+hS@KALftnNBLv?asC8_QG9r;tm?E#wjM3i*WmLII(mP)H~&6cLIF z#f0KQ38AD=N+>Oq5y}eXgz`cKp`uVps4P?wstVPF>Ou{nrcg_$Ez}X}3iX8gLIa_p z&`4-3G!dE#&4lJc3!$aZN@y*#5!wpvg!V!Qp_9;A=pu9#x(VHd9zsu{m(W}2BlH#e z3H^lu!a!k=FjyEO3>Ah6!-WyTNMV#PS{NgY6~+nUg$cq$VUjRem?BISrU}!98Ny6q zmM~kGBg_@%3I7T6g$2SwVUe&{SRyPHmI=#+6~ankm9Sb^BditH3G0Oo!bV|}uvyq5 zY!$W%+l3v%PGOg@Ti7GS1VlguOuz*~AO%XG1x8>6PT&PW5Cus9f-ES4DrkZ(7=kHS zf-N|LD|mu01VSi8LR{D@>=X722ZV#dA>puaL^vuO6OIcfgpwCRP_~h&9DpVr{XGSXZnk))yOy4aG)c zW3h?YRBR?T7h8xe#a3c#v5nYPY$vuCJBS^{PGV=Vi`W(FCUzHlh&{z#VsEjJ*jMZ) z_7?|;1I0n&U~z~zR2(J_7e|OA#ZlsDaf~=t94C$!Cx{cpN#bO2ia1rACQcV;h%?1m z;%sq_I9Hq}{wK~C7l;eRMdD&{iMUi;CN39Oh%3ca;%ae?xK>;zt`|3m8^ulHW^s$S zRoo_S7k7v|#a-fVagP`i5fK$J5f=%O6e*Dw8Ict^krxF~6eSUevZ#ovsEN90h^APuwpa5D$un#KYnd@u+xAJT9IPPl~6+)8ZNNtawg5FJ2HY zikHO8;uZ0#cul-6-Vkq!x5V4x9r3PsPrNTa5Fd(<#K+k|L>+2I-O^nUW>h zk|Vj2C;3t!g;FHNrM=QVX}@$pIw&2I4ogR*qtY?yxO74~DV>r|OJ}6B(mCn8bV0f( zU6L+KSEQ@bHR-x^L%J#5l5R_Pq`T5R>Av(pdMG`T9!pQ8MCqyYOnNT8kX}l!q}S3L z>8xO24Gv(jV!s^iN6vNkCGN3?v6BKuVAbqy}j~ zT96K;2N^&{kO^c4SwL2h4P*y7Ku(Yg_UXTyu2L(VuPzV$TML;YXbswcwxAto4?2L3pcCi}x`3{r8|V&tfS#Zi=neXSzMvoI4+emNU=SD#hJc}9 z7#I#lfRSJnfD4Pj7%&!$1LMI2FcC}wlfe`)6-)!u!3;1H%mTB)955Hm1OI{fU;$VN z7J!l-Cz%h0hr(f zD8K*?2tWb~(0~Cf-~bNoB504KpIa2lL}&VqB`Jh%WZf=l2sxB{+%Yv4M#0d9g@;5N7e?t**Z zK6n5gf=A#ncmfi^Q}7Ht2QR=&@Cv*JZ@^pd4!j2+z(?>2dJSd~$xdfLu^6Bo~&8$VKI1P;t40Tv9G2mzK-OW#w{mdAWjIQLZFcmaE8B&SKGdUAcaf!t7TBsZ3u$W7&Da&x(b+){2Ox0c(;ZRK`yd#HolQSKyn zmb=JZCJW?JdkCw;CW94!3 zczJ?6QJy4EmZ!*5p=t7Td4@bwo+Zzg=g4#AdGdeqe0hPqP+lZ2mY2v&Pk3HhXaN+%ixrhH4jE#Hyv%J-oA@&oyy z{78N*KamsVr}8uTx%@(YDZi3m%Wvej@;mvx{6YRGf094TU*xayH~G8#L;flMl7Gv8 z| zR`Mu$m3&HmrGQdUDWnvJiYP^uVoGtPgi=x|rIc36C}ov$N_nM%QcmrYR_Z8qm3m5jrGe5=X{0oUnkY?`W=eCVh0;=KrLtSnKMD$A7R z$_izrvPxO4tWnk~>y-7%24$nNN!hGyQMM}El*LM2k-%3fukvR^r%98?Y| zhm|ABQRSF&TsfhfR8A?Ul{3m&<(zU}xu9HBEnnsQyaq1;q%DYun7%3bB2 za$k9%JX9VjkCi7%qViOEraV_(C@+;)%4_8f^j3MNyjMObAC*taXXT6XRr#iTSAHlz zm0!wl<&W}L`KKhPNz|ljGBvrHLQScrQd2`|)U;|kHNBca&8TKlGpkwDtZFtjyP8AI zspe91t9jJCYCbi;T0kwR7E%kVMbx5DF}1i_LM^G5QcJ64)Us+hwY*wEt*BN~E2~x1 zs%ka0x>`f6sn$|!t98`6YCW|+)Ie>hHc}g_P1L4pGqt(eLT#zGQd_HS)V69nwY}Ow z?WlHAJF8vPu4*^6yV?WlsrFKPt9{hIYCpBVIzSz$4pIlJL)4+_Fm<>(LLI4&Qb(&} z)UoO~b-X%3od``*C#zG`sp>R!x;jIhsm@Yot8>)3>OA#7b-ublU8pWn7pqIurRp+u zxe7=0s4LY~>S}e3x>jAMu2(mx8`VwfW_63YRo$j;S9hp8)m`dtb&ncT5fxQof?dT` zLM2s7rBy~{RZitqK^0X=1*)tns;X+Lt{SSTTB@x&kgIyCuLf$UMrvH$tL{_xs|VDB z>LK;8dPF^{9#fC2C)AVbDfP5^Mm-CiQ_rgx)QjpR^|E?Jy{cYQud6rIo9Zp~wt7ds ztKL)Zs}IzN>Lc|r^h8ZmpQ_K)=jsdfrTR*Jt-ev;s_)eI>Ie0s`bqt)eo?=w-_-Bw z5A`SXOZ~0>QU9v{)C4VwmQ+ioCD&4DDYaBuYAubHR!gU)*D`1swM<%OD2tX=%cf=5 za%efVTv~1|kCs=LbzHP>2bEwxr!YpspeR%@rV z*E(n&wN6@Rtqasu>!x+rdT2ehURrOhkJeY~r}ftcXaluD+F)&nHdGs?4cA6!a6zUv zN*k?>(Z*`ywDH;mZK5_wo2*UIrfSo)>Dml!rZ!8Pt6)RLnx)yAqq&-=`B0#RTBOCbz1lu) zzji=7s2$P{Ye%%B+A;08c0xO;ozhNgXP~p%Iqkf5LA$73(k^RPw5!@R?YeeDyQ$sM zZfke6yV^bNKJ-9)s6EmiYfrR9?Wy)md#=6EUTUwj*V-HHt@ciPuYJ%yYM-Fb+86Ds z_D%b){m_1DzqH@lAMLO9PfO5~=t=ctdU8F5o)Su>r`FTxY4vn^dOd@lQO~4j*0bnY z^=x`}J%^rC&!y+q^FVp^e0qMpfL>59q!-qU=tcEndU3skUQ#cmm)6VZW%Y7;d8mS3 zQLm&|)~o1M^=f)`y@p;>ucg=4>*#g$dU}1mf!+{mq&L=^=uP!zdUL&n-coO+x7OR} zZS{6~d%c6+QSYR8*1JGm^=^81y@%dY@1^(F`{;f3etLg>fId(kqz~4I=tK2k&~SZ( zK2jg0kJiWNWA$%=je0wdHR3)e0_nwP+z1k z)|co@^=0~UeTBXfTBWbn*XV2Yb^3aJgT7JUq;J-@=v(z|`gVPXzEj_&?}qm1F&)uS z9n*20&`F)rX`Rtoozr<;&_x}F7dp^oUC~ut({t&|ZC? zzF$9}AJh-&hxH@+QT>>HTtA_o)KBTB^)vce=$w9Dzo1{#FX@-{EBaOantolsq2JVR z>9_Se`d$4VbYFjdqW)BWra#wT=r8qG`fL4-{#Jhnz1KhJAN5cAXZ?%* zRsW`c*MI0g^ZBd?Lq$Zr%d3L1rs!bTCJC{)ZSZj>-e8l{ZVMj4~5QO+oD zR4^(Um5jHn+25t}rX~1N$K^u(08l1r!f*~4`0Swtt4AsyK-7pLjvJBgB4A<}s-w2G*h>Wt=Z0OZ+0*{nw`wfW*4)o+0E>3_Aq-wz0BTbAG5F7&+KmwFbA50%)#ambErAY z91e{zN1CI|(dHO)tU1mcZ%!~Lnv=}Q<`i?PISra_&M;@1v&`A%9CNNY&-~AvZ!Rzw znv2ZE&=Pa0xy)Q{t}s`ctIXBr8gs3=&RlP9FgHS*%+2N&bE~<{+-~kLcbdD*-R2%M zW+Emm(ljv>Hwlw8DU&uClQlV$Hw9BPB@>3Frfe#vYHFqq4~S`6rfoW=YkH<{22f~5 zX58Fs?lbqB2h4-!A@i_##5`&qGmk?j%#-FR^R#)!JZqja&zl#_i{>TsvU$b43SBd= zn>Wmx<}LHKdB?nK-ZSr;56p+=Bj~aD#7s1wn$OJV<_q(s`O184zA@jL@67km2lJ!( z$^2}7F~6GM%H1S^S^)JkS0w^CRstyETOD~*-bN(ZI4GFTa{ zOjc$qiSUIg+R&FSdmDkE=<+lo01+79>VXKH$)GB5bw@O$gp;A_9tBh6F zDrc3qDp(b*N>*j7idEIB235CeST(I$R&A?}RoAL#)wdd04Xs92W2lMM)M{omw^~>& ztyWfRtBuvxYG<{#IzSz*PF82Di`CWYW_7oESUs&?R&T41)feh#^|uCC1Fb>UU~7mq z)EZ_Dw?#X$_9M)oOv^H6rtu5A8Yn!#*+F|Xq zc3HcvJr*psw-5`pFblT`i?k?)H-Gzw@z3mty9)%>x^|4I%l1?E?5_>OV(xU zignexW?i>#SU0U(&~59Eb=SIQ-M1cC53NVmW9x~PXg!6VSzDQ0`eXgI{y_lZr8AD+O_Q3b{)H}UC*u$HLx4njqJvD6T7M1 z%x-SCuv^-#?AA~lyRF^MZf|$6JKCM>&UP2OtKH4+4)w5m+P&=Fb|1U1-OuiC53mQ? zgP_6o5PPUS%pPu!ut(aX?9uiZd#pVU8gEaqC)$(j$@Ua`sy)q~ZqKl1+Owe9_8fbz zJw}03_?O*n9`;YzC{%0pRNuZ=oGAFr{!b$0*a#A~K zoU~3lC%uya%IIWrGCNtEtWGv3yOYDo>Ev>9LwTIMPCh5UQ@|Lic>C|#+J9VJCPCci-)4*xyG;$g{ zO`N7qGp9M!!fENWa#}lWoVHFor@hm`>F9KVIy+sQu1+_nyVJwz>GX1XJAItKP(P=? zGr$?>3~~lLL!6<`FlV?k0vhRzaz;C2oUzV0XS_4PndnS%;M!4ViZj)j=1g~HI5VAD z&TMCnGuN31{pZYg7B~x?Mb2VpiL=yM<}7#M7+Pngv&vcRtZ~*l>zwt@24|xKhu1ip zoh{B*XPdL#+2QPTb~(G9Jq~Q(aS#V}Fb8)Chjb{1b{L0sU^3j{9l;SD$pMb+C@{#? z9NmEz6UTHc$95dYbv(y+0w;7LDDLca_Bs2V1I|I`kaO5M;v993LC2jF&PnH#bJ{uM zoORAQ=ba1CMdy-p*}39eb*?$rog2LzoO zyD6ZQZYnpmo5oG+rgPJ~8QhF+CMdI;#m(wwbF;fS+?;MMH@BMy%IoHH^ScGyf^H$V zuv^3}>K1d0LnYjjZYj64TgENxmUGLy72JwYCAYF$#jWaAbE~^G+?sAJw>DJAt?Slv z>$?ryhHfLbvD?IL>NbO#yDi+7ZY#I7+s19{wsYIN9iWbGC%3cP#qH{LbGy4e+@5YP zsJGk4?d$e)`?~|&f$ku8usZ}A>JD>njPj=9I(6Yfd( zlzZAeKd)d9>UUjdz*WDY?P4||2+r8u7b?>?N-3RVN_Yw5iec~p% zPu*wkbN7Y&(tYK=hTgbu-FNPL_k;V<{p5ajzd&EzZ|-;Zhx^n0<^Fd6xPRS$P=c4l zOX?-_l6xt!tJ3dl|fpUM4TIm&MBpW%IIoIlP=+E-$y2$II*G^YTLl zynVihCuzl29qHv{%L}>y`7$dlkHjUL~lqSH-L9Rr9KQHN2W$Ew8p$ z2deAU^XhvIyoO#Qud&y}YYH{8bwb#aL>$UURLmj-1UMH`!*Tw7Vb@RG= zJ)oXmFR!=P$Ls6$^ZI)Oyn)^zXs|cL8|n@7hI=Etk=`h8G&IH=>y7iqdlS5g-Xw3b zHwBvNP4lLEGrXDJEN`|q$D8ZTgZ}g8dkegU-Xd?Yx5QiO!9nlda&LvV(p%-N_SSf7 zy>;GtXoI)W+vIKbws>2;ZQgcohXA+dy*J)l@16JF``~?qK6#(LFWy)0oA=%O;r;Y}LBG8} z-e2#Zm*6Myllsa0rm{QQ0azo1_TD(n~Wi~7a<;(iIgq+iM}4VCfB`sMuceg(gxU&*iRSAnYf)%@yy z4Zo&e%dhR%@#{kM{Q7|>Cw$VUd{}tv zGd}BcKJN>@=u1BEVS>h2d=);m>AvBczUA9K{5AQm@A z&%X~n@E`h*{Kx(iKhb~cKl7hMFZ`GOEC03s#((R-^WXa)ppX71|Fi$a|LTA9zxzL+ zpZ+iZxBtig>;Ll;f+SGVAX$(+ND-tAQU$4lG(lP@U64M=5M&H81(|~^K~^YRkUhu| zgCarEpcqsgW5L65*1(ky; zLDisIP#vlf)C_6`wSziA-Jo7jA8HUZ3>pQEgC;@Kpjps7XaThhS_Q3xHbL8*gCW7tU|29b7!izwMg^mT zF~QhiTrfVE5KM$71(Sm*!PH<{Fg=(N%!FnIvx7Oo++betUobyd04)p_1&f0v!O~z^ zusm21tb|qttAjPc+F)I;5qancp1D3UI%Z2x52yMJ@g^?7<>vo2Va7( z!MET$^dtBg{0e>te}cckzaSw@2$O_K!(?IdFh!U$Ockbv(u8TlbYc21Lzpql6lR99 zgjvIEVfHXbm@~{3=7#cwdBc2R{;)t;Ff0@nhKhtm!(w6autZogECrPg%YK?4u^z8!(rj@ za6~u~8WoNX$An|UapCxILO2nc6iyDOgj2(5;q-7u2-g6Iv%=ZooN#V9FZ?f@4=o56 zhKs_*;gWD^xGY=_tq50!tHRacns9BnE?f_72sehC!p-59aBH|N+z#yscZR#d-Qk`v z7Q!az5Dl>q4~dWrsgMp~5qQXkT*!w)D27r9LYNv3l~4_}P!EmJ3}K)h+MyG=p%?mL z5Qbp{#lyYfzHondAUqfz3J*g^!lU7_@OXG4JQ<#XPKRg0v*Ee$e0U+e7+!)dhgZU@ z;kEF3cq6}D}qhwL?C`FVqN(H5k(nM*abW!>! zLzEH96lIRGL|LP3QT8ZDloQGo<&N@1d82$${-^*{Fe(%kj*3J@qhe9LeWQL+|7buoFd7sM zhK58#qhZnTXhbwJ8U>Ax#zbSIanbl_LNqa&1Wk^nL{p<_(e!9W1Q$t1v!dD2oM>(| zFZwT<4=somMvJ1w(UNFsv@BWnrLmb4q6{=h&D!>qRr8kXe+cW+8*tQ zc1F9R-4Psd5yc`TLL)4~BO)Rr*d7$o5fiZy7x9r0!Gg0$ia;bsN~A_w1k(YL5t)${ z*)XhfBY5eF{3w7&D2n3I-e_O6A36{nj1EPIqa)GL=ooZ7IuV_WPDQ7qGtpV-Ty#FV z5M7KeMVF&1(ADT#bUnHe-HdKUx1&4I-RNF)KY9>7j2=agp(jyd^fY=FJ&#^QFQHe_ z>*!7NHhLGmk3K*jqfgQ2=u7l9`WAhMendZ`U(xUAPxLqX2PMRl#FNI8#goTV#8X14 z;;G|l;%VdQ;_2fV;u)b#@yzio@vQM|@$B&&P|kR+c&ENF z>&F|!8^#+!jpI$?P2S&i4-E(n z3=Ikm4h;zng<+xLp%I~xp;4jHp)oKvG%hqgG$AxGG$}M0ri7-3riG@5W`t&jX2I;x zoY36RywLp6g3v-(6j~fw5?UHs7Fr%!0V_kRLaRe-LTf|oLhE5eXk%zoXme;wXln?u z=ArGO9ig3}U7_8fJ)ym@FSI{&AapQvD0Db<1dfJ|g^q_#guV-%3}KWobSiW@bSCse z=xhir%b}k_=R)U0KchwE*AT9tL%-uv=|bpY=+Drl5Dw>|%lLHuC-iUVztI0eDL*1a zhNuu7VnS?)13n~##E=w{LrO>mEu@ExkQuTOW1A?gzKi26hWXh<|78WT;3rbIKMIkX^J60L~VL>r!#9r7(>?aNo2Z=+(VK_n@C5{oti4(+k z1jdeu?}<~yY2pm=196r>^E>eqagI1o{7n2ppuL6ojrg7TgSbFkB>qe(2@-!1mx;ge zG5s%rzck{1M2H{=lAs8hV1Ok!f+qw*BqWdtg-{8N&EdP zjEEBnND^0wtHd?pI&lMT61Rxk#2w-;agVqU4~U1vBjPdfgm?b3jfq7nz&P zL*^y(k@=wjS&%G57AA|3MWGm3oGd|>BukN{$udxuEJv0nE07h*N>G`sLRKZKk=4l> zWKF0=)+Xzab;){UeP}>7BpZ>9$tGk|Xht?CTaYcuR%C0k4YVcOk?qM2WJj_SbSAry zUCC}_cd`fcBzuv)$v$LXvLD$W29N{ELF8a^2sspnk;BOmmR$tmPiavC|EoB=b*S>$YT4mp>c2lL4V0xtLr+E`?>}a&iT^l3YcuhBf3` zaviyz+(2%GP2^^B3%QltMs6n&8&B>ecagivJ>*_;AM7U&kO#>_<$n)gSC-KQdDWG43wqHQRS%$R7I*1RHmv>RjF!J zb*cu`q-s&MsXA0$svgv*8c+?XMpR>}3Dp#uQO&6qR7Y8ka0R!}RcRn%%~ z4Yd~5QR}G<)JAF(wHdZhTd8f-c4`N;6LwL%sXf$QY9F;94p0ZFL)2mF2z3;WQOBth z)OXZL3PbbMDe5$JhWddzOJO*N`iVM6ou_`LexcBQNBu_qPW?e$pe|Cl+@dZ~e^HmI zzo~yH9NDP<@WB+K2#Taqel*2UEX7egC4fjtluRjx&l|JYt(h>26dCV1-Gd?)LrTxbsrv352;7gW9kX@6rNGfsTb5s>J{}G z-cWC;chr081N9L;QJ<+V)K}^om5NS9r>4`;Y3X!ydO8DSq%+Z(=`3_sIvZrCbI>{I zTy$xb?JI^eP}>8q#MzV=_YhjXht`uThJ}(R?wPmL${^d(e3FD(2?#$ zcc#10UFmMno$f*RqinXV5cY7CoDuL(iq>!F+lFy^vl+FQ%8kQhFJ^oL)h%q_H%Z zUQMr|*V60g_4EeVNN=Jy(_3gP>!G*N+vy$jPI?!O$ai`Vy_eoc@23yY2jLKXm_9-u zrH|2wKB7<1-_a-O@99%;nm$ASK%b?5q%rnGpQF#yKhwX^ztU*oqkpIWpfAuD=|5>) zgwcP|m+8Ohe^SZ-^ndjK=nzfdHHC+YrfG&|X^!S;0YqA&Wm=(C&}f}DXp^>R8ywoD zJ=&+kbN~@LO2_Cpoq!~Lg}zE(qp#C9;3j>GzD?hu@6z|+KK+1xNI#+8ca>57Sv|yFm;)FOnqp;G-MhvjhQA)Q)tFCXId~VnO00|Xv4H++A-~! z4$zV5#B^r5FkP8$(4FbQ^kjN5y`c}&m+8m!X9h3>VGuKz8Nv)@hQV-V1T&Hu#f)ag zz*uG+GoG2iOk^g(WM&F8m6^s&hZ)RFW)?G>nZwM5dCYuf0ke=<1dEv^%u;3G259Pu#?%v>}K{bdl|%MGy9nX%t7W5 zgD6Vo2y>J<#vEr(Fo@`3PBPy!rDdgBkgh}jVb_zR{od(m{8SG4U7CRf}uyfgY?0j|syAT$!i`gaY zQg#_EXIHQ**;Oo-Z?kLIwd^`}J-dO$T1s{kyP4g>Ze_8|irvocV0W^+SS+ew_pp1} zee8br032iwv4`0s>`@jWeC%=d1p6I(l0|?Fdx|~Ho?(ArF`&%;$o|BhW6!fcvlvWZ ze`SATe`o(-(JaPZWdCF@v463+WMltk|6%`S|6@@+U_&gyk}Sp2EFLbFWjU5-1y%%! zm05*VSq*g7U`^IyZE#qZ^;n+`L%>GZC>vwrYyy((74|B7jlB*x*qiJv_BMM5?y~pT z`|JbuAv|IqvrpKk>@)T`ykK9luh`e@8+gmUW8bqM*pKju{mg!0zp~#T6_=Vz!=>fY zL3%C&myyfFW#+O#RxTTtoy)=Hgj`&1E)SQN%Lns~#ntBOaCM;`SD$OZHRKvWW3CC;lxxN{ zhZbB*t`*msYXfb$c3gX|1J{x31f97qTvx6e*ByFrJ-J?7Z>|sY<@#~`xdGfj7{m?c zhHyi@|hqrpM}rLXXCR&4n8NJi_gvHfxLV^K0jZ8F9?PB!h8|FC|?YU z^CkF_d?~&(l;O+r<@oY^1*phZ;w$r2_^MEiug=%tYx1?AHeZLY%h%)ULj%4c--vI_ zH-V;nGrl?Bf^P|}_||+IzAfJl+VdUwj(jJ+Gj!p*^4<9Ed=Kc!_u_l=efYl6kMGY9 z;0N-9U@$*~AIcBohrj-^GjeUzl>kbui#h0DtV z^1Jxm{2m@lLHK?Ae*OS|kVoV;f0#eQALWnnh@Iq5@Za$#dBkAxr})$S8U6%koJjbW}1YYDNUIv9%d5zb315DoHZQkKs@OYmO^8p`$C?DhFe1cEH75*xJjla&{ zfSdd+{x*MyzYF*H`}_m`Aw1$A^H2Dv{4;pYzu;f;ulU#ShJVYyl!Q`3X`zfzRwxJMg$hDNp^{J;st8quYC?6P2GkU43AKefLS3jQ)E62E z4WW_HSZE?N6`DbFp@q;=XeG3UHbPsWozPzB03C%+LT90i&=tA~-Gv@PPv|A|7WxQ% zg?`Xq7$6K31_^^RVH!*qW(YHd zS;B0XBg_@%3G-oruuxbeEEbl)Qel~}Tv#EjgjK?7VU4g>z#@5Jy|6*pC}4fCuvyq5 zY!$W%+hK>WQ`jZ!7WTkiVV|&HI3OULUpOQj7LEvrAQp}Z$AuHZcLDa1(9`w}m^xUAQOQ7aj-?g-7sMcp^L%o(a$4h44~%CA@|=!du~;@Lu=; zAB9iCXW@(R6}}0n#MELMNGqlj(~B9zjF3spEM^h2irFB$m_y7d=7QW}9x<<&Ps|Sm z#DZcWv9MSKii*X=;$jIXDV7pTi)F;JP);l_RuC(Sm7ua%MXV}TgX&@pv8Gr{tPOR< zx?(-CJ~R*;ijBm^ViRa8HWQnREyR}4N^C8*5!*sLvAx(q>?n4E&SDp_tJn>?i#^1i zVlS~b^bz}t{lxy_02n9^5(kSzV5m4u94?L!N5Uv^v^Yi_3**G`;skM`I0+_;Q^cv_ zG;uo25NC?B#Mv-MoGZ=~=Zgzqp}0s~EG~hi;xci$xI$bBtHjmf8gZ?-4%Uks#El|W zRg0U&E#g*jn}~Im;tp}AxJ$$mF>#N$SKKG=7Z1Qe@sM~}JOW3>W8!h~gotQP@uc{@ zcuG7iA|OiqK|Cw|C?ZHgJSUzPe-?icF(xSfCjKt|A)=9AyeR%DUP8MZ+MmR~(UkD7 zh^s+d?1V%Dg)fvTL|SCR^bbXo{7OKGIEkWNZ3WsovLCMmO&Man8=gX~fcDW{YRa!Yxn zyiz_XKNOG(N`<7tP(&&!6_bifC7`5KN-8atfwEFLsk~GHDoT~4%2E}nDpZrIOEsjL zP)n*U)sgB-^`O4gKx!y8g2qx4sj1XVY7Q-=mQpLJHMEi1O6{cfQU~ZLb&@(uU7)Mf zP3kW7ka|KdskhWe>I?m({?Y(xpfm^uOGBig(l8h}&2o24z%R%x4r^{LVhX{WSH+6{Z8z0y8uzjOc&N{6Jw(h)c+9g~hrC*V8j zr1ZUXN<#RYbVm9?Ix8WxM*2xQC!LpmmN29&{VM$?{VrjwNV8f;1x(+v_o6;@mwsZ&XO82Du(gS!XJ(39zDmdJFHQ_tFRHBYcuROJAg~(l_kY6q!7nBRhg`tRCR4yhLhZ1s0xs+TQ%E)Eqa&mdO0#uYM$(7|QP*tub zSC?x*O}Um_TdpJ5g?e&*xq;je8p)02CUR3~CO4N`$Svho&{}RIx0TyLd%1($QSJnt zYFSNS&?Lyqzv@&);#j6nnWlKhu^ zS^ir_;}zOx{*(VFd`G?u_vHKX1Nou+2p-E%;;$N-iZg^&N(HE>R8lG{ zRiLU;O{uQbfSO7zrM6NB>MHe=`bq<6s5DXPiG6^OtQP-ZH#l-V#xnXAlG=EDMIp|VI>3`>-y$}(j+tWZ`e ztCZESMp>(@Q`W-{IqD2jHM`NI9$=fuqVX z<+ySJzEe&r-z%r!v~ouIK{=}+piB8lIj5Xg5YD0eqWr4-reL^M`9ry&TvRaNg29Eq zl*SSgP=fpoof;^4KYgqAHrAgQ1v;rP$yo zuHq>^gq1*vC{c(haV4Q7;fiuqxu#r)8_G@PmU0{JD0h{6%6)jCJX9VjkKu{(RC%U6 zS6;wN<(2YUc>`~icglO^1AJ6IDW8=u@KyPyq*7Bu8a1t&PE8LP)QoB-H8W&Uv#QzD z?2tpvspe91LmoA+norFS1=NCSA+<0RQH!d@)Z$P=Evc4LORHs|tXfVjuU3GHY9+O@ zS_P`A)zs>04XCNsQfsSqpsrd^t*wX@mLhitIt8Yx)70te44A3TQfI4k)VVNEov$uX7s4WSvARTE3d_{x>I!uw ztWsC2Yt*%{PF=5VP&dLRb+fuf-3r^(?dlE{iTY$9iWQdXK6Sr(K*fqL^^kg4 zJ)&ZfiF!;uuAWd4x~`s7zgJJG2zgb{s6VJ@RfNN-KdI-`^D2U2)L+zJ)!$SMcB_A= z7u1U?hO5*|>R;+*6-~*@{lrg{r*t9R79>OHuxK2RU3 zkKnQTM1870gXiiC^`-g>UaN1^xA0DVuYOQJ!YB2!`bGT;-_%rEYAp?<)zWF{wG5C^ z%cN!2vOrcXo0eV60Xel?T5c^5S^`02GCGzq&3!>KvS)m)?8}=Ewxr! zYpo5m)!J$8p@Y^@>!fvtE?QTuo7NqAXg#%FT5ss1_0{@m{b7JMP#dHTh9TNeZJ0J3 zMrb3oQQBx2qm9+ZY2#snHc^|TO@=AjRBf6z9cE}VwOQJ1n4`_r=4ta`fwoXv1dFvL z+EQ&9EZ0_OE45XyT3e&7)z-m!ZG*N^+XS1nE!tLX8*JBhXgf75k=Ax=d$he8)^Te4 zwFBBg4GYS&!`cz;sD{NW+HviKhR}cQr1rgbN<+x7c1HU_JF6j_Q2R+cr=8ajOr-sy z{i^+@Ay`2BL%X0|)G*wqUDE#2E^BC#*Z$G|)&A4aFs6kxLL*bIKQ&roQcAuWr}3Jg z;b5Ujnye`)kDsP#x@Kr5SemUlnhT!hYhf*bh!)jiS{xEuQo90IwQJgS?FQV`ZfUo* zJ8)OKr`^{cz(ehk_E>uYPqk;-bL|DZ)Lv6P^=P*ty{SJ!JmO}&<0TdxCk^?G`Jy#X}T8|jVp zCeT!GrZ?AHKuf)q-WuBIZS{6~d+4Bd)H~^&p^M&C@1}Q$9(qr`m);xt=zaBm&|e>* z57Y<2V10-_R38Sz^%43=eH4t=$LM4AaWGz=pihKJ`ec2IJ{6|v)AbqpOqiw5*5~MR zVV*u;U!X69MfzfW2`tr@>C5#Muu@;8uh!SVT78|qUf%#4^-cO_eG6>Wx9QtqhrUza zrDH+1zDM7y@6)kdR6n2})DP*0;fQ`zKc-_*jDAA@PCp6X>!L|jAM~^Ok2*pU z^>g}pL}MWkN&i*J3n@oiUD9Rz{o)mkhcOr~7&s0zINf^%%tUgr3x|z*YU4 zejRS;H}zZkZMdV~)$i%|;eq~8f22Q#C;C(U89dit=r8qG@LGSPzt!Ktd;Np{QU3&= z^)LEY_@<{aQX6R?t&z@1Z)AXsMkXV(kp;3E*^KOv!^mmmGIB#6Bd?Lq$PWdKf<_^u zFcdM08pWWvQNk!`l!DSm8KbOG4$2!9jEYdnsBBa*szNoRx>3WZ3AK#cMjfLr)HCWE z4WOaX$Y^Xdfu=??qq)%nS{kj4*3iaiYqT@kLkFXy(aGoxU5u_qH={fBFnSujptsS- z=xg+Y{>A`fpfLyr8$*ntFw7Wkj4(#RC}XrS#uy9ZjPb?=sxi%&Zp?t0 z#w=sDF$d-v^Njhhz*uN3G8V%UW2v#sSPm?_}M@(m+>otcYaR^(l9O<7cu6H@m1q5sU*o@&)>$KD5C&6XBw7a8xFXJXZS`K0wZEXA!fvlgpq_R z##Q5*aUE_LH;r3x+qh%gHSWQEys%%;%HY;LxImS!unwb=&Rn(fT?(826zb}~Cd7qhF`&Fl_6%${a1 z=xz2f`m@=C9E=6n`_Lqu+Cg>Zh(#ECUdj7 z1-6>o%^ldC5j3`%SWTgs)!b@fwS-nyYpV^kwc1(jtq#!9>ST3>E>>5o zo7Ek9SUs&?(A(-`^|ksze`|m>5C&O;ts&M>7-kK(M!-mGlr`EK17odm)_9m;O|&Li zlVOTA)tUy=tr^x#YZlD5=2&xKo;BZEU@e42)?#Z3EVY(d%dHi#(pqJ$hBek&Yn`yUNWIs!+nV-{AqStqRTtdrLF zaLPJuVLgHMgLT&W(L#)|b6T&PakMPUwj6LR&+@G> z1Xjd~Ld=R=2`dR#tgF^FxNhCBZd$kCwspt43-_%1)&uJyJhC2JPvEKb%zAFUfS1-Q z>ovTw-dgYAz4gKRXnlgu))(t5e6v#7sqHk7)=p=qhYWT`JCmImve;SeY>?f~Vdu1S zL2f&bofq=i`RxKw&@N;bwu?YfyO>=ZO4udsQg&%5W0$qdL3z7^UD2)tmF+5aRj6iH zw`)L6yOv$st^;-LdUkziU^lcI*^Qxz-PCRd&FvO;OS=`cw%gckp`G2{?f@O_PIhOz z3v{)++1;Us-P7)6_l7=pU%Malw+Gk*?Ljcu9%2uLVfJu)1dOyt*`w_-FxDPtkB15N zM0=7w8K&4%?P)OGo?*{~S@vvujy)IV+4JoMu+Uy)FSeJ!QhS-b99GyX?N#<_SYxlX z*TH&wgS`@(%T2@gEm$x+lTEV_E8%PBkkk% z2^-7G?34EQ_9-}RpRs?i&%%%PPd1{J?eq4}_AfSq6YbyZ-)+QRA=>DojZhN%5&{G+ z+ZgV(|G}sohNbNP*&&;-(F$x+Xt7Iag<@^a#x<`k*rF}jC>Ek(DN9`EI?S!3#EB00U8eF$;*f;H4aNE9P--Ubjeft4Cv>(}z?I-Zm zer7+17xqj0mHist*l+E3@ZSDle}qr=XZwr&6~5W2oYauUN$aG8^iBpRqmv0TJ6W8p zkj=^N2l6`kocvJ0Dd-e}!cGyVs8bAzJ0+ZwP|7LolyS;JIj6i+0V+C` zoXSwesp?d7szVK@rc(=QJ9V78P|vCFG;kV1Bd4*`1e!X{oaWHNY3a0bT0uH>bPP1A02WoZisK>Fe}!`ojQcpfd;tJ42kIFw7b5jBrN6C}*@Y z2F5z$obfQhndnS%Cc_kGsxu9yJ2RY_Fw2?k%yH(zJZHYM02VrnoW-!jS?Vlvmct5X zrLziFJ8PV^u+CZUY;ZQhCTFv=1-3fdob9l~+3D_DdlzYbg!95E|vaxb}mxroqk|91az|4kV#b^qsvT#Vvic!F}#l!SGzjWL3YD`Z!4 zWmj=gYDB3=cX7ONP1j0!VdBwrT@QRW>;@2Vqi)QNL&8nESKz99&Akpc+?(z#xb5C? z@4ENkzWcy^2#?&y?h|Adt_1}`II@-ll_Aghj4lg4$jkuP)T{>U#~Kq1VW3>@|U= zUNf&bwD4Mbt)R8n#%l}hy!KuP=;(FwIztz)tJlrz4n4e{UN7kF_3`>bKd-+x00w%4 zyumQU8|n>%;ob;uq&Esidtg!xnF=w+*&?JG`B+%iHbk zfxX^7Z$BLH4tj^+uy@2e>K%jQ-U$!ON4=BY_ueTu?Va(k5Xn31{pev;hIh_8?;#-F z`^Ed!`^`hZsrQF>!Mg~5dY3!|0(qCczdgh*c>j9;c^F;wLLT8^w8x`7+Cyuz$9kN{ zduXTeL|i!I^3PLH2uC5((>=q(k;k(<+rx_!kEiGP5cUEu0#PsK#UbG(y(`{TxaM8= zZop0NmUkQOcz3;faNm32J%mTzWA6z(^`3dp;f43od*!``H{M(C9lZBGcpu@D_u2ab zU%hW$DnFH<+E3%Bg>-&;KLcd+Gx?bzi=Wlc2HE`_eoj9Z6Oi$QU}gkKU$`KA3bP}VQ!mxl^|MZXeM_N(|+{c2F%ui@8(T7GT64%GGQ`SqcJ z-_UOajr}HmQ)uQl_gg?qzm?w_+W2k#cF^AM;CJ*pL1({<-xa#~-TfZW)9>Z?hCY5@ zzaRAX2lxYFkU!WT0z>^_{%{!KkMu{uXn%}97RLGG{R#d=nB-6Pr@&Nynm-+8_%r=k zFx#Kw&xLvZe18Ef^cVSyVTr%gUk1zl75++C<*)YFz*>KuzaBRD8~shN+27)C^|!%x ze}}&lcKN&gJ+Rl`=kJFD{z3l`9QKd+SUT??^N;%{;5+}Mj|H3lDgU&827d6*`dH-S z|Ky+Z&%@9DFFvBW{onlGeMGJL7yOI)#uUhg(e(d^5v9kLSOYYAB9Ta@J%1bAm8>KA1_cmqP`!7z>oM*i1~3p z0ZIRge-*Cz*ZmuC)4%24_V2)5|DJyz9{3OaNATEx;y;CF{&W8Yy!2oBui=gV)_(`@ z{SW>}_~d`~zra`jo1ZG2Dx5l;CY%=1h0}*KK*n&UaAwF7&Kk}J*~2-)IU!d#cQ_B^ z4d)BzhXUb(;X+V2TqIl+iiL}ZOF+qRsc`9V87Lbr7cLJK!WF}npmMlMxGGc&R}a^K zn&DdE+E6E4H(U?uhZ}?&LZfiwa1&@6ZWeA1Ey69st)O+dO}H(z3%3tQlIo(?m@GsClBc6d&BF3bzh4=;d);YH!auq3=RybP9ySAs_^RY z8dw`%7hVq=!W+YzU~_m&cq?oRZx8Q)o#9>K-LNOTH@pw_hYy4g!lCft@DVr~J{HE} z_3(-CcVR604Syd#6+R7T!as!1!jIvf!sp<8_~-C1@N4+D@bB68%>wDEIcG{A-hw2Gm12W=Ee7*~p64dwKd zLZ*qsddktr3A*;rs9) z{4o3o9*3WVpTe{7^Y9CJ8GaRh4R6A4!|&jI_(S+3d08I%f2 zLz$p#P!7rm6@rRTDX1J&fvQ2ZpgPnDY6i8Sc2Fm%3-yBfK?7(QGzuC+lb~tP44MZm zf|k%KXdSeHwn4j~J#+{<2A!aD&;_~%-Gc7WBj_3Qg5E)&pfB_b`UeAGU@#~c3`2sU z!7vyej0i@;s9Cg7GjRm>5ig$-$IhDohKe2Qy%1Fe{i1bAq|SJeVIW2o}Pk zU~#YnmIlj$<**`H8LWcU!J1$#tP9o$8(?Fw2{s2?f~~ME*dFYFox!ePH|z=a2K!)t za3DAchl0bw5jYwg3y#By;JW}Tse|u>Qvp_-24{jFg0t{r@Kb>GNWuBw=K!lPf?tE* z0)(0ee*_nTi|}V~DL@Eda5+FkSMX2pZ-Bs);D13Vz;Jj#22{$JT)+fu$~Zv42SR|R z#y|?>Kmiq(k2)BE8CYQBu$1yi>IHtvgBk=uB#1&RhzAKs23LZsa4onFH-ekNEw~-r z3GTwZ;C}D`9tMws$M7V08a#vN!HeJ}yb4|iZ{Th4E_e?gf{(!`_#Au*zQVU4RU|c} ziKK;ek@S%akTH@ek{Pl@vPQB&_DGIMPRJF>9mxZEBl#lvp+KZyq!1L26p0jtVv*vJ z5>PTyDpDHCM9M+$<(g+$y znnapHvq1+61(B5k2vqVPkg3*yNk+Co?GCnc^CPpSjCc~7-)W|fL9+?rD39}-z zBXeMGWL{)GEQl&t!RpAG$XZwzSs&Q|8zY+{n_)|2Yh)X2 zkL-Y*kzJA9uqU!NvJdu04nz*Zp~&IL5jYw-7C8FMlQi$k;^G@atH?dH-dPc$p2D8F(O2SOc@o9 z&=Dqr@u&!g!2&dVM?|zlqCG95pj87`P9W`;iBchwv!!IPwIZMxI5U!;8qv$SZgq zc@udH?;`KvL*!%R6MT+*iF}1`kyO#tkS3Zonhw%OGek2&rfBA97RVaS2HB%IqB$W~ zGzS{v#_>qhHA{b++|LueFj9Bl$kqs^ktp+&T1v=y|Dwt=?McG33GA=)w8 z2|7o+M7u(_X!mFj=o#%5?G1gReW72pe{=v0j1Gzph9S|R(P1z=IwCp}Mny+Q$H3U= zI2a$D5S<8&ej ztD>u6O>`}+i>{AufQ`{j(ao?Wx;45DwnukFcfzjdZrBsu8{G%{qX(h~;ZXE&^avb{ z9*Z7_6VdOYC*k|(DL5TH6UCa@=-KFxQLG<~o{OH3V(C=$m*}t2-{AKsmIFjDL@!1W z-5k9X{VOFjGx~S*pOiSV=zr1wMG@l?B~s!NqEwWQVq7%JM!6_PVKAZ~M$yC@mC@LS z=Ax(;#nn2lG|iN$SglxXs1vIjs|WRC4WMDH zQLHgEi8YNigXXaov6j#();iV(+Q!;J`&frqN9Yvm9P0vIW8Grip+~GI^osS4^?|;z zezE>AAT}^I2nNT7#D>DK*l-vT8yOn~qhn)YV_{rud~5{sFfBGcHUnnH zX2oX1oY-8L7n>hj01IP_VvAu(Y-wy6ERU^#m9bT^)vzYEHntAd$2P<^!lu|}*b>_s z+XmZXJ7POwS8R7|5A2QYi|vO4v4e0Zb~ttfj>e9~j>C!Acd?W3ee4vRj-82NIdbf5 z?8g|EX2#CN&d0DSE%r+cE0JQq#eR=rrA6#Q>|zXohHn}l*7 zJdeGIy@Xe>*ReP7HuetQ$3Db9!l&5h*cbR3`xZ+TPZducPXlS=>Eh`jLp){ca>w(;^FqFOekc$x7%v2c<3-{{p;)|lyabetmx9vqGV!udE?z!f z0V>8TLFIUrcvYwtuO6=fHRHA7wV_VDF4T+Hk2ipZ@ka5+&?Mdzn#G&PTR_Wrt9WZ@ z6K@-D2kqk>pkusKyfbu(ca3+0?(rVbGu|uS8~Vii#`{74_<;C87!)52L*hf@!(e!P zM0_NSijRgd@v-r7Fg`vZJ`pCxC&#D2)c7=*9-k4P3A5s}<8xqcd>+h?FNiOMMe)V) zC9pKUEWR98#8<+q`0Dr?SQ}p#Uk@AN8(~v?b9@VIjc?a zf%w7rAvhdA0!QP=;>Y1c{JZ!`_&$CLPRGy0vDQ0&HvVHA>t5sM;^*P#_%CrR^NRl# z|2>XnEb$BRi|}XsQXH|?@yqeQRhLbF8kLbHw)5J4)o_GN-6R#4l;Z5Rg;vKwCe1MOMPl?a)CGj=!4N@gjLz-mTWI9Nn z%m5janUa|yOEPOR8)Q%BfSk!($=r}9nKzja@+S*G!DOLiVJMO;3dNGelO>>JvQ)A( zlu4F_a>??^3Q#dw2`VS6B&$NTWc6eXsF|zNCi^A(!+_*K7?d2G z90Eg=!;-^cL~ysN`V{%h+Gi*t2g>A|0$sMpWxhuIF z_9XYhzU2Po0XUdE1c#GHl1JfK@_6zDe3v{4-zQHcPs5qy4{$d5V-oAOljoA>;pgNp zNi0)L{+9eb`3GD`UW7lBmy%d2ki4Ay8~*t}MfbocxweF1c#mz{wr$(CZQHhO+qQjX z=FC0bNjm9H+vd!D)6ZY%bg$H^`YQQB@+i`wk}1$q`l6t~E85@M!6EryzQEc? zNERU_>yl+C$$A)))np?~$!1uRt;sgnlO4%UxRTxQBzu#6@FxclOb#W75lN0BmK;w` z;Air$jxTa$$wA603yO!Wyg<)?vM{LD+~*!e(p{whG&@UD$!0 z!Y*Mq_6U2iPuMRUz(L^<4hu(wqc|oU7f#@$a0;h|Gs0P%6IdEATo5h_mvC8NC8%&! zxF)dDO}HW4#4X{rz-kQPu0U|Ta9?;JJj5g6F`fudwMa7Ixj@K|@KSgs5MaPy`CEa} zOX0o1IFj&@mVB)t`insGpYT=qCeT8_MJ-oh!vBOn1j=H}!ap1$_!$7%#IvlPUI4Ei+PY&%!mA90kI$oiG@)_EGia5 zaj^tSilxNTC?l3dIkCK00Tsncs4P|ytD>4%9W}(7VlC7b>!7Y!PpppyVnZ|%8;ecQ zRBR?TM+>nfT8XX2HfSrhLwm7<*b$w?&gdd`6}zFk*aJPqUSeiOXA|{EG#VMF7PQ!F@hBy}xI{NRqF406F9r}4Lt+>aF^ZTN7Zdm?{z8(JLQ08LQfj1;(n{%&Udn)sQYI-g zvPfBxP0B9iKu#$aa!YxnyvQfzM**oI3Q2{fA}A^qLvg8uR1&46(kLU9mCB*KQ~?#G zN>XK1k*cDaR9&ipno=#)mg-1#QBSIm22w*bk{U}*&{S%M=28o(C0a?X(MD=3wL^QU z13F5bq|WFfbwxL+yVL_crC#VQ^^y9bpVS`%q=6VD4VH#rs5A`2r4iCdjFLuUj5Jmn zhw;(`Oq3=`lQBh_ifPhxX$EFWvoKqlgSpZ?X+9Q63$aLAEG@xOX&IJFE2NcJC9TF9 zX|1#l>!l6YC~cB9V~eyE+obK-A?=iQVYjpgd!>ESejJbv;*fM$I)bCpF&vjpNGEYh zI*l{ZS)7y3OBZlax`fNp73nIjN!M{hx+&elZRrl~O80PIdLTW-Bk3`o5I#y&r1V@O zd`)^Oy^;u)k=|$#4AML4y~LO(qk4==NuMPK1Zc|ssx=%+-z6Goq#x4%v}-l#Pp$kb z{Y_bdmphKf94;h5`!Y+Cq?2SQk{$+0m5eY+W>_SvWP@FDz$v-lmOPRdKFN=O6qG^; zOA$n+m=s4s`iWmsk}gS?LYGpP3aNEzkXDyYmmV2(8IehsS(gP_b=i^-AMNv#w93^xmQA$@@R|aKuR~6NC)pa#c zQ&$VMb#-)gQBPMN4Rj54jnG)v1Wk3#&|KF-*AlICtKvvjjDM>khD5A$^kuu!*1w-`%wOR-G194mAyb*r#iw+3r<>vZd} zLAMc`bepk7w^g?d+jTpzQ@2aE8+&wnu}`-j2XqH@hj3VT1V?qpbjNW*cM_*`r*TGi zR(B5Pbr*0^cS*-0dfgQrtEP3=bgYhLJ?Kr{E!@`K!Ce;Qu;_$!6)YUkJ<<{Lt$U(- zs(Xg#T9_b#Z?AMjCF$Pi2o)jZfq{DlpFipt{$ub;8+rLn_f=>nJjg5OLw>mc3d)7#!YCpaMKQU!TmmKKQYbB#L0P$+TpktVil`)4MisfLTn*Lb z8mKAPl53-mTo?7^`e-0GlpCS3+yqVKW@s+AkXxda+!}4k%wZKJX{`uk@6^vmd9YMJWd{u3Gzftk|$$| zJXM~C>GBNBlxNAaF-M+@dGdTLkQd5}uvlJ#rSdW?msiLuu}WTzHS$`llh?}|uuu5BAFYuwOoagYqHyFpkJaaZEmr6Y@#<6i&-$a8^Et^YR7x zA}-07aYbflB^vT0`LPx_E?F1FiCBnK3T;H<@7t`J4P*rp=K?GObPL51C74`7ilzt#~Z| zD^tecC6ME@Eb=qWms*x(1$x;4RW`~dm}LvBvQ4(bAv@uc-SEg>*$2NIKu``LEJx%h zVsab_`6qtKNlFT&R8k?ek_Ks&bV_<;P%okgG7jUF37Du%QYK@HG8NO5>6oF+RAyne zG6!>&d6=&(P!?j5vKUL0rC6pcS5{!9vI?t}HCU^xQ`TdHvJsn<&Df%BRkmTfvI9Gn zUD&PcQTAe=vL6SOgE*ud#u4SHatz0n6F8}y!fEAK~dKl%A@>qF-r^+)tS6<+y@=77tj=(WOppfHS{%6OJ5sx^mX<1P+#8w4fT!C zSl>k76wUO_(L&!6t@N$YM&DN74(;_F&{5wBo%LPxUC~Y79X<3t(M#VOee`|x{m@@O z00Z@dFjzkXL-oV-!!bfX5~K8^F-AXDKMv#d6EIOf36u3xFjYTIKOHmlGcij)8*}t? z_46=azW@vMi?CR~1WWbH^vkhAzY?qTtFcDER=*DG^&7BJzX_Z5Td-BXO}`yG^gFRj zzZ-k>d$CWyUw;4x^@ng+e*{PM$MnZ>LVpsc^rvw~e-`KT=k*tGQGW@S^;d9Je+}35 zH}ou`*5A^zJXU{4&w@w&J^g+C13c6g*XSSXpWvw$#IAp?f1!VgS9)R_^>6fA#MwJN z;XnEh1Yr zxaKgz&$eFC>-7exdLvAFGc0;5Y zg(O1?q%@>LYC{^NHKa47M+QSiWHMw%7DHBKGh{d9Ku$w0B@|MKeQlv@oeTqGAuSM!BWFAEH|vcO2aCwHmotM#X7@!Y%pxZCc|cIF>J*)!*;_C z>@@7cZo?kzHSEKF!vVuV95Nin5yMd&GaSbW!%4#_oHm@nS;IM;H(bC)!zIIITrph5 zHN$n>FxHLkyfP3>YItKH!q4!| z@LmfUGJG@;wL#1Q3m3=B~kz8M$|GW<@1JMF`UKMXXx(LO{o2G`nL4syMcY@n=1 zF@)DggHAh^@bj&G#SI1(MuW*D3I#sAfWDH4Cz;+0^XFq2@#`H8=98d67@ej{<5z zwGaxcMNm{NhT>`olvGQprBOyLi*jmtR8T9Tl3E#6)T(MVR99=DrdkWN)jFuF)H>8k7O9J|L|uwy>T;}5S7MdAT3v&+>N>1fH(;Z>37gd|*s5+*w_}I86T8&i z*rV>nK6O71s0YO1wlN|cuRQHzsN zKdXc&sK2R<3aj5#2H@1+)gM}mJk7_nbgF-;v>d7bs9b@o|EXNTP`nmZiX^;Ja`;vi zm7j6GVxRE;67Ch$}Jg(PDNq%@{7 zrbZfLTBI|kM+Rd?WHM$(7GqXpHe@&EKu%*WF=KI* zFqT9qV`-EzmNk|`d1D1sG*&`oV--|2Rzr1T4b(K&GS)^NV_noU)<*+lLo_lrMiXOG zG&43gwm?f`E3`JYL0e-xv^RD@M`I^+Hg+*~MK@!2^f2~BFJo`?G4@42V}A@V4m1wJ zVB-)BH4ejY;|Pp2j>2f;7~@!sGmggu<3vm{PR11DR7^8Y#|-04%ree4&cR&cJj^#P zz(V69EH*B|QsXi#H?A|I^%k5FmA*q<7R9zZZ&ShcH<80H15J~;~wla z?!$iL0UR_QG9Jbe<53(l9>)peNt`mC#u?*ToHL#`Ucg1;C0sUM!ByimTsPjpP2(-x zHr~Nq<2@s5VvP@sENe7AGCsx=<5N5{KF13qVfn^a#7w`zTO$FPgcTARXZ&a+ILY|g zNL&aJ4cburH*L_<_`C6kkzo+yA6o0O@h`2l&iIek>S6p(yJ|EFMiG)xXXG`L!?<=} zF&O!x8;wR2%ti~WMjPx#2b@Ni(G8E$3!l-CfH8=WF^q^YikLBugz=~G7m`dVkkXV2 zsZD8+)|3wEO&O5UlnI$lSxi}x&6FKEOgWLulpA?Wd6CbQ9|cSWQOH!-R0Ks$#ZcT- z0wqnQP})=mWliN!-c$hgh)lAh97WTd~cw9Xm`rvCFg@drW(= z&$J&0Ob2nubl7wRM@`3Y+;jpbO{Z|$bOvWl=WyP10T)e|aM^UlbQRZ3*Kxyi6Sqva zamREQ_e}Tk!1NH0Oe_R6JuyASGt+a^3%oSF!fVqTyfwYUd(#JeG=0KnEgHx4n~4Aj z(>K$1ZFH33y#L`36OH<`KGR4_OCF6)w1#l8&XuHgl_Qxb6jD^-)zhRm8MGgDzU;~5gu&(^7-}Ad z;pPz-X&!~q<}ny+9*6Pf37BY}gvsV9m};Je>E;>cnV4msjXCDIm}j1k1?GiVWL}IV z=A~F>UXB&!l~`q7jWy=A=5<(a-hhqfP1tPSg01Fl*lyl|o#tKGZQg^u=6%?2K7fPf zLpW?cVm^vv=HobFK8aK2(>P;3i*x4lxM04BOXkbCV!n!N=Igj&zKL7r+qh%CYrcp3 z<_CCaeuT%`+CuYFZ7rDjx%mZNnqT3y`3>Hh-{HL$32Odm{)ErwFJ=OG%wNsKOPIf# z8J0Hx&_?|jqGB|~{Fl}^ZT?4VNlPh|wv<6x zOF5LcR6s>bB~-RlK~+mNRJYVXO-n7*w$wpgOFh)LG(ba3BTHj6u{1?9OLMfav_vaQ zYqYVnMLSD-bg*|7Ry#_vuwu>%TDaF?8Y9;UhK2%#{tVh9I_n7 z5zA2=vmCdaz)8y~oVJ|7S<5+`w_LzQ%Oza4T)|b#HC(scz)j07+_v1oUCTY(w>-c@ z%OgCtJi$}TGd#Dvz)Q<3ytcft5HfFhXCZ*u^1(tFspXS}AU?|%3$as{uNEy-<-3K5 z0Lu>x1Fn`ov~euUUlzs%EdN+&U$y*ap?%6CXxH@?$-;#YrE{&ssJHNn%Hdr*)R?p{ zy~Sd&!e+668LHPg(PbVq_n0&YHJ##wWdRQ zYX)SrWfYe|%{mPQ$C zS(LMuM+Iv|RI*k^6>C*gvsOn9YfaR$)mUra4#80CFbubj zz)0&TjJA%!SnD{9w@$!B>m*FJPQg^`G)%Y7z)b5b%(l+KTmn?+F2Pdk zGAy^Qz)I^XthTPfTI)Kjw{E~j>n3crZoyXTHf*=meMr z9>G!TF&wv^z)9;V>uH>^p2a!qd0enw#3k!xT(MroHS2ZUu-?Qi>uubz-o-uZeLS$T zc-{KQ%EDvo6Dx~Ft#pH>pLsa+zi4r5 z1Y8j&Wc{v1V-P;TXt(tbD}#uPb}=Z!$N`Ppw7$|fNXr$CDppCm2(`*qO6wFKwc?Cw zvL)G4*is^u zEj7~E(juKLJu=ubB9kpMve>dBn=LzX*m5G5EjRMm@*e}j|zO4Zo+8Uv;tqGdi znxVO^1zOr#p|!0I+S=Noy{!W}+B%`LtqZ!^x}m$R2YT9ip|`CM`r7)Tzij{p+6G~; zZ3u?ihGDpE1V-9MVYF=w#@fbVylny|+9qMLZ3?E^reV5m24>o3VYY1!=Gx|AzHI>( z+7@B4Z3&jzmSMSV1y^*Y=k-Bxd_Z8(Ogar!~#m1g%Mn#unNDY_fK(WYgOyOxjfK zMV7<8cG$7l`10FqHai?PCtNl+JT@gCn>{;n*mEM6JvZ{$^CF);KMJ6ry$}l9i=e2z7>e6V zprpMNO54kzti2q{+bf`=y%H+htDvgA8milCpr*YRYTN6euDu@W+Z&*vy%8GQo1m$^ z8JgQ$prySPTHD*8t-T%E+dH76y%RdyyP&JR8@k(jpr^eTdfWS;ue~4o+XrBveGmrQ zhhV6E7>3(NU?fJ_M`MhAEXLW#V}gAmCfO%rihU}k*{5TMeI{nvXJd|iF6PZrX3*w*3z7 z+VA1M{Q(}@AK|h637*=Y;ko?57XNSmX#WH)g7}M_xJ~<4J7IG6 z?{=b*>_6-Td)WWb#?bA5*%^~$;Eb^y1|Mh}r{R`1MZ2W6!PsRx7pr!?R+y(~Nh!x} zvUAwy*rWaY+w9uMVRyo1cf(`%!e{p*U=JdMuswpPJ%+eFfuHtYNOGh=N=GWBcBDaC zM>?c;WI#qoCS-PGK~_gLWOw91PDd`}cH}``M?U0t6hJ{oArwXtM^O}W6h{e1NtAMw zMj1z0lyj6v1xH0xa#ThYM^#jFR7VX*P1JJKMjc08)N|BF14lzNax_L0M^iL&G)D`x zbhJWiM;o+tv_pGG2Xu6FLT5)8baiw?cSjHObo4@RM<4Wc^h1Bg01R{t!eGY`40Q~{ zaK{LY#3;vTjB$*`ILCNQa7@G`$7D=#OvNwAMckIAU$1d!4?7?2gKJ0fKz(L0$ z9CjSRQ5yOQE#049Ysop}eyK zDmp8nva<@RI;)|&vj%E9YoRvkIP0RGvpyO)8={f3F`77=qM5TfS~y#xm9sV4INPF~ zvpqUEJE9XhJG-E(vm3fQd!VPY7kWGUps%wZ`a1_;pmPugJBMJXa~OtWgmWZDIY(oR zb1cR=$76zXA|^Q}V~TSsra7l$hI1xnIcH-I<~rwLzHkOLVv}<-wm7$9n{zvMICo-~b2s)l_hO%OKMpt#;*j$&jyR9v7>+wn z;H2{uPCL)wtn(bsJ1^j(^AavQui&cl8m>EU;3jT4Z{v>hF77$+2$i_ zc6#7-`rvm45OjtRc193&#t=us`4hjKNvj85Dawe^ zOmI!aB-dn2aZSZE*L2LlOxG;TcFnUEXNAhO006N#v0dJ ztaGi$2G>Sxa&5*I*H&!9cGnKGpYYlB1;4qz;+yL`es}%A|6G6IPuE}gTN}l9G1AOXC?j_z zcOUe1_d|d801R{w!eI9h40R8~aEx$|#3=V@jB$^}IQMu=a8JY}_hd}LRQEJYchA5~ z_bkkI&%s>xJj{15z(Oo?FUAu0QY>>X#|rmKta7i$8uwbP!+Q4yY;efI-AbU(sl_Y*vIKf`nP3%qo{0_%lY`^qX#_d7RB>f9gP zENpUraW6e z(>(3PS{tU`O&bpl4%)>pS7C0mn<6QtBJFkA?&g@}cDi{W+-|oAUijR81l&P{++jrA zQN-MFB=FPy3rU_7Na;z1)Sfg*>q&?7o(#x{OrFfh;>n6^p6tls$%$N^+{lBxo_xsf zDS(2WLMZGhf});cD2@`Ik|^aVjWV9HDCa4U3Z9Cngvy>OsOqVP>Yf^?>8XXpk>X@j<&c4+VEfR3I{=!`C&uIT3Jjvk(#=;i5+ zKAyhlhyI=c80Z;KTUN7~vU-QJ&El;~9%_p7EIAnTSc4?3se8o@townSq&} zS(xpagSnXJnU4jYg;?ZSj3u6>Sms%d6T^jopfzo|8D`IgK-(vpDBDj|;fyxrED}E4b>p zhU=aixaqlt+qmPoi+i5?c;I=6N1n%c;(3Z^cU$fYp|=qlqlvdEnt7X}g|{VId0V3m+Iriey|)88dOM-Bw+p(Wo3}f9czdFkw>SED z`=THEdk0{kcMt}9hhV687=~kncO*u6M`Mh4EXH}qV}f@gCSkI73Z{CeVY+t)W_o8~ zHs*NeVxD(C7I+t8k#{kcV5xT*mU~xVrFRupd)Hts)_K=sgLfk~c{gK=cPqAGyLSh6 zdUs*BcMtYrpLahFcn{)`_b`rlkK!1Pdr#n`_Y_Wh&)}^09M0o{_aZKNFXM{$Dz162 z;|6YeZ{fE04(@vI;lB3)9^#SrF`jsz;+gk3UU*;P6<&Ma;H|bs*Zbc40Uy1ez;Y{= z3bDw=`_;>u0`GS(q1)acULs??gmDsaNNAk*Z?6_+^skpl4KI-d40AIA%g`VrR$f_a z+4t(bG<|CAaI^{0IN>#Wxl-d|K`XA>y}VR=d71FKv_A_T4DI9d`VsI35klA-LDU;V z+?&8p{PHIGl6)zADUr&T8fkoKkq+s78IaMJ37LIakkywB*^$GS6S;i3k;j)8`HuE)mIJGQNvdgwS2Wv$5$8i zeD%=)4SkK!*w+M2ea+AuEqpD}%GVlgd~MOr*B%|v(box`eO=Ji*A3m#!`BnNe7(`f z*BAYK{V@OoeSM*9uqLpHwlw{Q!v#x4bw5hHxsjb zvoXgv7xR4cu>cEwi?G+p)v9 z6T5u7vB$R;`+WOx00(`CaM*VQM}5a|94CAyamsfZXMAUI4(ELraM5=Omwi`o)prfo zal>~Lw|uv8$9EU^aNqX;4}FjD*!Kia@yz!eFMKcY%J&*?d@MEhy~BIo2YmE>!e`$X z{D!Z-Z}{%}9Y1^oD*K44_5JB1P|^3dkDxE&k_en3u7jWhZ9Lp3Y2$~CYBA74%fHqV zO|z)as5KSQWWjZ_&+6mi!AIehl9TqT?ecMy@_Bqb96s&i_XQC2g%I{d5Jk)vN5c0L zzkEslB!3EjN~A(+e;TCqr$c&w24qAge`aLyXGJ!DcH}@#e=g+q=Rsb7KIBIMe?b)T z7e*0(Q4~XQe+iWImqKZO8I<*xLwQv2S41U$WmNH3MKx6S*Fa5wE!6hcL0#1I*GB_? zLp1U?MiVskH$!uO3$*mNLTj|~w?#XDdvx%3L??9icR^QwH+1*+Ku`4Y_eLLoU-a|$ z#{dlU55i#o5DfJX!*Go7kHjedXpHfX#W;-jPryX~Buw^C!BkB1Psa@ZOw97n#vIJ` z&%=EG0xa||!eT7(FU2zda;)&L#44=zufbaXI;{6^z(#EHZ^jn?R&4Wc#}4fD@4{~X z9_;n+!+sp_AH*U5VI1)v#W5WBpTJ4~DV+A7!C9R1pT`CNMO^Y<#uZ%kU&D3(4czqK z!fo8~-^D%ueLV0##3MZRKfzP~Gd%adz)QUHzs4K?TfFnX#|M1$f5K<~7yPEJUDB4^ z_`myE*+3}17CHWhpMYFHk)s4S5{2jgR|{nFCu>09s-YdL`1{}i@%#M&1pOg|5%EV6^T(0!|HLmO z1yUeoAQe&v(jYC;1=1r!AR{sbG9wGJ2C^Z0AO~_HS0Fd?1o9$ZAU_JAV4x5R2a2F* zpcsmyM4%)}1xlk#pe)Lve4qj<1}dR4ssyT{TA(^=1ZtudY6t3|ZlE6O2O6Lu8U-4o zNuVj31)8G;S_WF7b)XH}qFtapIs`hRQ=l`tplhHTx(9loXP_5)qfekO`UU!9KwuyS zVQ^pwh6aXVI7S3UVpL!>#stP<9L5JGU}9hrCI_ZqDy9XdV@6;mW?^<<4(0~tVSZo% z7GhCgF_r|DVp(80R$ygd6;=n!_F`XP zKMvqv;1CW6j^JqE7>?sa;3Q53PU8&D2F~Gp-~uiNF5xn+1g_#*;5u#uZsHbh2kzi* z;2!SdLEs@C1s>x`;3=NrdEfulPBL@Ma)|yJIp4PTR z^G3iH;QEe>3$0-5f;+&=N5HH7iSUqUpCCekFd~5{Vu%M4_!;siRzvk*4b()fU~SY1)mxtGwS4YosjbO?4tr(kDv33f#{bPx7G&tNa~MxS6`^b7XKfZ#w3!rQ!+b0VF2thXVl2VZ;4&-^ zuE0vH3a-YQ;99H;uEz#!3~s{a;1+Dfw%~T`2=2tL;BM@}-rzp$4<5il910%Bk>F7r z!|~t=oD81A>EIch#kt^lTnJvoC0q_(!PVe3T*r;zP239J#+~3@+{69g13U~q!ecxM zKE<=(bG*RI;48cizQNnzJG{pSZPj@2Q}8ps;J4scd<%ZZ@Awh?AN~mbiNEl7kYH8f zDGB%sCI<K zJ(L4EL%EO}c|v)SFO(kzP%u;og+oP9G*k@5Q6f|lr9!1q24zF#P(D-v6;UZv8C616 zQ4Q5YHBd8D3$;UaP#5(=_0b^I5RK3{)C5gK&CnbzLM_oM)EaHjHq;L7Lmki&okE?_ zCDaw&&^^=xJwv_FJJbh#(J#~=1408a2!lgIFf=p_!!aT>5~D(+F$QBp<1jun0TVGP zG#OJuQ!x$GLo+ZlGz+ssb1)b4Li4d8v=EE1IJ5*yL(8xnD?%%=DzqAFur{;~>q8r` z5t~Aru_d$>+ps;f13N>zup4_qd$BLH9|uAQaR`S)M{qQB499UIbP}gRr*Q^nL+5Zl zbO9G}DRdcELRWDO*F!gOGjt2LaVK;a_d@sa01rct@Hq4YPw_1D94|sI@d~d)Z}2ws z4)5_H^bwyzpYa91g}&lj=sSMLkI?_{N9a%d75W?h;9o5aGnA}_u!RV(BJM{AIi!Gr zWX3gB7(*t|08TS3ErB7M))Jy!hjR_+f;&V>OM7wWTnvQMY?c$WC&+OCS(q0LDq0KWJiv0PUH&bMjqr1=R^K*0Te`` zaA6b)7ez4?50^m6a4D2VnQ&Q@3ztU)R18-_I*!}0J5oD84BX`Bh4#kuf#T)@TfC0q_)!Bt!fU&oE`P29rm z@EzR6z3_cJ2tULlJPtp>)9^Dq$BXbwyb8a@8@vs_!~5_De8i_PYuv+Mw3Wr-ulN@J zj^D#SzWzbR;8Z(%8{#R(C-5>~VU{;(m;=%+Ri$B+;s z6Jd+iLaVhAhV5Y*NWxC-qENdSp$JT=E9}!=RkULme=j^R;cz&DC}QC_68IVZg``Ld zq(rJnYNUyzMLML9WI)D9CS*pINLFNvWJeCJ4t zGc=F1K+8xgv__jqTeOR`M+bC_bV6rziF8G`NO$x=&qy!yj`Tra^o#VzfXF}$!r;gd z42=xKaEyqI#3+o8jKSE*IE=@H$V5zvOvV&UjZDMz$PCQHtjKK4!Q99^%#SR z3Y^Ga5p6}oKlnHDABe0a?30K@Lgk3yiV#;ss0*9<4mAmB1qY2rn-j$+W*2?XihOqKF|LN#JMX7m}h$(G<~?NEJb&4HZA70r!2$Q#Xv{Lum^h(giAC=xA-VkjOhfs!Z{ zEsZkKvM7i0(F&*-t%S;`60M4As2;6>n$cRQjXKe~s28n|251;sNE77aChU?KAxQScQ+qe_Gi+i{qeSnA2M|g}U z(WiKZ=g}8<8GVJ3>n-|!XRv_-qo-?ep-QI_$s=8WZ0(Z8as zHi`a&f203F3wsu{Xwj$?CA?0H`HCu9TuhWWji{=PUo(=*fL@egB}O*1wso3zqmC$T zS~R9;7x7#LOu~>1GK*?Asl#Z1_ zS(J;FM+H=jRYK)h6;wsFSasAu%~&nej@3b3)Qi=27Y>aKf=GYc&#kSaX?7+_0F6@r&!Cvf(?Z*Kej2*(^*byAXvDk5( zz{%JtoQ|ErS)7ZV#|2!BUBczq671E13Zj9!ecy%J;k%w zbG*RI*ekroo7h{ti@nDOe2jgSLWKfo%>*dMV!@fZG%{eypF|AC@i%yj zck%c5fRFJ{_>3>{-|#j54d3y5{0CSD8~-EzC;r0U@qh5IwkjjessTdUiMH0lfa5wX zWHL?|A8}=bImJ~i{6h;wh@0aUXd{ERI3pu*+U02y)*2(VMlesDi+!#xwW}G5&hemD z9ME2N;*mIiKiUHnkH-`E8UKZ(L{cI}A|+BGbs`PYB3&XqG9)r06EY{VAS<#ZvLgp_ zCUPNnA`kK+Um`yWpkSg93ZqD(D2kzYq6A7NN})8$B+8;3$|owIA}S>+qe`MGs-b$K z25O>KqBiQFZlWIQqd}q}8YLQ|37RIFp*dP4TA~$NC)%KGq8-|!L!u)(p>v`Ox}sa6 zJ9?mJq8EB6`k*iRCHi9k1||k!FoqoAVl2jCd}0D7Vp3u^U?yfIW@8TKCgx#279PU4mV1xdOWiyVi0Yb^~^^XVj~>DYuizsmE$>?3 z&BZP7R!Vr=*x&6j@CJaEFMnU${9TZ4`Tlpo;P1l^{3i~+4gI}{T3T8{uy9LMC@Rzn z79EO##ll*L+Q8yqZA0;}cChxL4zL7R$51C&Vkilg3`+^6!qQ;rp$u3iEGv`^%Yo&F z@?iO}f>0r>C{zq9ft7~JVCAriP$jGiRvkjXYGAdYI#@ldA=C&%hEOmx3=_h_a4>v` z03*UkAu@~tqlRcOI*buw!dM|Tj059_crZRp5E8;fFmXr%lfq;nIZOdlhEy;$OcT<= zbRj*=05gV6Ff+^&vchaId&mKE!dxLY%med=d@z3~00UrP2!aJ+okLwXR=`$< zR>4-o)`Xg2Yhmj`>tP#U8$+95n_*i*TVdN^+e15GJ7K#*yJ34mdtv)v`$Gp{2VsXo zhhaxxM?=S8$6+TzCt;^xr$c98XJO|;=V2FM7ekj|mqS-zS7F!wen|K4r%6LML$_eJ zVRu4zVfSG7|Bj{)J%l|9J%&AjJq7Y zHnY#Lzgq>q{(Z6Ycd8rCKp z2Wtz954VH0hjj=iz&gS@g%e>(u;g$GEESd(PKRZ{GQ(N0Y*7nTRh4;R1+!$q)S zSV_1PRt75%SHLP^RpDwF0#*~Qh1J38!ws-T7&454p<$RX7KVf2!vq)+MhcT*lrR-W zgVDnb7!$?{vtb+5||Vw3(H{&m@=$_sbQM17N&#g!v>fUW(u2O z7ML|`gW1Clm=op-yI~%fH|&G?VSz9J17T1&2 z7uFBfKRf_75H=`07&ZhpG&~G8JUjw65;iJ48a4(tHareC9yTF75jF`nIXne66*etA z9X12j6rKs21)Ck71Dgw*7oHDW09zRT7q$qtIJ^Y56t*n99JT_sGQ0}58n!0f3|kvs z2U`!@5Z(ye1lt_m0^17P7Tylq0oxhg1=|hV6W$Bk2iqS$06Pdf6g~_)0y`Q$20IQr z5k3h!1v?!+13L>l7d{WW0J|8z1iK8o621z%2D=`<0lNvi6}}C-1G^i(2fGh@5Pk@I z6n+eQ0(<&*gyG-OZsGs_j{XV1guM#ChP{Ek4Z{b(!tY@p!XIIuV4wePy7{}IAp9-- z9rgqEGYl^+g#SJs{RzXjrN0+bIP!}Wh!ltvh!ltvh!ltvh!ltvh!ltvh!ltvh!ltv zh!ltvh!ltvh!ltvh!ltvh!ltvh!ltvh!ltvh!ltvh!ltvh!ltvh!ltvh!ltvh!ltv zh!ltvh!ltvh!ltvh!ltvh!ltvh!ltvh!ltvh!ltvh!ltvh!ltvh!psLC=eC-MG8a; zL<&R-L<&R-L<&R-L<&R-L<&R-L<&R-L<&R-L<&R-L<&R-L<&R-L<&R-L<&R-L<&R- zL<&R-L<&R-L<&R-L<&R-L<&R-L<&R-L<&R-L<&R-L<&R-L<&R-L<&R-L<&R-L<&R- zL<&R-L<&R-L<&R-L<&R-{2vrZi$X-vqcl-q)R3sAsOG4HQP-kgM@6+tYlUb4Dk zwi?o^sa12UgRQQ$dfh52IxQLzO^?<@gV95xo1&Ye4@O^$ejObZlNN)Bp~q-qz?dO1 zO)eDf4YnTAx~X+@ z>w~SYwSL_?s!duOL>qb=O&hSykTy+in%f*~bFIzmHc@eDafmp2oF)#88xq$P*Bo~+ z?poaIxTv;iZ4qthZ8dGdwnN%BwQX*DuG7I)Fn&mUQ+#v$!T4+O zuj8ZIrL{w}qqozv1KSO0*VL}L-NAO(+P!WU)jq8~qCLI6rajnxNc*Pt&Fv4izt;YB z`=}0S9S|Mp9W))l4nsOLb!hHzu*0Ug)~hmJ9w(mPdkB6pH?a&+p^X;h~vW;h zqfVbY#U`dFRwfb>g^A|GVB(;}$%%^+HzgiUyqx$X@k?TCQfg9J5+;e0q)GB5^-LO> z)ReS5X=~Eaq^n6!l0GLzCnqHrB{w9~lBLO(WGK0R^0?&L$t#n$Cm&6|lKd$7LvkoN zAtfiJDg~FqO;M*fQ@W-MN*SLrCuL>Iwv;0&7gO%1yh-_)5|^5qT9jI!N=_A|>QddQ z-BJgoj!&JPx;%Ar>Ved=skc&}r+!Ker*=roN-Il4rcu*GX}UCbTGzCJX=Brx(w3yH zPurb#JneGY{j}F<-_v5!JEiBOm!~7ssp-OWZMri(nBF&iWcrl!`RS|Dx1=9PKb?L( z{Zabc^dIT58J#k+Gs-g>Gsqdd3{{3TBaqP}V{pc}jHZl58O<5nGY(~(&A5^AIOA=` zkBsQdgv`v$lFZsnd?qVXlBv&hWd<|*WDd)mm^mwRN#@$j9hrwR&t_iFe3vpXIswxoD(@0b8h84 z&Uu~lC8s5)O>U>$%-o{f>RePVIhUO)$<^lCa{akobNl8F%N?IPBX@r8^4#^g+j9@( zp3J?Ndn@;G?yKC-xxaH`^AhsX^78Y_^Xl_(dGtJfo+8hX=gb50y65%J8=f~IZ${qy zyybc8^0wve&pV!XKJP}}gS`Lp-skmHEbeXFibM zJ-=W6u>5iP)AHx$FUen%zd3(*{^9)7`Iqx==ReMWmH#RKSAKLsyMp9`?1G|#s)EJ> zLII;dP#`bR7uXB@1zihz7Yr^KT`;*|X2HUO6$R@HwiWCvI971B;A+9$f+q#93qBS6 zDu^zOFH9=TEG#UnEUYiY6;cbig_1%|p}Ejq2p0ZR*spL{;n>2dg|iD66|O8?U%0(+ zU*WOBvxQd*?-V{Rd|CLh@JC^|Fs`U$QCd-6QE3sP2vtNZVifU<T(Ya=K*{lvb0t?wZkIeN`LE<%$(NGfCDEmAOFNaOmFAWfmsXWF zlwwOMrL0n6sk~HIYAJP>0;SzbdzTI>9Z@>IbZY7B(uJkVN}EeJmF_IvUwW+cOzEZ4 zo2B$+(XYsK%1R+Vv;36;r}nU(pKrIl5c^_7@PQYE93TPdzoR_ZD(m9EM_W#`Hs zmHjG*RF141UpcjMX65|KC6y~H*Hvz*+*!H5@@VC$$_tg(DsNXltbAViy7FV?_sTz& z(N%F(2~{aonN|5!B~?{b^;PI9Vim25QzfdBS81zERgNle6;#!&s(01Es$o@QswP%V zubN%8uxe@5>ZS1qYlR_m%Q)y`^vb+Ed7b)V{i)x)dDR8Op)UOl^dLG{w= zRn_aNw^Z+}-d}yB`egO_>MPYZtM6AoseV!Yw)%7RkLs4{7(`n{0wM*GiO53~BPtQK z2qXfJpdgqC9zudpB6J8d!in%9AVfDrZ^Qt^P{b(21jJOtOvF6IBE$+rGh!oR8)7%& zAmSL}G~xo{D&jWc0pcm*CE^|8GvX&AgovqWTa!?eT$53gS5sV5QBzaXSc9!0)i7$f zHKH1Mji$y}W2Rc)Kv_O*$%X|>t41+}HMRkd}ss9Jn2rIuOC zs}wPR{0)=sOPRXeYCQSI{DHMJXRx7O~e-Cuj8 z_GIn3+RL@qYwy%PtbJDds`h>Dm)f7Tq1u?bxVjE?Npy5Z zS;wmr*U9TNb;dedox3hj7p&`E*Sl^&-O##Gb>r$L*UhM#Q@5~gN!`l2wRM~7w%6^h zJ5YDD?o{3Ry32Jp>h9J(s(V)Vs_uQ=m%1NyEp^fLarGVQ6YJCJv+E1$OX@4@YwH{9 zvGt^SdOf>dP%o`l)$8ib^^ST^Jy74JzDIqZ`hoSs>POX&ub)ysqkc~P!ulokE9=+R zZ>--|zq|fG{gL{U^=IoZ)nBi_UH_o|Y5j}(xAmXuzt#V)k7|f*h;QiFklc{bkke4u zP}We@P}hKJz%`H?=nb3(VS}_m)u3xIH#izR4M0QZhJPCRGz@4M+Ay+VT*IV>=?$|R z<~J;ESl+OvVSU4vh8+!i8xA!bYdGC-zTry4jfT4o4;!8}yli;a@VVi8!=Hwz#@3DT zjU5}48#5Yn8Vegs8><>?8=2z+KzUk{b&f?72N~f7d;R?6g?6>7CjL?4LuV*7rhX@1ib>i z2E75j1-%2k2Ymp21bqU17JU(Y6@3$Z7yS_Z6#WAI7X1sm9b{kQgk6h@oLv7#>E1kzrIA9ma&QVO$s=2E=s5^uYAN48RP*jKGY+ zOu$UR%)rdX%*QOkEW@nAti^1^Y{l%t?86+w9K)Q#oWoqgT*KVL+`~M=Jj1-iyv2OP ze8v34v|w6cTVvy~3D_iT8a4}?hb_XEVXLsU*hVY{OTbdF3@isLz)G+RtOjeqTCfhR z2OGc!vE8sev3;=vu|u&Vv173lu~V^4*g4n**u~i8*wxsz*p1k&*qzwD*n`-k*pt|^ z*o)Y!*qhk9*oWAs*caG0*bmsx*zefi*f2H*7l&()>x4_eW#Dpf1-KGiIj$O4heP79 zI3kXUW8%0tAx?@@;50Y`&VqB`Jh%WZi0g*yiR*(KfE$7vfg6LHfSZDwft!V!hx-?| z6t@!BjN5?Qg4>SUjoXhqj604yjXRIKgu8~jg}aA)gnNd2iFF zUig0aLHJ?#k@&IriTJ7bCj4yteEcH(GW;rhGkybp3w{TFH-10HfeT0LAqlA-$GlUC-D})<_+l2ds$Ao8umxQ;34}>p-AB5k8Fd>E* zM{GyzNK7WC5wnPS#6n^zv66@&))P@gERjg05*b7eQ9u+EG*_L?_Wp3=o6F zZp0qMKEwgU!NlRj(Zq4YNyKTyCgL38eBvVFGU7^NGjRiPGjTg{H*p{F5b-GSB=Icq z0`Us*I`KB~KJgLp8Sy3Y4euI$4`~2tFljhxG-(`Z5@{-_ zi8PxupY$(jDQP8X4QV}T6KNZ1CuuL~0O<(n1nD&CJn0hY8tEqKF6ja33F$fM73m%6 zBk3#Y2k8$fOo}15A-5wZkdw%%}CnY@9# znY^96i@c9~kbH!Ef_$2Mo_vXXjeL`Qm;8YIg#4WRiu{)Rk^F`HgZ!HuB1cnNQ{pKd zD2bF5N(LpHl20k3lu;@v2ueK#Nx@PG6bgk-VN-Y%Aw^12P&5=h#Z0kLTof-QKnYU1 zQhHE&Q~FZ|QHD}RQpQjwP$pBRQ)W`;P!>=YQI=6wQr1w`Q#MhyQFc=HQVviKQ;t(k zQO;2=Qm#;LP;OK1Qyx*CQeIGAQ{GcPQNB`sQvOh)s4>(w)OOSaY7#Y-nnBH>=2MHP zWz&)b7+?)V|b#)FIU2 z)X~&&)QQxo)EU%S)Opl})FssA)K%2A)D6_l)a}$=)V z)ce#&)Th)J)YsH^)KAo})F0G8)G#%g)`k{O>p)ASrO?u8*|a=bA+3~FL93?K(Hd!J z8lFa?QE3bshsLLgXfm3TrlA>VW}1!WqIqcn8bs?#`-j$x){i!jHiR~UHkvk$Hjy@! zHiI^cHjlQDwwShzwu;tFTTk0e+eX_-+e14*J4`!9J4HK7yFj~4yH2}ByGwgOdqR6o zdr5ms`#}3l`$qdkYoSHaW9V`8cJu^#B0ZI!LC>b=(~Ib(^a?tHUPo`Fqv?1$iB6?6 z=o~tqE}~26O1g%wr<>?Dx|8mq`{^LP3%xtNC%q4S0DUlh7=0vtEPVofGJP6-CVdWl zKK)<%68Z}IYWiCG2KpBIcKR;*Uitz05&CiZDf(IZMfw%`b^0y(UHU`%6Z&)dOZpr7 z2l{9FH~LTdA9@rchS7!*&*;ENWTY_C8Ci^6Mj@kwQO>Ah)G!(tCU`%FAV>B`5Fy=G< zWh`MVXRKnZWo%$s%~W1M81VVq}NW?W<3WZYrgXFOs&WxQa#X1rs3 zWPD+KXZ&J>7_FGG%s6H{W&$&jnaa#yW;64ch0GFWIkTEs%WPnxm{=x(NoLZSEGCyJ zV2YVCrjn^)8klCLjp<~1n0_Y6?9A-W?8)rI?9Uv;9LgNQ9L*fZoWz{UoWY#MoXcFm zT*O?;T)|w!T*utV+``<>+{N6>Jit86JjOi5Jj=Ymyv)4Dyve-7yw7~Ze9C;me9e5v z{K)*m{LcKvY+**RVpwfh?N|w{L{W^H5bWbI+?XB}c4W1VE3VV!4PVqIn3VBKcjV?AU&VLfNP zWW8a%XMJLQW&L3NW`$U-*s<(5b~|{U%wx12MJF~m7d$4=6`>_YIhp>mUN3qAUC$J~8 zr?H#Zv)S|53)zd=%h)T~YuM}98`)df+u6I=d)WurhuO#2C)sD%=h>IoSJ^k%x7qjD z57|%H&)F~8Z`kkIpV(j7KiI$7A$BWHEGLfBj+4MiPCloIQ_88}RC8)M z4IC5)%OP;c92$qo;c)mI5l6~VaMT>L-z%L#BGP8Uu$P7h9RPCw2-&JfOU z&M3}U&IHb6&NNOFXEtXZXCY@XXBlTDXANg9X9H(5XB%fHXAfsT=Md*8=LF|8=N#uE z=L+XK=N9J<=RW5V=PBnu&MVGa&IitC&Nt3a&L2*g6U~j~#&O$m6S#@o6mB{*RX4elExja=UW> z;r8P8`b>sEm_2TvA4d4yt4dadEjpmKxP2^4CP3O(z&Ed`CE#xicE#s}^ zt>LZZZQyO@ZR73a?cwd`9poM19p|0mo#kENUE*Ek-QeBk-QzvvJ?1^*z2Lp(z2kl4 zedc}R{p9`Og?Z8ZSbiM89Y2Ae$WP&?@iX~3{Cs{9zl2}Tuj1G6>-k7NnvdfX`4m2# z&*XFXe7=Y;zmmU(zmC6=zlFbzzmva*zn_1Ie}sRWe~N#We}R9Af0ciO zf17`g|A7CP|BU~F|BC;X|AGIR|Be5H|C=A;w-Uq(+6dwW9R!^ONrF^Ch9FyzCnyjU z3(5qQf@(pnph18VU<7ypNkA1a1S|npAP|TJQh`FC7U%>9fmvV^I0YVoPXGvlg06z@ zf}Vmtg8qVmf+2$8f>DAog7Jb$f~kV(f|-Ijg871lg2jSmf|Y{Rg0+GTg3W@hf*pe0 zf_;Jmg2RGif|G*Nf^&k4f-8b+f}4Ung8PDpf+vFKf|r8Vf_H+Cf-iz^f}esvf{>t< zFjg2Rj2Ctgb`mBFQ-vAAY+;_TKv*m+6IKYTg|)&4AySAD;)Nt3MMxL2gd8DXC=yDA za-mA773zg1p;hP*x`bX~KnMyu3%dz>2zv?p3I_-W35NGc6kZly6W$cw7Tyy+ z6h09?6TT3>7QPjJ5PlYZ75)(Z7PbhZL@}Z^qPC*;qK=|OQHm&Clqt#)<%?S+riXNwh_@U9?NI zN3>sbNOVMWTy#owMs!|uNpwYYU35!yM|5BGNc2SXT=Y`(TJ%ozQS@2#P4q+bTNDyS ziDSfV#BIgx#T~_o;uLYZI8&S>&J!1kOT=a3N-;uQD{c^@#27JNOcGPXbTL!R5%a}D zu|zBvE5#bIUThRw#5S>0>=FCKfH)}bBJM8kA?_{iCmtXkEFLBvAs#IrE1n>pES@T! zA)Y0kBc3l_C|)dHCSD<4EnX{LFWw~HBHk|ECEg?6FFqtbB0erYDLx}UFTNpP?F8(EM5r@Uml2}Qcq@ARLq?05`k}AoNWJz)* z`H~_@sia&|B|%8)B#jc31S`QyND``qE@4SH623$vkx1kcr9>mqON2w@P6&r2^#uSl;+Z%S`V?@1p>A4{J~|C7FyzLCC@ew2QZev|%`{+5QMQL-3W zYgt=aJ6VFPlPp=5D$9^%$#P}+vLacDtXx(lL&)l64KkDrBg4yxGK!2QW6Ibvo=hMU z%cL@eOeNFG^fHsoBD2e!GLOtB17wh_i>#Zhhpd;ZudKgpkZg!-xNM|sjBK22qHMBk znrwz_mTZn}zHFgvv22-ag>1E~S+-ubQMOsOO}0a}TeeqrKz2xWRCZi;N_IweUUpG- zMRrYgQ+8W+Pxe6eSoT!*pX{aVjqIK5qwKTno9u_|x2#1LC6AW3mdDB4$ven9$&=)% z@^pEYJV%}{FO-+a%jA{vYI%*kUfw82%dv8ToFu2p>2j8wBj?M7a*13fSIX6Lo!lTd z%dK*|+$HzO{c=Daly{MLm-mqOmiLtpkPng%l@FJXl8=#(lTVaSmQRzPK8I| zQv?){qO+o#;vYpXMIS{!#X!Yi#W2MP#c0J?#RSD9#T3PKMU!H-Vy);)3F`;;Q0?;+Eo$;=bac;)&v! z;)UXs;*H|H;-lh=;+x`!;jY zWt9@4tW!29kxGmbrz9%LN~)5fWGT5yzEY@^C}m2eQmxb~4N8;JsZNFy#p4DCJn?c;zJJ6y-E!lX8}Fj&inZusdBk;m2!=8 zopOV6lX9zayK#pz^TtsPcsJl=7_dyz-**it?KBrt-G(uJVEMk@AW1x$=eb zweqdNHUt6Hn#RP9t9R2@}GsuWe4DpQrM%2O4nid3bla#f`Y zp{i9isE{hO3a28dNGhs|u41Ym-wOF-OwL-N@wMMm0 zwL!H>wNv?N$5LfI6t| zqVA^dq3)&bqwcRBs2;2yrXHalr5>vuub!x$qMoLnp`N9lqn@W;s9vOAqF%0Esa~yK zt6s0(sNSsJrrx36rQWOFuRf?gqCTcRp+2oXt3I#3q`soQroO4Zt-hh@ku2E{#8m-2lF=;FsyT++;YkZo3 z2Gj&KT{Yb`Jv6;FeKq|x12sc5!!#o_qcvkS<24gCQ#8{wGc>a_b2Rfb3pI;0OEk+g zD>bV%&6@R^jhfAxt(qO0U79_b{hEWC!W(uQhKqA2gpdUo_t}KQ+HKEt)88v^G{7r;XRP*CuEawaMC4ZMrr~o1@Ls z7HW&NrP^|Bl@_6`)z)i~TC^6cC1^=nik7BjYS~(@mai3QC0dzQp;c?OTD{h!wP zF4Qj8F4ZpAuGFs4uGOyBZq#nkZqx42?$Ykn?$;jF9@ZYyp3t7sp3$DyUesRJUe(^v z-qPOD-qSwRKGr_fKG(j~zSh3gzSn-ze$jr@{?Pu`wrIoJR=QYS8(mvnJ6(dVlP*b@ zqD$9h>aumYx&mF1u0&U+tJGENYIJqFMjc9r(cyFi9a%@!(REB6N5|6%bRwNpC)X); zYMoAJ(3x}=olWP|xpiKhUkB=fx-PnIx*ob-x<0yox&gYux}mz^x{)=$+>*U!|?*3Z??*DusB(l6C7*RRyC);H_d>o@8*>$mE+>v!q* z==bRl=nv_S>W}MB>QC#>>M!Uo>96Ro>2K(7>F?_A>mTYL>!0fX)4$Ze*1y%i*MHJ~ z(SOtb(Erl^(TDY|3^9h*hB!kzLkB}gL!u$skZQ;W*v7yvZZm2X=8)^*o zhDHO*fHB|Ft`jJgU=8!K!(nS zu7>W09)@0qzJ~sWfri0`p@!jxQHC*wafS(oNrowg>4ql5EW;ebJi`LRBEu5HGQ$eP zD#IGXI>QFTCc_rPHp33XZo^)~e#1e-VZ%|wal_Zbfu4;zmfj~h=K zPaDq~&l@ipuNbcxZy0YG?-=hH9~d7SpBkSVUl?B*-x%K+KNvq7zZ$vY8wvm&s%DnF1!z)Y;V4)ZNs>)XUVz z)Xy})G{`j6G~6`OG}<)QG~P7PG}$!OG{ZF0G}|=SG~cw)w8*r?w9K@^wA$2cT4&l| z+GN^d+Gg5e+GW~n+HX2&I&3;>I&L~?I&C^@I&ZpUx?;L!x?#Fyx?{R$dSH5FdSZHR zdSQBHdSiNL`e6EG`eOQK`eFKQYB7aPt;{jz*5){Kyt%zO!JKGLHm91?&6(zGbFMkx zTxc#fmzpcgRc3^_)?9CHG^5NIGtNvflg(5!-OMzz&0I6zEHsPFQnTEwG;7Q{v%zdK zTg*1I!|XD9%sz9#44FHdyPCV3dzgEf`alr<$jm zo6NJ!bIkM13(bqnOU%p6E6l6RYs_oS>&+X@o6TFz+s(Vod(8XH2h4}eN6g2}C(NhJ zXUyl!7tEK;SIyVWH_f-rcg^?B56zFwPtDKGFU+sZZ_Mw^AI+c5U(MgmKh3|*E#|Pf zl_kc~+7f4px3sr(v?N-REvc4tOQt2;l55Gg6k3WcrIvC_rKQ?ZYpJ(1T2K~@1!o~x zNEV8PW?@)Z7LJ8y5m-bPsYPy4TGSS;MQ<@$%oeM~ZgE=N7O%x`0W6TEv!$EmA4^Y5 zZ%bcGf6GA2V9QX;aLY)`XvlC(9ShH_H#pFUucG$P#6Zw#Hi9Sle3LSvy!eS`)3w)>LbT zHOrb~&9fF*i>xKqGHZpk%8IboTI;QiR+JTE#aRhfl9ghmSs7NAm1E^u1y+$&YL#1+ zR<%`Y)mx2Lv(;*~Tb)+7)ob-z0V`zfZ0&08ZtY?1W$k0_XB}W2WF2B1W*uQ2WgTN3 zXPs!BY@KSIZf&y8vd*#2vo5gyYh7$zYF%z!Xv7WPDuwJrWv0k&@u->xXvEH*jus*gvwLZ7Lu)easvA(l@uzs?B zv3|4uu>P|Cv4*TswrE?dt&Od%t(~odt)nf`mTXJ4rQ0%X*|uC;zOB$!Y%8^u+bV6< zwi;WVt-*%0p>0?j-bS>MZB!fG#+aFuV7G;mN z$J*Q2+uGaNJJ>tg6Ya_NRC~HT)1Gb5wddOl?Zx&|d%3;RUTv?j*V!BFNITk&wd3tX zJK0XP)9p+<+s?J~?LxcQF15?;O1s*wwd?IhyV-8F+wD%f+wQgd?EyPz58AufyV?J- z_q6x6_qF%853~=q548`skF<}rkF}4tPqa_APqk0CH`!;|=h)}j7uf%`FSakWFSoC> zueLYa*V#ANH`%w?x7l~tciH#Y_u2Q`584mgkJ^vhPufr0&)U!1FWN8LuiCHMZ`yC$ z@7nL%AKD+=pW2_>U)W#S-`L;TKiEInzu3Rof7pN7|JYmXVS6h_jH9(9&Jpiu??`ZT zawIuY9BGaWN0uYUk>@CI6gf&9WsV9*l>_0Zb<{f=9ViFdfpy>=L^mTMu*v9b=VzFhs)t{_#6QT=mJa_n*Ja~yCSavX6SbDVITa-4CTb6jv-a$Ip-bKG#;a@=;@b=-G6bUb!E zbv$>xaJ+K7alCVUaC~xnaeQ<9aQt%oakMzXj#kbXXKQDiGv3+Wnc(c?Ome0;)0`R3 zEN6~0&zbKmbQU{Ho#oC-XSK7&S?6qUBAsX_)`@o#on$A)NpmusEGNgwa|)ayr^G39 zDx4~(#;J1}oJObFX?5D2PN&=Hb^4uv6LNNTc6D}l_Hg!e_ICDl_ID0+4t5T84tI`p zj&_c9j(1LUPIgXpPIJz1&UDUp&UMarE_5z(E^#h%u5hk$u5mUy*Eu&hH#xUBw>ft> zcRBYs_c;$Z4>^xGk2;S#PdZOK&pOXLFFG$fuR5g@}A2=U5pE#d6|8u@{ zzIMKKzIT3fes+F!eslhC{&N0thMZBZXjiPOjjOGzovVW@!PUu?ax3>F1O3$^0@*o&=qubadmV3YDCqa?Ntham{lraQ*9Ass&H=-TYs>e}Yo;o9Ze zr@GVKneJ?Nt~=jd z=q_@XxXauX?kYFJUF)uQH@cB-v>WTjyNPbHo9d>!nQoSwqZjoE!mbn#fm0RuB zy7g|O+w8Ww?QW;r?e@5R?tmL~2i;xV-Q54Ud%AnM`?&kL2e=2hhq#BiN4Q72N4v+m z$Ga!GC%dP*r@NcnGu^Y@bKUdZ3*C#{OWe!c%iSy8tKH4+b?y!BP3|r3t?upao$lT4 zz3%<)gYLubqwZtw6Yf*)GwyTl3+_wqEAFfA>+YNG+wQyW`|gMC$L=TYXYT*pFWs-* zZ{6?RAKahZU)Xedulv&o(2!ngZ5xOcn`rt@=!c955vRqa6DWO-y`&hJyMU{qx7gf z8jsFn@R&RnkIm!oI6ZEU*W>p99>~+#)5X)x^N**er?;oCr@v=_XOL%zXP9S%XOw4* zXPjreXQF4aXR2qqr^z$RGut!QGvBk&v&gf=v&^&Hv(mHL)9hL2+2Gma+3eZs+3wlt z+3nfu+3z{vIpjIwIp#UxIpsO+IqNy^x#+p~4rTj(wFmUzp&72YZ@!dvUD^EP;qUbGkM#d`@}l9%G8c^O`om+j?x`Cg${?3H@u zUWHfX)p&JYgV*FWd#zr(*Xeb8yz4U@4wzf-X-2;-WA?e-ZkE4?>g@W?6st?@sS-?_TeI??LY&?-B1Y?+NcI?-}n|?|JV+AW?<4PH z?^Ew{?+foM?;G!1?|bh@?`Q8qiN0iC zsxRG_;mh*n`0{)Oz9L_-uhduWtMpa-5WZSpy|2-S@?m^fAKpjwk$qGj-N*2;d>kLo zC-4b`=zSF)lzH`0{zDvF_y_ri_=oz3`$zgm`^Wmn`6u`%`KS1&`KSAv{ImRX{PXT2O{^$Px{4f2l{crv6{U7|F{9pXv{6G9Z{lEP! z{;)qP5FLmOvsE5P{l2 zeV{Rb44?zp06strkOGtdEx-sc1MC1dzz+xmqJShI3n&80fI6TJ=mW-pDPRfM0*-() z;0|~L{s0gF1HnL-K(|2mK#xGLK%YRrK>xtNz~I2pz_7rGz^K5Oz}Ud}z{J4hz|_FB zz>L7m!0f=Bz`VeMz`udTfhB=uffa#Ofz^TLz`DSOz{bGlz}CR_z|O#~z@EUqz=6QQ zz~R8r!12I|z^TBQz`4MMz{SAjz}3L@z>UDIz@5Oo!2Q6(z~jKv!1KU=ftP{TfwzHo zfe(RCfiHotf$xEzf!~2Yflwd{hz4ST)<7H(53~n503CruAQ?yj(tr#g3&;j?fqbA4 zC<01=GN1ye0;+);pblsN8UYl50dN2wAOd873eW%szyde`7vKXzKnzF#8K3}EfCkV4 zdcX*n0SjOQ9DoaO175%n000OCfi6HdpgYh5=mqov`U3rdfxuv32rvv70gM7h17m^l zz(imYFa?+f%mA8zS->1%9xxwR2rL4Y084@8z)D~>um)HQtOqs%n}99AHed&^6W9&x z1@;36fJ49$;23ZmI0>8v&I0Fv3&17d3UC#;4%`H819yOXzyshB@ECXsJO^F?FM-#< zTi`wL0r&)b0lop>fuF!{palp4QD8I}3$_O1z<97d*a7SaCW6Ue3YZ3FfLUNRm<#5E z1z-_a0+xa0U?o@$)_}EOJ=h4MKs1O2@gNZ-ffSGiGC(HC2Du;~6o4X70!l$Ss07uZ z2GoHD&;*)6D`*FupbPYXJ}>|RAOv;>yMW!mf54t#FR%~T4;%mv1P6md!QtQta1=NO z91D&ICxVl~Dd0441~?O(1@#%ApioQAk+ow26cyeK)s;eP+zD&G!PmD4S|M1 z!=aJTXlN`n4w?W>f+j;#q3KW)G!vQ)&4uPc3!s0Y#n2LH8MFdg39W{jp>@!DXd|>4 z+5&BZc0jwJ-OyfWKXd>(1Ra5nLC2w!&}rxlbPl=zU4$+}SE1|B4d@nh2f7R0haN(Y zp(oHY=s)NM^a^?dy@TFEAED3C7w8-G1NsU5hFYL76cvmP#s*sl)((}FXCGlR2(bAt1N3xW%S zi-JpnOM}aUD}$?oYl3To>w_DDn}S<{TZ7w!JA=D|dxHCd`-2DnM-V&!Sd}OM0L`{- zbK_>){&}8U*|uGq?SD?%!L)zV4yPSWJDzqT?Nr*Cw6kgF(=Mi6O1qMF zE$v3y&9vKTchl~rJxF_$_Bicn+Viv*Y5%3YPJ5H~F6~3wr?k&$U(>#){Yd+j_9rbv zAX6YqAZs9dAZH*~AWtA)Ab+4>pm3mQpjepn9NYpjMzx zpkAPUph2Kfph=)wU;i1p{!M!_b*rom>x z7QvRm*1@*HcEJw8PQlK>uEB1>9>HG0-od`X{=oskLBS!x{{@EyM+8R(M+e6Q#|Fm- zCk7`4rv#@3rw7x5!C)vD3C4o)U@`~k z=7$!97KN6CmWGyxR)$uE)`ZrD)`vERHifo?wuQEbc7}F`_JsC@4ulSd{tX=t9St21 zod}%@oe7-{oey0MT?$P* zSU4U|hJi2`hQnwW3lm{7Oo!Pp7Z$=&SPrXUEo_9XupM^8emFfmD?B?qH#|SQAiOBN zB)l}dJiH>jD!eAVHoQK(A-pNPCA>AfJ-jo#E4(MXH@rW5FnlO{ID9mGEPNt-GJHCG zHheC8A$&1>IeayIEqo(S6TBLfUW~6qcPNZI>exyO9QKWICX{1@Cd8B2eRisU%U8H@aW2950OQc(*d!%Qi zSENs*U!;FzU}SJ)NMvYaSY$+GRAh8yOk`|id}Lx|Qe;YGYGisOEfR=?BH>6h5|1Pz zsR$5(B1i;{;1M!HMVJU1;Ui*1iYO5^qDRb#6>%bN#E;C3%!=tct9Otc|RXY>aG*Y>8}*Y>(`W?27D(?2YV?9E==_9F81`9E+TYoQ#}~oQa%^ zT!>tZT#j6cT#MX@+>G3g+=<+aJcvAuJdQkxJd3=Dyo|hxypFt$ypMc{e2RRIe2sjM z{D}OD{ElRZW{PHxW{qZx=7{Es=8oo#=8G1H7K#>*7L68*mWY;$mX4NM#u~?(#{P~qkF|)kinWfljkS+;h;@o}j&+T7i}i^0 ziuI25jrEHShz*Plj{Pq-G&VdoA~q^EIyNRYE;c?kF*YeSB{nrSJ(d;=#6q!fEESb!M$C-aF*oML(ql7Yvtx5&^I{8P3uB98 zOJd7n%VR5Jt72tgF;8)KVdTVh*d+haRpyJCA{dt>`!2V#d}hhs-#$7080Cu65# zXJY4K=VKRRmtt39S7X;>H)FSAcVc&A_hS!Yk7AEwPh-zxFJk}2Ud7(T-p1a?KE^)9 zzQn%9zQ=yVe#QR8GQ=~*Gsm;Wv&D17bH;PW^TzYV3&ab?3&)GZi^WUCOU6sb%f!pY z%f~CmE5)nCtHrCwYsPEE>%{BE>&F|!8^#;Qo5cT)H;cE3w~V)rw~e=pcZhe4caC?7 zcZ>Ik_l)1cq$IYp*Rvp<9M8mQ*kEF#`(Aqm*R3a)@e5)BiL6HO9-Cz>T%Bw8g}C)y_3 zB|0QJCORj&B)TQKCweA&C;BA%CHf}@CI%&jB>tBemKdHGnHZh;CowiLE-@i7F)=wY zB{3~AJ&~3OBtnTuBASRN5{Xm-NI(fVfhMp7k)RTEf=zG6O#vc!tSs>JHV+QhoVhQ!9i=ERo7w#4?t&cv?7p2Xh7 z{=~t=p~T_Dk;JjY@x;l*sl=JY*~Iz8g~X-AmBiJ=^~8%`l{yTpgY$HeEvm&CWk_r%Y{uf(53hGeE>mSoms_GFG^u4L|H-ekUH zfn>pC;bf6yv1IXN$z-WynPjuA1 zX36HsmdRGhHp#Zh_Q?*(PRY*6uE}o69?71`-pM}6e#rsJfyu$iA<3c1VaXB6k;&1? zf0AR9SMen@^yeolT#eoKB&{!IQ#{z+y`WlCj9 zWld#I5HBJ4UYMyG5YL#l8YMW}8>X7P~>YVD5>Xz!B>Y3`5>XYi5>Yo~r z8k8EG`d?~jYItfyYE)`;YD{WuYJ6%!YEo))YHDg)YDOw86-50VRNvKxv>1P!=c; zQ~)Xgm4T{2HJ}Di6Q~W;0qOzufd)WBpfS({_#0>jv;bNHt${W`JD@$#5$FVT0lEU+ zfgV6Fpf}JL=m!h{1_FbDA;3^z7%&1D35*8*0mcI3fC<1vU@|ZTmVHP zhye*82><{HzyJbZ01l7<1uy^$@PGhFfDEXB1{i<|*nk6gfDgs|n}MysHed&^6W9&x0rmm=frG#y;4p9mI0hUCP6DTZ zGr(EkJa7TH1Y8EL0@r{Wz)j#ba0j>t+y@>4kANq@Q{Xx90{9Pj1-t>?0`Gwjz$f4{ z@CEn=db zfEB^YU=^?$SRJeZ)&gsTb-{YzUtj~U5!e`P3jPf?2U~!xz}8?}upQU|>(fg8Y$;AU_OxDDJ6?gV#%yTQHSKJWl|5d0TB3?2oKfhWL| z;A!v-cn&-dUIZ_JSHP>_HSh*_6TA)H0q=qL!H3`@@Co=7d=9<<{{vrvZ@{)$^>PGvO?LQ98gXuHfP*JEDR01jqm4?bd z<)HFVMW_-~1*!^FgK9uEq1sR#s2)@wY5+Ba8beK>zoBMO3#cX33Tgwjh1x?MpiWR{ zs4LVB>H+nHdP9Aneo%jCAT$UX4E+xp3Jr%wK%=11&=_beG#;7&O@byvQ=nR4f+B7gnmPRpbT&(I5V6T&IadzbHcgdJa9fZKU@GV1Q&*j!o}bc za7nl{Tm~)&mxn9BmEg*7Rk#{l1Fi|zhU>uf;QH`ia6`Be+yrh4H-nqQE#X#h8@Mgp z4(2s{)X29JP8!lU7T;4$zxcsx82o&-;U zr^3_W8E^m&!XY>UN8vb}fKxC4Lof^@Fb3l=2~#iwvoH?}umsDn0&B1io3I5tunYTe zIy@7e4bOq+!SmsT@FI8#ycAvruYgy=tKl{9I(R+25#9uEfw#ij;2rQzcsINU-Usi8 z55kAwf8itWQTRA~0zL(whR?$1;PdcB_!4{tz6xK5Z@{zzlGnyAK;JhXZQ>J75)zYfPcZi;S5MdBr}o)$%Mnj+1R z=15DV719Q2i?l;JARUp;NEf6V(jDoE^g?~AaV#fj2uCZBFB*v$SLGBauzvx%M)RO~(fnuuv=CYtEs7RHi=!pcQfL{pELt9|fL25+qgBvqXmzwES_`d>)L56V?Umigm|& zU_G(kSRbq()*l;)4Z;Rv|HFo2!?6+ANNhCr4>lGXhmFT3Vw12b*i>vfHUmq;f>;QP zU{NfFC9ot0U?2uz2!>)fMqngHV+_V&JSJcgCSxk5VFqSm7Up0s=40vDOl&qb2b+h@ z#};6Vu*KL?Y#Fv3TZyg0)?jO~b=U@MBeogaf^EaLV>_^2*luhuwh!Bn9mEb{hp{8r zQS3N&0y~AB#?D~pu=ChO>=JevyNX@IZeTaDTi6}!E_NS#fIY+>V^6SW*mLX!_8;~N zdxO2j-eDiGkJxAI3-%TJj{U%XVZX6ISVlY(o(0c}XUB8kIq}?h9y}kOA1{Cx!VBX? z@nU#!yd+)$e z8gAewZsQK_;y#{^&%$TpbMSfie0(9k2w#jZ#h2kL@Rj%~d=0)9UypCVH{zS|E%;V^ zJH7+oh404q;QR3X_(A*-{x5z6KZ+m6Pv9r<)A$+u9DW|ZfM3Ed<5%!&_;vgyeha^i z-^K6Y5AcWhBm4>e6n~Dtz+d98@YncT{2l%t|A>FWKjUBVZ}<=VC;kingJ&Qz5t)fB zL^dKjk(0{06C;UH#Asp+F_sulOduu_lZh$BRAM?Yg9s2oB1A-pC=n+TM3Mjq zkbnq;Kna{62$G-)hTsUE5D1Bo35C!IoiGWDunCv&2%nfq%p&FxbBTGx0%9Stm{>wA zC6*H_h?T@@Vhyp5SWj#qHW8bNEyOlrJF%14MeHW_68nh##6jW^ahNzl93_quCx}zT zY2plVjyO+TATAM?iL1ml;yQ7YxJBG1?h^Ni2gF0-5%Gk0N<1T85HE>W#B1UW@s4;; zd>}p%pNX%;H{v_-llVpaCNhv2$;@OHGAo&#%t7WPbCY?NJWOuR$*^}%|_96R{{mB93AaXD{gd9o^BZreC$x-A#MkdH436LNOkqC*B7)g*MNs|o8k~}GpA}Ny!sggQr zkQQl^4(X9TnNH3kXOnZudE|U@A-RZLOfDstk;}=IgCNoeOsmxRs zDl3(p%0cC%a#MMzyi|Uw09A-8OckMuQN^heR4J-7RfZ}@m8U9Fm8i;8RjL|QovKOI zqH0rhsd`j>ssYuIYD_huno`ZE=2Q!+71f$*L$#yYQyr;JRA;Iy)s5;-^`v@Hy{W!b zKdL`9kQzh{q5ek=rG`@@sFBoY>K|$hHI5ojO`s-Glc}lHG-^7PMg^!K6{aFol!{Xc zDoFtpNWm0Bp%hLL6iLw(L$MT336x05ltO8gP8pO%*_1QoE=<)Lv>Ib$~iZ9ik3XN2sIJ zaq0whk~&SDq0Uj~sSDI4>N0hOx<*~6Zcw+V+teNE9(A93Ks}-!Q%|U8)N|@3^&j<$ zdPBXX-ccW@kJKmX3-y)yM*X0EQopD_R0cW|ote%;XQQ*zIp|z;ZaNR0kIqjQpbOE3 z=^}J7x;R~eE=8B7%h2WM@^nSI5?z_DN>`(+(>3W@bZxpWU5~C$H=rBRjp!zHQ~GbZ zIo*P8Nw=ok(Cz5Hk(K>C?7H!im?a@9xlb%J-rsvZ0==t>cm(wfhRrG3lExnFj zPj93*(VOY5^fr1sy_4QW@1gh7`{)DoLHZDVm_9-urH|7m=#%tm`V4)RK2KkuFVdIk zEA&t_N#CaL(0A$k^aJ`K{g{42Kc%13FX)%_EBZD4hJHuCr$5l2=+E>Q`WyY7 z{z3nuf75^Hj7%mbGn19c#$;!5GP#&MOkO4*Q-CSR6k>`nMVVqu38o}diYddCWy&!X zn2Jm#rV3M)sm9b`YBIH$I!s-r9`hH|fN97yW|}ZfnPyCLrUlcAY0b1@+A-~!4ooMe zGt-6X#&l^G$z1=m@pGzVoaP#FewILKn7+I24!%DU`U2$7=~qdMqornW)wzcbjDyz#%3JG zWqc-`naRv%<}h=a`OE@lA+wlS!YpN$Gb@;t%qnILvzA%MY+yDro0u)kR%RQsgW1XK zV)ih5nSIOw<{)#3Im{ekjxxuY6U<5GG;@YI%baH}Fc+E2%oXM;bDg=t++=PucbL1( zedYo4ka^5JVV*M2nHS7U<`wgrdBeP8-ZLMVPt0fL3-gWn&ir70GQXKWOa?X+o0-kR zW@EFnIoMomZZ;2_kIl~(U<3(68?lYqChXsAGqyR~l5NGdX4|su*!FBkwiDZ#?aFpzyR$vnUTkl+ zFWZmp&kke`-3hn2oS8 zHqIv4Bnz-03$X}`vKULSBulXj%d#9Rup%q53ahdjYp^D3u@3999_zC+*;(vtb}l=Q zozE_07qN@krR*|xIlGcw#ja-8vg_FO>_&DIyP4g>ZezEzJJ?<9Zgvm5kKNB6U=OkX zvWMBD>@oH@dy+lHo@URm=h*Y?1@;nqnZ3eZW3RI}*jwyv_6~cGz0W>iAF+?wC+t)9 zIs1Zr$-ZJ=vv1gU?0fbD`-%O`eqq0{-`OARPxd$aht0rc;xcntxNKZ@E(e#3%gyEC z@^SgO0$f3^Fjs^t$`$8Ia3#6YTp6w`SDvfDRpcsjRk*5LHLeC%ldHwm;p%erxWBjt zTtlug*Mw`zHRGCdEx49kYpxB~mTS*-;5u@hxh`B+t~=L*>&f-z`fz=@e%t_VAUB8` z!u^jM$_?j6a3i_V+&|nHZY(#Ro4`%vCUaA`soZpK2A9SKxeyoTB3z7%a|tfR0UXG| z9KxX-&Ji5RQ5?gu9LEWq$Vr^Sshq|coXJ_7&AFV%`P@uy7B`!l%gy8Fa|^je++uDC zw~SlPt>9L1tGPAYI&M9;f!oM!=C*KKx$WEzZYQ^!+r#bU_HzfggWMtRFn5GI${puU za3{Ib+!^jHcb>bzUF0rtSGcR(HSPv?le@*;;qG$xxcl5g?h*Hxd&)iIo^vm`|F~D& zYwj)gj(g92nlHnb<;(LG_=$*d<(uM-XM9r%uXC%y~cmG8#);Cu4D_}+Y9z8~M8AIJ~l2lGStq5Lp@ zI6smf#gFF4@MHOL{CIvMKZ&2rPvxia)A=+$zz6vdAK{~Xj8E`MKE;DP#KSzoV?53i zJjK&I!?Qfk3%tn7yuz!z&KtbRTfD=&yvL{WGx=HkY<@03kDt#k#R{BC{^zn9;~AK(x2hxo(%5&kHDj6cDju{sMoIzr-t zp8vpqxG+K( zC5#sS5ylGRgz>@zVUjRem?BIQrVBHKG$AO2gs>15VnSR<2q^&&KmisI0TnQT5J-U% z7=aZyffqzU5@bOYG(i^(!4ho25j?>c(uJAAY+;TtSC}s>5EcrHgeAgKVVST(SShR$ zRtsx|b;5dKgRn{1ENl_B3EPDo!cJkgut(S{>=zCQ2ZckzVd02yR5&i25KaoGgfqff z;hb zAbb=)315V-!Z+c&@Kg9D{1!5Z8O2OuW-+UnP0TLl6myBW#XMp@F~3+qEGQNhi-<+V z;$jK0q*zKUBbF7*i50|(VkNP%SXHbhRu^lCwZz(D9kHHRU;ImKAT|;ki%rD8#b#o2 zv4z-5Y%R7C+luYQ4q`{Kv)D!KDs~flh&{z#VsEjp*iY;)4ipE8gT*1@P;rwh-1ZZ;&^d_I7yr=P7$Yx)5RHLniv#AVpxobF)=PC#FPk#pa_YGh>Dnqi=;@2 zw8)B_$cus~iL$7Os;G;GXo|Mzh_2{~>EcXrmN;9SE6x+=iwng?;$m@$xJ+Cwt`Jv> ztHm|qT5-L&LEI>A61Rw3#ckqtai_RT+%4`E_lf((1L7g^U-7VbL_8)Q7f*;M#na*$ z@vL}WydYi_FNs>Kv2Bx_DE(CEgbAiuc6(;sf!K_*i@*J{6yfFT|JPf8uNLjrdl4 zFMbd|il4+U;#cvT_+9)d{t|zSf5ePZCMmO&Rmvu1mvTtCq});-DX)}YDj*e<3QI+# zqEa!bgj7;0C6$)SO68>TQU$4!R9UJbRh6nsHKdwSZK;k_SE?udB{h&5N{yr@Qd8+~ zshQM5YALmn+DL7sc2aw(qtr?2EOn8(N!_I$QctP3)JN(o^_K=n1EoRI5b1x?P-&Pn zLK-QJl158oq_NUCX}mO1nj}q@rb^SK>Cy}-AO)q66qce=Oo~ehDJ20CC_xe;p%NzH z5-Cv6Uayx+~q29!L+RN77^Isq{>GF1?WclU_-$ zr8m+$>Amzp`Y3&tzDQrCZ_*Fxr}RtuEoG21%9-TMa#lH;oL$Z#=aO^FdE~ruez|~L zP%bPNk&DX3;HpFS)neSMDeGmj}p$f%9PB=tjx*0EXtBB%ZjYYx@^d%Y|D=9%ATAq&y;7$v*o$+JbAvnKwcy- zmY2v&<>m4Ud8NEcUL&uS*U9VUjq)aWv%E##CU2K_$UEiT@*a7wyiYzLACwQt|H?E_Fpfpq(DUFq;%HK*erMc2lX{EGQ+9>Um_DTn(qtaRFqI6ZdDLs^)N-w3i(pTxH z^j8Kb1C_zb5aoZ$P-VC>LK&%yQvOlKC}Wjz$^>PiGD(@NOjV{S)0G)YKnW@#C9Fi1 zm=aeKN>Tw7P=OR!K^08F6+)pDT45Ac;T1s<6-iMPRnZh(F%?U(6-RLuUrASHDzlV1 z%3NihGGAG!EK(LLOO$2Ga%F|GQdzC6QPwK!l=aF+Ws|a5*`jPywktc7oyu-ykFrBjt(mRC%U6S6(XrDX)~*%3I}~@?QC%d{RCuUzD%PH|2-&Q~9O*Rx+p=)l6z; zHLIFU&93H9bE&!2JZfGwpISgIs1{NSt3}mfYH_uMT1qXgmQl;9<&{r)LLq7wT@a>t*`#2Hc%U?jnpP;Q}u7Pnc6~askTyEt8LYGYJ0VV+EMMSc2T>k z-P9gxPqmlYTkWg%Q~Rp})Pd??b%^>ub*MUA9ifg?N2&j)W7M(gICZ=_QJthtR;Q@b z)amLBHBAkwAvLT<)TkO)6KYaTsh|p}u!^XdimQZ5s+7v8tjejpDyotytBR_rx@xGV zYN?Lus-EhrGu2t@Y;}%0Po1wWP#3C;)FtXtb(y+cU8$~8SF3B(wd#6xgSt`Oq;65S zs@v4<>P~f+x?A0&?p61z2h@Y=A@#6&L_Mk=Q;(}B)l=$e^^AH>J+EF+FRGW-E9zDC zntENmsoqj=t9R6U>V5Tr`cQqOK2e{l&(!DYOZ7kXmHJwJqrOw$s~^;l>Sy(f`c?g= z{!o9aztrFAA2p+vNz1Hd(XwgTwH#VbEti%@%d6$n@@oaPLRw+1h*nf9u9eVAYNfO? zT3M}}R$i;1RnjVJRkW&Fb*+Y0Q>&%b*6M2YwEEg#T0^ao)>vzzHPxDF&9xR#p_CdTPD2K3ZR`pVnU+s14EvYeTgEX~VSP+6Zl=Hd^~f z8>5ZY#%U9@iP|J>vNlzlrcKvoXlYtd3u$33qQ$hhme7(~N&_`WgEd4$HC!V!Qlm6R zV>M3WH9?a!SyMDs(=|ggHA}NKSMxMqOV?&;v$Z+eTy37VKwGFS(iUq=v}M|IZH2Z{ zTdl3p)@tjt_1Z>lleSsgqHWW*Ydf@^+AeL6wpZJy?bi-yhqQmS!`cz;n08z{p`FxD zX=k*v+BxmKc0s$OUDmE>SGDWf4eh3OOS`Sz)$VEcwFlZG?XmVmd#XLtUT811|Fl=y zYwfM}PJ6F?&^~FOwJ+LN?VI*P`>FlXertcUjCv+Lvz|rIrf1i4=sERVdLBKmo=?xO z7tjmoh4mtOQN6fcLNBS8(o5@Q^>TW7y@Fm*udG+mtLoMC>UvGRmR?)0qu0~x>woDD z^oDw4y@}pb|66aSx6oVat@PGm&71`e^+heT+U%AFof)C+d^*Df(1>nm%1m(*t@?59wh&s>k%W zp3svzpo2Q3!#bj4I<6BssZ%z>$;(vx~1E?t9!byr|UEI+4>xP zu0BtnuP@XW>5KIx`Z9gFzCvHAuhQ4(YxQ;ddVPbwN#Cq*(YNZ`^d0(6eV4vl-=pu- z_v;7rgZd%;uzo~8svpx&=qL44`f2@)eojBHU(he=m-H+8RsEWNUB98<(r@c`^t<{! z{ek{af22RwpXkr@=lTo%rT(A(T7RRz)!*sw^^f`|{j>f>|E7P}f9OB;U-}#{Z0A#&BbVG13@i{9}wU#v0>{@y0}B zk}=tsVoWur8#9bFBVYuLun{q$M$CvCNh4(d253MAVxR_Q;09q(25m3~YjB2Oh=ydy zhGJ-jZWxAXScYS`hG+Ojx-rX`ZOk#|8uN?=#zJF}vDjE*EHjoHD~y%KDr1eY)>vn( zH#QiXjLpUtW2>>v*kSB6b{V^kJ;pv`zj44gXdE&Q8%K>S|$+&D>F|HZcjT^>I|k~@JDHu$u4Xs0yV=9+Y4$ezn0?KDW`A>_ImjGr z4l(~{4mF3HBg~QJD08$q#vE&oGsl|~%t_{CbBa0DoMz52)69SwG(%?CjG8esZYIp6 z37DV>nXrkNn2DQ&Nt%>No2<#1yeXKXDVvI^nwqJbrfHeB>6otRo9X6EbCx;VoMX;2 z=bH=6h2|o2iMiBVW-d2Zn5)dy<{ERYxz1c~ZZtQUo6RleR&%?#!`x}^GIyJM%zfs5 z^MHBKJY*g=kC;czW9D)5qo8dgoKmQ}~9Yt^&rTYp&%twvU3tBKXr`rB%5wXj-Rt*q8oTdSSb-s)g= zv^rT`tgco!tGm_1>Sgt|`dEFfepY{Lpf$)EYz?vgXAQH4TO+KI)+lSVHO3lijkCsE z6Rb(rWNV5w)tY8ax6-VD6|_QD*os&&D{dvMq?NKj3$kDfu}}-M2#d5Ri?$exvv^Ce zL`$+{OSLphw+zd)Y|F7+%d>nd-I`_1w&qxKt$EgbYoWEsT5K(`mRifL71l~?m9^Sh zW398+TN|v6)+TGSwbj~YZMSw!x+fx?|n7?pgP(2i7C&vGv4yYCW@_TQ9ButXI}+ z>y7o+dT)KOK3bow&(;^~oAurLVg0mzS%0hyc1Amso!QP}XS1{0IqaNvE<3lK*Uo3> zw+q+>?Lu}DyQp2vE^e2wOWCFEGIm+JoL%0oXjif;+g0qUb~U?(UDK{**S71}b?y50 zUv>k#q20)CVmGz_wwu|_?G|<`yS3fMZfm!*+uI%OPIhOzi`~`kX7{jr+P&=Fb|1U1 z-QONy53~o_gY6;qP_X|5gWBJo3KfnvT2*KS(~>7TeKxxwiR2mb=$B_+p=xjwLROn z)9sn|EPIYU*Pdt3w-?w8?Zx&Id#Sz5UT&|jSJ|uWHTGJ2oxQ={Xm7GN+gt3d_I7)R zz0=-h@3!~Y`|SPp0sEkR$o|(pVjs1S*~je@_DTD+ea1d(pR>=~7wk*+W&4VK)xKt5 zw{P0F?A!Jo`>uV@zHdLYAK8!XC-zhOnf=0kY5!-xvR~V8?05Ei`-A<_{$zi)zuMpI z@AeP-r~S+RV`p$OI+>izP8KJdlikVTv)duq&qX6S?<{Z@ zI*Xkp&QfQYv)ozXta4U6Yn-*tI%mDJ!P(?&cD6WMoo&u`XQ#8v+3oCc_B#8V1I|I` zkn^u|*g4`Hb&fkHoRiKe=d^RiIp>^rE;tvROU`BIigV4m?%Z&0I=7tL&Ryr8bKiO3 zJais8Pn@UDGv~SU!g=ZZ=e%~_IB%VI&U@#B^U3+_d~v=y-<Ua5K7@ z+$?TZH=CQ?&Ee*BbGdokyly@>zgxg9=oWU1xJBJ!ZgIDSThcA$?ryhHfLbvD?IL>i+FEcU!nE-BxaEw~gD*ZSQt) zJG!0R&TbdCo7>&(;r4WUxxL-KZa=rbJHQ?24sr*(|8s}B!`$KS2zR7A+Wp5Jox9%M;BIm^yIb6??lyP3yVKp}?soUMd)>hECy2soT z?n(EQd)htYo^{W;7u<{PCHJy>#l7lYcW<~i-COQ$_l|qlz3)D7AG(j+$Lfy-8b$#_r3eU{pfyjKf7PtZ|-;Zhx^n0<^Fd6xEZ}nUS=+V2UJb9N zSIevI)$!_j^}N5l23|w2k=NL3;x+Y}dCk2RUQ4f)*V=32we#A09lVZSC$F>D#p~vE z_j-6ey+cQl26}_M!QK#Ws5i_T?v3z9dZWD2-WYGJH_jXHP4Fgqle{V3 zRBxI$-J9X1c>yovg}sOu^{@IVjpU=Q(75BCU<^eB(^7?1UMPw+%f@?=l( zR8RK|&-5(M_8iakd@tRb>CN(Hdvm+SOncn7^h-oM^q?}&HQJLaA6PI{-j)7}~H ztar}4;9c}Cd6&H_-c|3Kcf-5s-STdGcf7maJ@0|{(0k-P_MUi8y=UHY@1^&j_sVyPw0)>F4tE_<8+& zety4zU(he)7x9bw#r)!a3BROY$}i)W^~?F?{R)0Xzmi|suj*IxtNS(lntm<6wqMt; z=hyfD@*DUK{YHLczp4MX-^_3BxA0r~t^78ATfd#(-tXXd^gH>T{jPpDzq{YV@9FpQ zd;5L;etv&{fIrY5TmP6`#b!d{w{yFzsKL_@AnV*2mM3-zy4wWsDI2q?w{~a`ltNU{u%$Af8M{~ zU-U2em;EdLRsXtw!@ud@@^AZh{JZ`=|AGI|f8;;*pZHJxXZ{QSrT?G*%75*@@!$II z{15&||C9gO|Kfl3zxm(&pZ+iZxBth_ke)F;Q+np~tm)a(v!~}s&zYVpJ$HJZ^nB_0 z(+i{*OfQsPIK4=E(e&c!CDKc#mr5_4UM9V4db#xfGjtDJW9wi5fM?sbZQHhO+qkoB z)JB@x{bt*?ZF{>t&tJ%Clb3~%!blOMC{he5j+8)3A*GQrNLi#DQXZ**R75HxRgkJk zHKaOH1F4DBLh2xOk$Omdqyf?pX@oRJnj+1R=12>qCDICMjkH19A?=Y4NJpd-(i!Q3 zbVa%&J&>MAFQhlp2kDFSLk1uNkwM5{WC$`88HNl;Mk1q-(a0ENEHVxmk4!)&B9oCR z$W&w+G98(L%tU4(bC9{nf5<#!KC%E=h%7>uAWM;D$Z})_vJzQ^tVY%%>yY)x24o|$ z3E7NnLAD{=ksZiR1VK;)LvVydD1=5Bghe=nM+8JdWCS1z0udF_5FIfP3$YOgaS;#k zkpKyi1Y{So8`*>GMfM^4kpsvf?iJktfJgJ@OIxgnUN6 zAYYMh$amxi@(cNm{6YRA|ByszVl)Yw6itq%KvSZr(9~!eG%cDA&46Y^GohK$ENE6V z8=4)>f#yPUqj}K0Xg)MQS^zDG7DkJpMbTntakKS+5~NiHba}EEzwqJYqSm87Hx;NM?0V$(avZW zv@6;T?T+?9d!oJ2-e_O6AKD)sfDS|lp@Y#O=rD9RIszSujzUMHW6-haICKI!5uJoi zMyH@t(P`*(bOt&LosG^x=c50i^U(R|0(2p|7+r!cMVFz=(G}=QbQQV=U5l7L=q2tUOi$tB6&? zDq~f!s#rCw238ZRh1JIDV0E#2SbeMk)(C5iHNl!<&9LTJ3#=v93TuP4#oA%*u?|>A ztP|E5>w(ueH`WL1i}l0$V*{~4*kEi3HWV9%4aY`cBeBug7;G#y4jYe6 zz$Rjou*ujIY#KHln}N;5W?{3jIoMq6KWsj>09%MH!WLsou%*~CY&o_9TZOI0)?jO~ zb=Z1r1GW*{gl)mLV%xCo*bZzbhF~a$VK_!&6h>nV#$p`CV*(~(G6paOgP4kGn2s5k ziCLI~xtNFfSb&9Cge73Ru-({RY#+8CJAfU;4q=C}BiK>wICcU%iJihuV`s3l*g5Px zb^*JDUB<3pSFvl@b?gRq6T5}o#_nSGu>05p>>>6DdyGB7o?_3i7uZYe74{l?gT2Mx zVehdI*hlO$_67TjeZ#(EKd_(JFYGt=7yE}L!V}|3@T7P$JUN~MPl>0-)8J|Gba;9^ z1D+AjglERH;Mwr(cn&-#o(s>7=fU&h`SARBLA(%N7%zes#f#y^@e+7RycAvrFN>GM z%i|UBig+cwGF}C*idV;L;5G4Dcx}86UKg*2*T);+jqt{J6TB(j3~!FNz+2+2@YZ-6 zydB;i?|^s2JK>%2E_heG8{Qr7iTA>L<9+bHct5;9J^&wx55kAwL-AqwaC`(l5+8++ z#>e1e@$vWsd?G#xpNvnzr{dG_>G%wM7CsxFgU`kP!{_1i@dfxod=b7FUy3iom*Xq& zmG~-rHNFO4i?72s;2ZHx_-1?yz7^kwZ^w7wJ8=}pa2zLa5~pw)XK)tha2^+N372tz zD>%eeT*GzTz%AUy9o)q|+{Xhv#3MWb--YkN_u~8T{rCa=Abtoxj32>|;>Yn5_(}W} zei}c6pT*DN=kW{pMf@^;1;2`4!>{8v@SFH8{5F0Ezl-0;AK(x1NBCp>3H}s+hCjz& z;IHu4_#6B!{tkbSf51QDpYYH47yKLk9shy<#DC$x@jv)q{2!i(NK7Opk`c*?6hulQ z6_J`qL!>3r5$TDHL?$9Lk%h=gWFxW@If$G@E+RLPm&ixtCkhY+i9$qSq6krxC`J?~ zN)n}r(nJ}eEK!aqPgEc(5|xO`L{*|1QJttk)Ff&VwTU`JU7{XQpJ+%lA{rA-h^9m{ zqB+rmXi2mpS`%%Fc0_xk1JRM_M06&)5M7CGM0cVm(TnI!^db5Z{fPd=0Ae69h!{)^ zC592hi4nv|ViYl&7(nTAYDrX$mn8OV%eCNeXbmCQzFCv%WF$y{V^G7p)T%tz)Y z3y_7#!ekM$C|QgwPL?1`lBLMfWErw7S)QywRwOHtmB}h(Rk9jcovcCDBx{j%$hu@b zvOd{>Y)CdD8kmUx24Y`(FN3JI~kQ>QO5-KT`j7mQoJ?CRK~7P1T|5QuV0%R0FCZ)tG8RHKm$S&8ZetOR5#snrcI}rP@*LsSZ>p zsx#Gv>PmH^x>G%VWYH`RygOZB4$Py?w!)L?1|HIy1g4W~v>BdJl;Xle{KmKsk@ zpe9n2sL9k6YAQ92noiB2W>T}L+0-2BKWZK|pISgIq!v+&sU_4>Y8kbhT0yO(R#9uH zwbVLlJ+*<_NNu7vQ(LI5)HZ56wS(G8p%g~p6hV;`MbQ*Pu@pz~lt77;L;*^nAf-|o zrBeoFQWj-X4&_oFMnJUx=%fz9#W5}$J7(*8TFicLA|72 zQLm{t)LZHu^`81beWX57pQ*3ZH|jg}gZfGRqJC37xsNOz(;(_QGUbT_&?-IMM`_on;Med&I5e|i8tkRC)2 zriai&>0$J6dL%uH9!-y-$I|2I@$>|GB0Y(oOi!Vw($nba^bC3yJ)53G&!zvP=h5@& z1@uCC5xtmRLNBG4(aY%-^eTEay@p;(ucOz~8|aPnCVDfyh2BbUqqoyLXoN;-jK*n# zCTWVMX@+KLj^=5B7HNr=X@!QgN^7)E8?;GVv`ss-OMA3W2XshBbOODb-b3%D_tE?5 z1N1@q5Pg_FLLa4%(Z}f%^hx>@eTF_upQF#y7wC)hCHgXbg}zE(qp#C9=$rH{`Zj%+ zzDM7uAJ7l!NAzR*3H_9QMn9)t&@bs%^lSPJ{g!@Df1p3opXkr@7y2vxjs8ympnuZ8 z=->1o`Y-*DPQ)Z(k}}Dd++lZna9WM#53*_j+nP9_(V zo5{oEW%4oknF35frVvw@DZ&(GiZdmcl1wS4G*gBt%amivGZmPMOeLl=Q-!I@RAZ_$ zHJDmVZKe)Wm#N3pXBsdKnMO=wrU}!OX~r~XS}-k{R!keFEz^!^&valqGM$*tOc$mr z(~arQ^k8~2y_nuiAEqzUpBcamWCk&VnIX(jW*9S^8NrNXMlqwAG0a$I95bGo$V_4; zGgFwU%rs^?GlQAQ%wlFUbC|izf6P2)KC^&X$Sh`-FiV+b%yMQ0vyxfGtY+3QYngS- zdS(N&k=ev-X0|ZfnC;9CW+#I%D1$LLLog&mF*L(4EWOcg%a{1M`vj#C&GHFkhMP%n#-#^Nab-{9*nw|CmH-GuwgcOd?ZkFwyRco^Zftk92iueF#r9_VuzlHnY=3qDJCGgB z4q=C~!`R{M2zDepiXF|4VaKxL*zxQHb|O28oy<;Qr?S)7>Fi8)7CW1r!_H;@W9PB+ z*#+!Eb`iUnUBWJ9m$A#)73@lO6}y^U!>(i3vm4lr>?U?IyM^7#ZezEzJJ_8p!lEq3 z;w-_EEXC3+!*VRo3arRVtjq#dVIixs8mqGgYqAz=vkvRB9_zCK8?p)PE_OG&huzEW zWB0QM*n{jL_Aq;dJ<1+qkFzJ(lk6$>G<$|U%bsU1uou}&>}B=}dzHP$UT1HxH`!b4 zZT1d(m%Yc{XCJT+*+=YS_6hrxea^mMU$U>**X$eiE&Gmr&wgM(vY*(`>=*Vc`;Gn1 z{$PKyzu4dGANC)ch)c{R;gWL6xa3?4E+v(sJpz^jromBbSNG%w^%Sa@n}- zTn;W5mz&GO<>m5m`MCmIL9P&2m@C2+<%)5|xe{DSt`t|AE5nuL%5mkn3S1?wGFOGG z%2ng4b2Ye{TrI9PSBI<1)#K`O4Y-C}Bd#&mgloz*d-cbB`z-RB-~54lI&W9|v} zlzYy-;9hdCxYyhp?k)F@d(VB~K60P9&)gU8EBB52&i&wia=*CW+#l{Q_m4}&C+3sz zN%`b_3O*&Dicig_;nVWz`1E`RJ|mxr&&+4xv+~*a?0gPBC!dSY&FA6s^7;7ud;z{7 zUx+Wl7v+ob#rYC^Nxl?cnlHnb<;(Hq`3ihRz7k)VufkX5tMS$O8hlN@7GImM!`J2O z@%8xzd?UUw--K_O!hzBAv2@5*=MyYoHxo_sI9 zH{XZv%lG5^^8@)o{9t|vKa?NF59delBl%JMXnqVomLJED=O^$J`APg_ehNR8pT6vj&F|s&^85G${6YQ@f0#eQALWnn$N3ZdN&Xannm@yz<cT2{xkoD|H^;kzwIij(dP04nfzVKBBs3P92u+1% zLUW;o&{Ak6v=-V3ZH0D1d!d8SQRpOe7P<)CgziEQp{LMG=q>aS`U?Go{=xuZpfE@n zEDRBb3d4lq!U$ocFiIFLj1k5PxA{f24SPHN!ToG5w;53gzdr(VW)ry zsDKH$KnSEj3ADfntiTDpAPABm3qVi=D5!!a=z<}bf+g63Be;Sm_(C9rLL?*zyM*1s z9$~MrPuMRU5Dp55gu}uS;izy-I4+zJP70@l)500ytZ+^^FI*5V3YUZ{!d2m#a9y|| z+!SsJw}m^xUE!W^Uw9xq6dnnWg(t#O;hFGUcp^8 z!dKy&@Ll*J{1ko(zlA@-U*VsSNK7my5tE6@#S~&nF_oBFOe3Zh(~0TD3}QwxlbBh| zB4!n{iP^;*Voouam|M&v<`wga`NaZaL9vinSS%tI6^n_*#S&skv6NU^EF+c`%ZcU1 z3SvdEl2}=+B32cviPgm#VlA<@SVycY))VWC4aA0GBeAjAL~JTH6Pt@I#Fkwv(6Nif< z#F64CakMx_94n3!$BPrhN#bO2ia1rACQcV;h%?1m;%sq_I9L2noF~o~7l;eRMdD&{ ziMUi;CN39Oh%3ca;%ae?xK>;zt`|3m8^ulHW^s$SRoo_S7k7v|MMOkJOvFV(Bt=T3 zMMh*rPUJ;F6h%ptMIb676g5#74bc=W(H0%i6+O`x12GgMF+toV?iTlmd&Pa?e(`{K zP&_0a7LSNW#be@e@q~C%JSCnM&xmKmbK-gNf_PE9BwiM;h*!mH;&t(ccvHM3-WKnO zcg1_+eer?#P<$jl7N3Yu#b@Gk@rC$Od?mgX--vI;cj9~TqxebuEPfHcir>WV;t%ns z_)GjP{t^F*|HMR6VkwD~R7xf#mr_V6rBqUCDUFm?N++e4GDsPvOj2ekiXL1 zid0pqCRLYeNHwKeQf;Y@R9C7e)t4Gb4W&j>W2vdsOlmH*kXlNuq}Ea!sjbvbYAEF7=RlO1-4sQXi?W)KBU!4Uh&(gQUUI5NW71Od2kYkVZQsx(cSF3pf;O0%Td(i~~7^q(|OnlCMo7D|hx#nKXKskBU5 zF0GJON~@&R(i&;4v`$(tZICufo21Rs7HO-rP1-K)kakLlgi4r%ON2y9ltfF6#7dmR zOM)ayk_040f|4p}k}esNDOr*&Ig%@Rk}m~PC`D3&v`gA8?UD9M`=tHS0qLN0NIEPX zk&a5oq~p>F>7;Z@IxU@%&PwN`^U?+BqI5~REM1YVO4p?8(hcdRbW6G|-I4A}_oVyM z1L>jkNO~+ik)BG=r03EL>812adM&+?-b(MJ_tFRHqx4DoEPau_O5ddK(hupU^h^3J z{gM7k|D;56VmXPNR8A%*ms7|o?av!;`+)wT=50D4SgXF>T5P7IPOdc+ekVnd+%CqFz@*H`t{GU8eo-Z$u7s`v|#qtt)sk}^HF0YVR z%B$qn@)~)qyiQ&(Z;&_2o8-;%7I~|@P2Mi=kax<6jLMjd%Y;nIluXNv%*ve1%YrP* zk}S(WR%9rvvL@@YA)B%#+p;6OvM2j;Act}!C&;_x-SQrJue?v*FCUN(%7^5`@)7x{ zd`vzrpO8<=r{vS}8TqVyPChSRkT1%YzmQ+bujJSA8~LsLPJS% zul!F=1QLTJASp-&l7kc=B}fHQgESy5NC(n`3?L)O1TupxAS=iQvV$BTC&&eIgFGNF z$OrO+0-zu$1PX&9peQH?ih~lMBq#++gEF8jCas)HJ!Ca48! zgF2uts0ZqU2B0Bm1R8@Tpebkunu8XgC1?d&gEpWoXb0MZ4xl6G1UiE*v96#S=ni^- zo}d@#4f=q-pdaWD27rNJ5Eu-GfT3U*7!F2&kzf=U4aR`6U>q0^CV+`x5||98fT>^_ zm=0!unP3)}4d#Hk;6E@A%m)j=La+!d21~$Funa5*E5J&y3akcez*?{ltOpywMz9HN z23x>ZunlYnJHSqW02E*V2LvDi1!%wk7I1(E0uX@&WB`ByAW(q@bYK7zSilAjaDfMW z5P%RwAOY+GyTKl?7wiN3!2xg(90G^I5pWb71INJ$a1xvXr@t??1J}U~a1-1Dx4|877u*B)!2|FRJOYow6Yvx~1JA(=@DjWNufZGe7Q6%R!3XdW zd;*`r7w{E)1K(pmz)$cC{04u(U+@nkQW7gkl%z^BCApG9NvWh#QY&ecv`RW9y^=x6 zsAN(yD_NARN;W0Cl0(U<MP+6obR+cDBm1W9uWrea*S*5I2)+lS0b;^2WgR)WCq-<8UC|i|n%64UkvQt46 zRKXNnArw-f6k1^vR^b$05fo986j=d^qCiDeG(}g8n5kHbtvHISc#5wCN~lCig0f55 zt?W_uD*KfE$^qq|a!5I>98r!c$CTsB3FV}6N;$2ZQO+vol=I33<)U&)xvX4Kt}54* z>&gw~rgBTUt=v)WD)*H8$^+%0@<@5CJW-x1&y?rN3+1KqN_nlkQQj)=l=sR9<)iXR z`5gPAd{w?F-<2QAPvw{LTlu5>RsJc7U}BgACWXmha+m_9gsEU^m0o-80cM1m zU}l&FW`)^cc9;X^gt=gDmSVC7g9SQS=-)nN@-6V`&YVI5c()`Rt71K1EYf{kGl*c3K{&0!1J61IY^VH?;M zwu9|q2iOsIf}LR(*cEnz-C+;d6ZV3=VISBR_JjT505}j1f`j1@I1~fr5|D%xq#*-Y$Uz3K^r>Ig&y=_07DqT1h@C7UVsd4PInXg`2b(%U|ouSTDXQ{K*IqF>XKXsluUtORsR2Qj>)g|gub(y+cU7@a2SE;MjHR@V* zow{D#pl(z*shia;>Q;4|x?SC&?u;QSs$wdx5-O=uDy=dqt8yx@3aY3|s;mN4QK71; znyRaYYO0oMtB&fbp6aWC8mf_+pzcz4$M&du)qU!I^?-U%J)|C1kElo0W9o7BgnCjv zrJh#LsAtu4>Us5odQrWkURJNDSJi9kb@hgNQ@y3$R_~~HWB1hi>I3zm`bd4OK2e{l z&(!DY3-zV?N`0-qQQxZX)c5KK^`rVp{j7dbzpCHV@9Gcrr}|6%t^QH}s{hnPS|Tm6 zmPAXcCDW2?DYTSYDlN5^MoX)u)6#1hw2WFNEwh$I%c^D5vTHfCoLVj|x0XlCtL4-3 zYX!7|TA^5Bt%z1sE2b6KN@yjuQd()Pj8;}Frt+du!8?CL@PHV4q&^l_Jw9Z-=t*h2e z>#p_CdTPD2-dZ26uhviNuf>aVwSn3oZLl^(8>$V{hHE3Vk=iJ2v^GW?tBupfYZJ7I z+9YkVHbtANP1B}pGqjo7EN!+nN1Lm~qd&EI+I($+woqH7E!LK3OSNU%a&3jSQd_01 z*4AiiwRPHhZG*N^+oWyQwrE?mZQ6EihqhCTi_0`r!!%qYG*Y89T4OX;<1}6qG*Oc@ zSp%A)K~2>(P1g*~)GW=`9L?1{&5s3Is6|?WwoBWs?a}sX`?USq0qvl6NIR?@(T-}z zwByuL0~dOAJ5oK*30N+^>TW7y@Fm*ucTMjtLRnrYI=3O zhF(*zrPtQ$=ymmavHE%gy`kPnZ>%@bo9fN<=6VagrQS+!t+&zJ>h1LQdI!Cu-bwGQ zchS4*-SqCU9(qr`m)=|NqxaSO>HYNq`apekIUS`XYU?zC>TDFVo`@J^BiL zrM^mEt*_D7>g)9N`UZWYzDeJ#Z_&5v+w|@F4t=MN=%|kAxK8MCf?cO{T4!`t=X72d zbWxXdSqHkJLtWK1UDplW)GgiC9o^Nvn6C$Vs7HE&zDwV&@6q?_`}F<#0sWwUNI$F} z(U0oK^yB&o{iJ?MKdqmMoz>6j=k*KvMg5X~S-+xR)vxK-^&9$4{g!@PzoXyP@9FpT z2l_+(QS7n)M1QJ3)1T`v^q2Z8{k8r^f2+UK-|HXrkNPM5v;IZ@s(;hJ>p%3Lv0wUc z{g3`v|EDK15*ta3q((9$xsk$1X{0hz8)=NRMmi(Ck-^AlWQt`rvKU#7Y({n?hmq6B zW#l&U7jPb?BbCWrZLNyZOk#|#{M(r8S{+=#zJF}vDjE*EH#!H z%Z(MrN@JC=+E`<(HP#vHjkqD#*l27rHXB=vt;RNEyRpOAX&?q_UFF za7G*;HUvX7Bttepe2s*LYG{UT7=~$BhHW^8Yj}ol1V$K(j09tsvD?^V>^1fo`;7y} zLF15d*f?SwHI5m_jT6R6CF8Pj#kgu*Gp-vqjGM+St-8UW>4~<90W8;bO)Ocn*H(nSojaSBNHo zFn$`pjNir|cvzpn= z>}C!#rrgYvyfTXEMgWlitD7~Qpvx(W%Y-Tn$TbM1)R%UCnjoH?0 zXSO#xm>tbdX6INJv#Z(7>~8ijdz!t>-ew=Oui4M+Zw@dAnuE;2<`8peY?wLR9AS<$ zN13C|G3HovoH^c{U`{kAnUl>a=2UZt~S@0Yt41$dUJ!h(cENiHn*5t&28p(bBDRpL}I9knYc-qq)C~y z$(XFknY<~OqA8iO3C#F;VM0?iHB&bY(=;v9HXYM7J<~S>Gc+SJ!Q5r;j_on`n)}TC z<^l7ddB{9$9x;!a$IRpA3G<|R$~qI~WL37R#Hw1=tm;+`tEN@Us%_P=>RR=z`c?z0q1DK0Y&D5BwVGMYtrk{GtCiK- zYGbvv+F9+b4pv92lhxVkVs(vmv$|V7te#dctGCt1>TC70`db66fz}{vur$@glGsvfnYG+nVXd@QS*xuz)>>x6aEI%S=<&RA!ybJqFT1?!@9$+~P^v94Oztn1bd z>!x+fx^3OD?ppV(`>_YsL+g?C*m`0;wVqketryly>y`D|dSkt{-o@TqAFPkoC+oBI z#rkS}v%XtDte@5|>$mmC`WyRaC9)ISN$jL{GCR4Q!cJ+YvQyh>?6h_|JAEvJozc!@ zXSTE0S?z3gb~}fi)6Qk*w)5C|?R>HPb^*JfUC1tM7qN@l#q8pC3A?0S$}Vk}iIug> z+2!pDc163AUD>W;SGB9z)$JN~O}my|J66Z8YuB^u+YRi7b|bs7-NbHcH?y1DE$o(d zt5|EhjosF6XScUI*d6Uoc4xbb-PP`9cei`QdfL70-gY0muiek?Zx65s+Jo%D_7Hoh zJuEid9$}BPN7O6*h}qY_HuiLz0zJ~ueR6NYwdORdV51`qrJ)AY;Uo*+S}~y_6~cejo7G- z*|;4SY1*Vs*|g2rtj*cHE!d(h*|H66#g3n)wrXp(ZX3249}wGiY}fW|-wy21j$#S+ zE_=7V$KGr2v-jHv?1T0p`>=h)K58F}9k)-|C+$=AY5R`4mx9r>Y9s90*&%SRzuph=A*^lif_EY zXn(Rl+h6Rj_BZ>x{lorg|FVDEe`0^_e|91#v6I9}>LhcLJ1Lx$PAVt0lg3FKOXs9_ zGB_EXOipGei<8yK=45wrI60kMvD{7`C$E#w$?p_!3Oa?H!cGyVs8h@-?v#j?bV@m; zoia{Yr<_yXso+#}Dmj&%Do)i{HK)2$!>Q@ia%wwuoVrdur@qs`Y3MYHHFlafO`T>= zbEk#V(rM+icG@^?opw(9SO=%0)5+=VbaA>m-JI@D52vTo%jxa(iS>2*IsKgh&Om37 zGuRp840VP%!<`Y%$k-@nv@^yT>x^^8I}@CV&Ln5DGsT(e#LKdr>COyirZdZ#?aXoJ zI{!KIocYcIXQ8tww%A$XEOnMS%bgX@N@ta`+F9eQb=EoYuoh>7v(ee)Y<9LdTb*sr zc4vpP(?J~6iHq$W+#wv&p&Z&_9M<6+-Vq$pk(@Y)?Ept{prbmPqdSIUI+kNQj^oBW z$9DoJbRs9g+2!nZ_BeZ;ea?R8K0*e&O_bH};s+;i?b51fb2Bj>U6#CaNf<~(;^I4_-7&THq5 z^VWIiymvl0ADvIJ&(0U;tMkqI?)-3mI=`IX&L8Km^DmajP3$Idle)>=Zd13J+uUv8wsc#?TDxuBwr)GOz1zX<=yq~DyItI_Zns!>w};!)?dA4%`?!7G zer|tvfIBcY$Q|qsafiCY+~MvBcceSY9qo>B$HvCFtca6K&UFWWMH^er&o7~Or z7I&+=&E4+qaCf?h8#mavn2WoFOS+UxyNt`aoXfj{8z(qj$(3E;DlT+YS95jOa81{8 z_o#c!J?@@}opevRr`5x_m}(I{p0?1|GA00#IYn^QZJd8 z+)Lr5^ip}Ly)<50FP)b@mch&DW%4q6S-h-XHZQxE!^`RAiskn5czL~iUVg8DSI{fu z750jFMPtRh;$8`_q*ux-?UnJ$dgZ+GUWHgiuaZ~UtKwDls(IDD8eUDWmRCDg$E)kr z^XhvIyoO#Qud&y}Yw9(NHTPP0ExlG=Yp;#h)@$dr_d0kTW1YOtUKg*c*Ujth_3(On zy}aIDpIBe7pV!|T;0^Q!d4s(n-cWCtH#|1N8|jVmMtfttvEDduyf?v{=*4SCy~*Ab zZ>l%Vo9@lr;PG&e@JNsHXpiw&kMnp>@Zw~+Cwa05 zJjH{a>S^&K*YHd)zLL*8NUh<7x0 z%scL#@J@QCywlzp@2q#uJ0H8?UGy$_m%S_ARqvX2-Miu4^lrs&dw0CM-aYTW_rQDT zJ@OuVPhwBKXWnz~h4<2X<-PXacyGOT-uu`G@1yt0`|N%3zIxxh@7@pZXY7~v+xz4F z_5OK@{KS3|KdGP0PaaF*r}R_#sr@v5T0fni-p}A?jAimO`&s;~el|b5pTp1T=kjyM z^7wiEe13kvfM3ur#y_I`y2d?{w9C3za_TS-{x=kclbMf#7BM1$NjjO#3y~qr+vm}ea`27 z!595FiSElj@D(5WY8)oheZx2X_#*4uzT>;T=lg!(hkoQI_`71e{XPC(f1khKKj0tq z5BZ1vBeA3YG5@%K!awPs@=yC`{ImYK*m?hgf6>3>U-qx~SN&`L_1F#nrhm)7?cee5 z`uF_%{saGE?2-T2f8sy&pZU-I7ye8CmH#^S#((R-^WXa){Ez-8|Fi!k_SOI9fA@d* zKmA|+Z~u?~*Z&tw6eJFk1WAKrLGmC)kTOUWq>iNt(gx{*^g)IoV~{Dx9At@Q4YCE< zgB(H5AXkt($P?rZ^2PE81%iS>p`dV3Bq$mb3yKFNVkLu8LFu4OP&OzRln*Kd6=Rix z%0ZQ&YEUhx9@GeG2DO6Pu{uHBpk7cvXb?0E8U>AmCb6bLv!Hp|jnXH}+pJFPI-J2o?s5g2lm- zARhD{EDM$gD}t55s$g}nCRiJ+i>(hf1RH}*!RBB~ur=5gY!Bk*`Cw;&1ZaQ-ct8YX zKm~EZd%y&2zy*9D1Y#fsau6q910{fg8fbwY7=amBL43IloWKpdzz>2T45A<**cI%K z?Fse<`-1(!f#6_pC^#G(i5(4&1;>LE!O7rMa5^{>oQ<6e&IcEQi@~Mfa&RTM8eEHA z4{iiEgImGv;7)KixEI`yJqR8KkAla+li+FaEO;Kgh`kJ61+RlQ!Q0?n@ILque2jex zJ_lcdufezAd+;Op8T^X<4*mpxgMUGyFmae9Od2MOB@a`CDZ^A@>M%{1HcS_$k7Wom zhMB_5VU{p!m@Uj6=7{ACbA`FXJYn82Uzk5E5EhIT3JZrt!lGfZuy|M^EE$%Hl@7~< zWy5k|`LIG*F{~6;j#UY(hSkFAVU4h6SSzd@)``^(>xK2h24TanQP?q()SU5Z!5gQqf3P*=y!m;7FaC|r+oEV!FP7bGpQ^RTD^l(NvGn^Hh9nJ~ohW~~0 z!ujEXaACM8wm4i8E)AE3%fl7n%5YU|b+{&68?Fo2ha19;;ifPi0u^ovw}#uo?ct7a zXNZJoh{f=b2+5EN>5vK8FfP0e`A`VOPzvP`gh~j*I6)I?p&p0Y%+Lz$&|A(0ybxXt zFNK%GE8*4fTI_mwBfJ^j3U7yZ!n@(U*!}Q9_%M7FJ`SIRPs3;7^Vo~!LzFSf6lIRG#Ii=&qU=$QC})%_${pp2^2YK-`J)0+!KhGFI4TkqjTMWE zMSygN>nwf7ONiBh-yZ)qS{fNsBTm*RzGSGHH;cX zjiV+})2Lb0Jk}y=8MTU9M{T0EQM;&ptV7f>>J)X3xG&mX(4UL9H!=n+gk+JUoS zL|db6(e`LZY-fZ-XoN+0L_}m1w@^iN#6)bwMSLVgVicEpMREipC4!L}X^|eqLE6ZS ztoS+YL~i6oeiTIU(TSocA=(w~j`l=*qkYl-*n#L^bSOF;9f^)c$D-r06Vb`&RCGEz z6P=CDMdxD|qKna`=yG%=x*A=JuE%afH=|q8?dVQ)H@X+yk3EPUMvtP$(Ua(D^elQF zdl9{iUPZ5?H__YZUGzToA^I47iatkQqOZ}n=zHu(^fUSu{f_=bf1`gYN+qP}nw(X>8+N5pD?F_)M} z%qOr0m{>?GA{G-%h^53bVmYiJRuZd-)x;WNEwPSR4;zS$#3o`hv4z-5Y$LYA4q_*< zi`Y%5@hx$R_>MpmIB}XdL;OJeNc=>c zC4MGQ8YX@vej|P-{vgf~=LsCNi9d0r8M{L_8**5KrM5@tk-;yd+)`uZcIrTX;vjCq57#iBH65;tQlAQb2${&tWEL_jnT^a&=75}JE;2Wnhs;anBlD95pdeX@EKC+5i;~62;!uJt zNtPl@lV!-VWI3`tR3IyomB`9u6|yQ>4XTqh$eLs=vNlnq){8IFcs?QY0l( zCKXUgjnqklG)arJ!69AJBO|0w24o0PGDgP91eqjL$P45}xI|tiuaH;CYvgtE2HYfX zk+;b^} znoG^2=2Hu(g|LWPOf8|7Qp>32)CyQht)f;_YpAuL=%2(p)ODtsY}#lxI$f}u2I*i8`Mqe z7Tl)pPJ9aldI#^R57bBM6ZM(;LZzZp z(W&V)bXqzcou1A>XM{|2W;zRH!u&_$sb zU7RjKm!wP4rRg$ImM%w^rz_AE=}L5Es6tnztI^fz8gxylMc1b5&~@p0bbY!3G^88R zjp-(IQ@R=599qyV=~i@Wx((eH+R^Rl4s=Jl6Wy8a0$u5Dba%Q3-IMM`_l7=nU%DUN zpB_LDghBLRdI&v~9!3wRN5DvW6g`?ALyx7$(c@tPJ&~S7Po}5PQ(+oCot{C@q-W8y z={Yc$o=4B87tjmoMf74=LNBG4(aY%-^h#Joucp_~Yw30LdK&A7=#BIydNaL+-by1N zpWaUIpm)-{=-u=ldN1sw_tOXHgY+T#FdU(e(#PoI^a=VK8e_-wxAZCcJNkS2G<}9f z^E>?`{S$qb{+a%TMtckW8~r=|2Yrq{Pyg@hl_32W{Wtv&KBoVp@s~!EG)2=iL$fqT z^B~Y7EzvTq&?;!OP8+mITeM9(;L;u)p?x}_Lx|EbI!-6(B%MNEfQ$4c`Z9fmzDi$% z>+}u!CVh*(P2ZvK!ae#v{eXT*KcXMQ6Z$FrjDAkPpkLCj;5Ge*eoMcj-_sx9BmIf~ zOn;$MF{zm}kd{fuq-QcP8JSFwnaRRrWwJ5ZnH-Rl$;ISm@-TUsd`x~Qz!YQ(F@>2T zOi?Jt6lY2>C7Du8X{HR6Wy&$-nF>rrrV>A5^ zm+8m!hXKq$W)L%&8Nv*OVa#x51T&Hu#f*kA%vfd|GoG2iOk^g(WM&F8m6^s&XJ)`m zW)?G>nZwLw=D~bs0ke=<#4Ki(FiT+>vz%GMtYlU(t6>eZmRZNFXErb!VH2~N*}`mP zwlUio#Kto_nO)3oW)HKM*$4ZX1I$6@5ObJ8gcEa=ImR4kPB7mv7-DC>Wlk~QG2b(% znKKMVDVQIbpO~}E&&)3jnx&ZEnBSQ{m~+f|2Biq*Pv$S?Z{{E7Uj`r848f2L#n24H zunY%0BQPQ(F*2io%4m$v7>vnSU^5QmG9D9Qd=WcbL1(J?1|103I@rn8(Z$<|*?Go-;3)m&_~XHS-4EGVhr8%m?Np z^9epPUzk*EYBmjr53Y;1Nm2b+`41-aQgY+g1Wo1ZNJ1=&Ju zVYUcclr09u*%E9?wiH{MEdyoQa%_3F0$Y)-1eMt;Y*n@zTb-=|HQ8EhZMF_um#qi& z*#>Mwwh`NyZNfH%W^8k|1>2Hs#kPhvY+JS++n(*fc7#rBXSNI5mF>oMhaPNCwinx* z?Zftker$hs06UN!#14ib>`-|AypJD**^E`&wwVs;6;lwHOyhZXEfb``stUBj-0b?ka}1G|yk#BPQy z>{fOgyPe&^?u1?JZgvm5m)*zihXd?E_7HoRJ;EM^W9)JE1p5tplEu(Gdy4&z{hmF| zo?$VZ!~V$r#GYk;W`AMPe#idC{?7ivo@39mC~mQTvVXCEv;VOFvN*D_1U{H3mS!3D z>yKl3R$xU|Vr5WRmDO0CHCPiY)@B{nWj!_mJ{zzh8)ai`91?7jO<^yv7uid2nZ3eZ zWv{W<*&A?^y~W;U@342_9($jCz&>Ojv5(;i`;>jgK4)LBFX0vYntj8*W#6&y;RE}T z{ltD|zp$yeR9tE<4VRWn$ED{oKt?VTmzm4LW#zI#b}k2(lgq{B=JG&ZE+3blE5H?m zLR?|42v?LV#ubMWTuH7JSDGusm4$L#d9DIik*ma2hALcDt{PXJtHITTT3l_e4p*0} z$JK`hTtluA*O+Ui`|OPF!cM3)hwF2Hm+HTu-hS z*PH7DeYt*Ie{KLbkQ)Spxgp$8ZWuQlMsOp!QQT;53^x|WapSoO+(d2?HyNgIQ@Lr} zbZ!PW6J~LMciU;2`uH7am%?C+)55hgSpk*8g4DOj$6-dfQ{TH zZZo%q!?GT38@HX?!R_RBafp29_HcW-ecXQT0Cx}$afi7h+)?frhv*~j1osVhlKYlB z1>bSsbEmm8+z%Yaez>2wv)s?zFWj#jTKKr%xj(pb+?Z37p7DoD2%5avG;|2AG`1*_^|l+%>q)-QaF=x47Hf9k|QglY7yeiNHy&+({2yrOIge8OpFGND{NMaP z{J;EvJgx|MlBe)F#qcbT2aD%dw zPw+`Tg}(q7`Ahs|{tAB;uJPCT8~jcF7JnP=@OSxp{C)lbJmeqokNGG3Q~nt|=U?zI z`B(gFc*DQt-|_GH5Ac!y#DC_$@Tr8=uaHm3FBE`+LLs5BP(&yy6ocYI38AD=N+=Cwgt9_8p}bH*s0fvW%0d;Ps!$E8 z3pIqALM@>-)Dh|m^@RFD1EC=_5*iClgr-6>XfCu6S_-X%*3d?1E3^~Z3mt@x&`Ibl zbP>7=-JrYBL+B~=5_&@)p|8+S=r0Tq2Erg=urNdzDhz|+!U$ocFiIE=V}!B7IAOdn zL6``Wgvr7bVX80#b z0z&wN8aCsn50xd8CD{ulHEW6^Q@91Ug*(Ds;hu0G9taPGN5W&_ ziSQJj3D1QW!b{;5ycXUFZ-sZld-x!H6g~-`g)fjwOf9An(~9XJy_iAFC}t8fi&-G6 zm`%(s<`8p2E-|;5N6ahcgZyFvv7lH;EDS}&qGBH7FE$VxijAPL*hFk9HWQme3$dlxN^C8*fwp2h zvAx(q>?n4E&SDp_tJqEK4n4%4VlT0`*a!NG{lxy_0C6A;5(kSz#G&Fa7%q+wM~b7w z(J)3FD~=P#ixXg?I7yr=P7$Yy(_p$dL!2qj5@*94ajrN|oG&hbh2kP{vA9HB3d_Xh z;tFx4xC&N_Ys9tUIuXl%#SP*{ag(@N+yYz0ZQ^!uhlsU8;x2KwxJTS8BK%+6FCGvN ziibo*>550hqvA2~xOf7-5l@QWil@ZyM8sT(r^Pek58{s^#v#SC;?LqQ;;$lF+r{6- zKg4t5c@d37;-BJQ;@{#wB1$Xbf4JZzajk&EpvZ`<$cem&2TT-2Nt8uJR6!GU(GX41 z0$X%MSM1~*EESQ8O2weKR6;5#m6A$B8L6yP zPAV@|fQnKjsj^f>stVPl>QW7{rfjikm>6KE#5)J^Ix^?;sIFR8cGN9qgxr2f(XX`nO+21`Svq0%sE zIE;`+N~5IF(ij*kjg!Vp6QqeSNt!H8k)}%1V7fFznkmhK+0qw)%cSMf3TY**l2%J=q_xsISTAjmHcFeM&9Fts`k=`(zhQpu_1G;&%< zC#RP)$QdD%oLSByXO*)-b~%TfQ_dylhCFg!IiH+gE&v7PLULibh+Gtk$;IUoa!Dv9 zmzK-OW#w{EUalZllq<=Vp^98pt|nKPYd}r8mRwt|BiDs`a(%gh+z=YcjpZhCQ@I&5 zms`j!<@Z@G`$SMCS>Y49yd8GPJLO&S zZg~&vmG{Z}FC)T5J|q7i|0rX) zTRtoQEdL_^Dq~Pb{$2h&O30ZBPUz93(eFTrK`ihNbRCSQje z@=f`cd>ih_cjbHXefa@Alpo2DtV%Y>uH;a1D!CxHl1Is_sSgd5hDsx) zvC;&ZD$SJUN(-eWv{G6tZIrgqPHC@nP&z7|ptI6N>8f;t?n)1(r_xL54SkfpN8Lf;_#=PE>{a$D`;`N5P&uR=R*t|?<(P6@IiVn$Q#q-8tDI84QxFiPoL0^# zKPU*2P<~R*DnBc~C>Rq|ep7x|{!q}!ubfx@r~HX_IkZ11|DY-1KLw>h6m}?u#>E$| zDHKlO@o9#?PDR4QrYNeSfvy;esaRkuj^Zkw5&>Tclu(I6Oo=NAC8?yq1?8f0Nx7_C zfvd_j<+^eMZYsBw+sYl~F5FY@D-V>1@JM;AJW-x1&)~W8LV2mYg4fC$<*o8gc@H0y zkIE}vDJZfGwpPC;Es0GzR zYGEj%7FCO>#nlo}QZ1#HR?9$HwVYaBtpF9(N@``bidq$_snyjQYE7u6)>iANb=7)M zUu~c^R2xBKwTaqPZKgJd7HUhimD(EGsBP7DYJ0TEp_kfQ z?W6XEerkVpfI3he1cTKf>QHqU3|B{}Bh^tbS{pnmS#b z0W;NE>TGom%vI;9^VJ3FLRh3OR+p$tVVSyIU7@aoRqASWjk;D{2kX@h>P8i-(bdiB z7Imw-O~v|Db%(lB-KFk^J?dU{pSoW?00-4W>S6T=99560$JG+RE?=|NT^9QMZKV2 zgiGpW^@@5GuBq468|qE<7Ti|vsCU(Sa9@3(K2#sUWA%ypRDGsChZpKg^_BV>-l%WY zcj|lipng<8sh`y^kV;FfrP0ztIxW4HLCXl4w9Hx-EvuFdvTHfCoLVl(t>w}3YWX0) zRzNGL719br5v{0JOe+p0w31pWtu&O;%4+4b@>&I`s8!M`YgM4CR!ys})qt8>Ev>dz zN2?3rwYOSEP)<$cqwS)Fr2d$&l2|8cJ@Qt7Gm)Os2{t)33j>lyTndM3!MXVJ6j*&w@~L(i$_(sM%| zJ+Gcm&kqIkf_fpnFci^?>c#ZpP(m-Mm(oi^8NIAtPA{)lfQou0y|P{fs_NDB>Us^R zsn^nL>vf>6UQe&DH-LtEBfYWS1e)s2^yYdCy(P5LTkCD~w$M&*uXoTpLMOem-bL>U z-SqBy54|V!(tGQD^uBsO=&ujZ2kL`hus%c|st<$V`UribJ_<(bWAw55I2f-_&?oAX zV6r|%pQ=yOr^5_=ranub4RiFl`aFF;EYKI~i}b~?L|>{e)0e{veWkuiUkz*YwfZ`J zJ#5f7>YMb<`WD!#Z_~HyJ7A~2OW&>UfxY@ZeZPJH4(f;W!}<|8svpyj>nGqF{iOb_ zehR+Rzt>OeXLJN~=|Ac}>1TC>bLhY5zv{o~7_Qa-(9h}Tbqu&*aN#fgZyk-I`oH>r zI-#R!LZ@_EXLJ++bWZ1WLC3LFmvmWIboJ|Fr|Y_*o4N(I?&z-WK}7fUKo23R$Mm?K zfTW(HU(heYCH=B~MZXHy^y~T!{U+SfZ|isTyKqmxuRqWq!Xy2${zQMOKZED`3;m`3 z3SR4P^tbvuc&~rZKkA?0v;IX-Wu%5QMp`4CksdM_8I4RvX2@b>HL@AmA%~IE$Ytb) zJVst4pOGI57zK?&Mqwyo6g7$&#i4{z(kNw=Hp)O*qnuIRr~nm>N=9X)3RE?!8P$y% zP}8Vo)HdosU89~+-)I00jYdXeqX{%MniZ2%r@p2b77t_-&kNQghj?;V~Md8mKn>96~;*)d#K0mGa3x|$I7BF4|7H!%&<(>d!7^;aF1mNHA5WuUBC&Ma?MfQn`% zv$9zQs+!fz>Shh7Y1T4pn{}YBSoW+$^VbTPY{-OTRL!|ZAHGJ8WGv#;6D><a^fNOeY#PlIBLo;f|AZ{kiq?rO2%!}qFxNKfAubS83x_QIAY2Jd{<{k5{ zc@OTJ56p+=BY12+F`t^x;JNw2d}+Rd*XA4ZExa?|n;*=N@X7paelb&7sjSpi8Y``p z4$@m0tc+GB$ZTb?vRc_7yOqPrY2||4Rvs%aRSzI_}1u2wgzJM^%6TD`2^(8ua)^@IM_0BfK%2nJh2 ztfAI07;cTQMp~m_v^B;WYmI~P)&y%JOtL0hQ>>{l&6;k_ux7$6YqmAVnhW!+`PKq! zAuO^MTT5W6wai*>t$>x*Dr>d12G&~Zto7Cg*l2CCHd|X@tF_JA4m+%!)-DSRvaLPV zUTdF)<)YRB>!5YWIt)jwqt-DCi(;%3);HEk_|`gQeP<#3*g9>Uv3{@+l4$*8okcVj z0+Fm=t>3;zTUdWs=dAM<228C#t-q|lEevW{|62c9goUO>i?V1mb)W=qaTaf(?0{=w z$&$YwZ7tQ(@b`;Xpk-Q?Wm^uomS;u4w*o7)q7bvDd`igm%dXkCKK))nh2T(hoQ zH>{g*%ermdvF^e>>%R5CdI*oK$JP^gYCW@_TQA_H^~!o}y@9vZJL|pm0X|xvtk3Yp zN@b_E(?D7~ot@s!02%E}c4j*ZWVN%|*&&CW)6Qk*hCFs&JD;5&3fKkhLUv&&Vi&cG zL2!Fqdxz0uwTo9!+3R@i25w|Ce( zVVAwz-ed2DefEC)035Ur*@x{TaMV6#AGc4yH}**z>l5r#_ILL8HX@nrGxiVmk2b;@ z?X&jJHiEhAUlF|X`_~{1`<#6qW6l^~wg0mJw*Rp)_+kHN6JJ|rZOW!?#zrfM&Dp$- z@|P{*;#jtE#bT>Ce&{y-ifz-j?5{t^c5Tm&fNuwOXh$Jt$L$0p?G*cheGx9%m+dR| zRk&tfw{O5r`<8v%z5{pdd-i?%0X(!H*^l9g{nUPDKZh6gOZ%1m8s6A%?RW6r{$PK! zKf!1Fi=E0z4QZUTPC7{MWNs-XDGwE#icTe{>{M~8I@O@MQ^Tq0)PmYh9j7kTbLu+{ zoQBZIY3wv{nnE+Dxzhq#I<1`6P8(?Jv~$`+2dAUc$>|JToUTqcr#tj;dOE$Jx6{Yz z>-2;E&H!g13~~lLL!6;7%o*;Ca7Mx?XS6d0#yaDi@y-O8=uC1Z!xU$#GtHR}Gn|>u zEN3>%appSnV7{}!S?DZ+#m*9EDJ*lAJ1d-(u*zBOtZ~-DI%mDJ0X90DoXyS_*y?O^ zw!;o*r?bo14SSru4%TEl`<(;MK?iF?ox{!%=ct2qXwGrxgo9N)&PnH6=ahr+f9HGW zv~vc2aDH@ta?Zlf&M(fd4kDnO-<>}k1a~;+o&Pz1Iv9_2{>JDUMv$EU9Ks(`2wlj)NUF#Eu?ePyBQ#(o5{`WW`V43Ha9!uaC5r3+}x1I&FkiK z^Fsl*pj!wEyG7ihZZRnCmT*f#DYvv+#w`ow-12S(sOVO5E4x*os$0#i4mI4GZY{Sq z)N$*&^`O4nz-{O@g2rwWw<$Dpo4YOCme9&=?Y4ooZacTV+W|Vdo!rjQ#qH{LbGt(i zx2M|+db@qxzHUG0?+$PW!XS6BJH#Cd!`$KS2pH*(a!0#kV5~dN9S;-SiS8tKGE8x& zy3=5~JHwsn&Vt$Q9Ct3vbLYDY+=Z~nUFd2(NU1aesAxa}hq}{^6c;5fkG6&;8T=>+A5X`;Yst`yUW4 z=~6BYjLW)cXmWX1a77m-HIzD(ua|+Y=IUP$DX!^SuI=J+bY0hTBjCG%8@f@5xp6lE zNjJs4;9i7F?q&B1Ty?Lx*WDX%)4k>1hCA+E_nvzn9=H$PNATEv;y!hs!E^V8`x0Kc zuiZEB)_v!`cR#>K_mle>zPPEp)Lt4$>!tJ3Lk2ISm&wZvS-h-XHpuSf@N#;&Ah(yt z%M1Cu{9XYl=oRt`dqtqASIjF8CA^YeDX%n?@ydGTpuAVXtLRmN%3c+(Dpd2Tdo`e@ zSIevI)q%QRJ+D4A@EUrJyvESPYw9(F=3Wc0rPm5tdu_b7(9Ub`b%2guC$F>D1-g3O zyzbD$>*@9KdP5(tuh$Rydjq_I-XIw44e^G;FmJdw0!DhHywTnm80(Gm#=`_}qBqH# z3{$+R-ZYr*&G2TzEN`|q$D0fDy!qY&Sm-VC7JExzskh8q4lBHs-YRc3tnt=*>tMaN z!P^L%yv^PgZ!2u`wtG8Zr?<=7?d^fR-aZd2>AeHqK@Y2yy~ExS@2H1`k=}9dgokBi z-bwFU?-YFJeea$2&cF}ej~=3xy|doW-Y*`46TRQO-#x@$A=>D?hfoslPXq}3?P0jr z`xm2j7?$!#kMd{_t-v0O7Q3&lP=Y6VDD!%b>H0V+l+ zMJhv;NYzNSNOhR$k!F$R&?3?@(kjv#+CRr z(#W#Na##^r8CeCZBWogSVO?Z>WJ6>lY>I4-Y=NzjZISJ;BeFBH3wB5LMD|AZ!T!jB z$U!(1IUG3xM6X9Tepk-sDVL@+!Y`7c65Fo1>889KszZ6}U!5k7(zDzvyr5fp_{ zs8ex;jjP0n5i!3WW$+Q{e0@xd-V1F<(Gysep$a9l=mz675z$3*{|YPg=&6vzXsIwYx%XIj$hZW=hue@ zenYc=4NC;X(J z0vG&?{w28VU-7TPHUGMQ18(}a{M-H=xa;5Z@52NCq5lXT`%nC*@XUYizkrwiEC03s z2HyJb{P*y||LA{$&;A!bRgfx39i$1;Lb@P*kRiwjnS#tg7RVZ83$jCwAZL&ZatC>W zypS)*9~6LsL7|{{mpe&RN$_EvoVo)il98`g-LA9Vd)Cg(@ zwV-xTC#Vbcg8D%NXc#mK8V60FY0xZa4lRO~K`Uq-v!I2;@ajt0lzcyJ=X^3mXA@NIAk zz6-t&un;LY6Z{ZhRYvera5g|ddhko|Yw%lufYacQ;9PJX{ulfiAP^|{JNPF+>_YHg zKm-_F4XA()FxnHa0T-aPIS>LdkOH*R1PThyDEiyg34i) zuxeNhs)segnoujO9oB)mVZE?EGzc4pji7PZBy0-J!scNMXc@K&TSJ?$ZP*UlhaJL> zVJGMub_u&ex3GKI1A2zN!rss)>>Kuj{^5XdAPfozheKd!I4m3vBf^p4C>R}%3CF^? zaC|r+oCuS`$>9{38cqwR!;EldI16TnbHceWFPtAPfQ8|ra4{?imxjw=dAK5639G`@ z;Tl*Qt_#=0hHzuJ2{wmY!mZ&p*dFc(cfzi4cen@khWo<(a3DMw9)iQ+kq}Gg!(-v` z@C1Alo(!>IGdvZ37k&??!!sck`Gh})KZR%E=kS*hQQhHh;qM`$R>O1Q`S5>V%$N63dli1yV`LcvX5hEa%x@h}0&FeSVI7sE^8Ww;Vv4X?rV@J4tuyal(zJK~QEIK?o0!BthMMuM!=-B8u7$2Pwod}bnlcQ5$YIIt3I?Ra9jLw4D(K*q%FfTek zx&RhN7eyDtlIYUtGFTp65nTzZqN}58U~P0=bUkc{Zj5e%&CxBC>*yPJ z8+{jj4H7GWq^#aOtH+6C6+ao4YJ2_#BxHeSngOJ z$Q#QS%MS%&1!IMvaI8qIC=`nokClLuu~MFh`8XE?~VNiaD!B{mhN#iqw*z|7dJ*ld^+n;V-4^J5EQ3t>@gacl`JjV+5UhZV7vu~o1- zwkEa~*2UJxHo(T%CfFR?659&fV%uXoU}tPsY&YzQ?Tzh&{jmeFgK#KzICcb%#*W2~ z!-?28F|4GHeH%L!!-~_`_p#HlGw?&~#~9Wl#m>flj$su>?AO?DF@&1O{)nB6ornL$ z{){06F!py05nZu=WBjSt>{jeH+=<G&@wAXG zo<5!dGR8B-Geeem)_69^9?uca3Ay6A<9Q%&JYPIN6o?m$7lOj^BJrY7EM7cb0!qe9 z#Y;n(cv&bHFCVV}72}oSm7z+!YP=d$kJpITgj(_1@j6g9UN2rB8pIpM8$siElXz2T z7H=MJ0WIULpmn@Wye+hgw~u##j`2?M&d?>^HQo)n$9u$kLa%u5cpvB+?-%b61L6bY zgJ5ucNPH*^iw}np@saUSFgiXaJ{HEs$HynY#Q3E6WSA148lMK!<1^wjVOD&0d=AWw z&x_B81@VQjD84wp1eV5^#h1g1_{#VySRG#zUkmHv>*E_>V|-J5Gi-@(jcY1c{G0em_%?nj{vCWDKMiN%Kg6-jE&fyd zY#b|m;=jayjsFI}$Nz|*gY)tK#s7rA;(vdQlS44re{sa~#L2Ir7;!qzd>s{zb8$Y7 z@u;|n!2&dV#}%|hqCG9Hqg4Z?^0*bZj!E)j7Q^mT;uV0BA$el z_=WgIxD>w}zXDg|*W%aVM*Jq+ir<@=pTe{F^Y{yR8GjXj z4R7La;a&WF`~!T9e~N#GFY#1~)Q~2THjxg}Co&{5LZ(FKL>9=J$OhRHITAS`S0Z;J z59CecOXP_(=(Fr;y zx+J41~QdpK)o>&1Z6RQ%d zVNGH!tV^sONz?gyZJ+QGkpzzXi2xt0_!PhkHlFzUOD0m_LgHfL5?oGPNnC|%iR+0Qa5Hf$ zaU1R=?!vvq{lo)!n0S??^3Q#dwDOnk+B&$NTWc6eXsF|#ltPORNb(8g=ezE~HOg2h3 zh9=3T$!5?z*&^8zS|wX2+d$i7J7}Nmkn9MZlAV)Xplh;QvODxh_Jm%^-pM}DH`y=Q z9|j}`CI`XbwC$Stkc_#To z5=%3aKPAs5u_`V3OA;%QlD{Q?PhzD-@?7$K5`p8%Ka+nY|Av2(2w+V9hZr=3SS6_> zokYk-l1*|+3^^x-B*p}jQc_N0*deJVwItdxlSb0~+76PmlPG>BU0m&dy+rksK@!I- ze8A$f0594^^6M`pc_DcbE+sD~ufWyhHMpL@;*FBK7>cf$H^z~ zH2Ey~99|?}CSSqpAU2rC>@SD4bFxr6?3jDV|aSN~V;8(kW$9%0ju6 z@+lRdVoD{boKhvFDpX6Uo>Bv9rqoKQ4RuoLLcNswDGi`uN~4s<&?KcPG)rlo(gIqh zv`T3WZBp8%w1f639iU@Mr36#uVLD-84+s&Ct!nEZuC((aqJ( z!+hNW-9jwVE!Hi;Qr$8v*R9a4#46ot-5RXbt;2fV2Hi$%(rwml!B*WiY}f73?ZhtK zZrvX2)$P;m#{u0z9MT=u9l=rEG2L;T(4E97-Dw?by>(}G=X9)l)m_kC#3kKj9m~9Q zS9RBPEMw8#(A~r>-EAGQ*1Eg8dpe>+bq{n8b;Pgf9_xrZ(mmBZ(-Hijd!c)&W58AS zTE|EjV><7&26o*C-AApNPWM?y3yJP`-4`8~Qo65{ShXV7UpijBbpO-+qy60K{?qAn z0`xi&l1|ncpy-S`6U;hQXMt5`gI(v)IpNZ|bsl(iKKOM3T@WE%SQkN57eic^&?V!$ z?uYIte(92gBq4>6Qb>i=LK-10(h2F2LC7d%LS`Y0kQLd4?8qVH6mlWAkVnXid_sN{ z5DE%~P*^A;6h$$iI7$d5g;FRjlo86JoKPMWgo;8XR2HfTRZ&f-jv7Kup%!Wjb%eU8 zC)7s+p`p+SjfEycQ#2ErqlM5?Xoc278=)=Q3GLBA=qPkTXQ7MG72SmH=ppnJdZD+_ zN9c=wLVpYp1`2~PSQsJ<#V}zwMhGK?Q5Y?Z5yoPiFdh?xiNYjI7N!VOF-@3`8Ny6q z7G?``gt?d}%*O&@p|A*xg(X-jEEASvg|Jdsh1J3utQFP?>#;%DC~U%JVGFhj+l1}d zA?y@(VYjdcdxd?%ejE@E3Wso5ID(_XG2u8)2q$q$I4!W0UN|e96IfO(To5kel5kmI z6`gQZxF)blNVp-~#4X{r7RfH$748Z5@j!TpN5W$*s!Mn(Jkvr;2$#?T0A2~N1qK?0 zw*o^y!h7L^)`~2A5rH4_*eK(`|t{apob_( zf((P82u7F$GgQGMSYZ?Ff&)&$1-IZ4yzmKr1caavLRg3hQN)Be5<;@@9Y2Jh!Y?H0 zQy`^2l|D7n=+h#dKD|BzGU_wwGb4*WE3)ac>vJHdK9@c>^62v-pFY3701E00p|HM) zz9@?6i|b3Eq`nkN>&xiNqMW`wD(EZfE1|N!ioPnU>8qoLzNWqwYU}Ih>!O~%J{ss7 z>Kmc4z6qM@o9UaQg}$Y}6bvQ?qldmHdg*)X`=GDB zANuPD=m%nuez1NBhU$l5xPF9wBu42+V~l>RejLW@C+H_)l72F#=%?zZVY+?>X6k3@ zXJd|ju6`co>la|5evy7LmgtvanSQx`1y<@;=~rWoel6DN*XuW6qka=M>$m8)Vw--u zeg}5ycVV}FkA5%q>G$J+{-FL44(pHTkK&mAI8NwK>QCXc{tV9Q&*@pWt-qkZh)ep* zdX_2buj;Squj7XPCT{6(>sczGzpKB8`}zm^hj^ratS4+v|5Q($lm5B>g`NNl{VQ$s zTmMG?RvTK=zh{txW_tZ6J*}j)@cpi}sn8UlUBCXR|E8xvOX-V(0I|hu#U7-mUk*tM|dL59osk>BETVqxu-)`UH~o-}OK6 zQ~yh!BqoU|#FR)SrWVs6t(Xq!#SCIbWD+wYicB+ zg~Y-rA{Iq4vA9?QCB;%=X_OJmqMTS>tbmGQB~%uxh*eQdtd1IDO|cegi*-;}tS8n- z1F@ml2#v)iXeu@no1=x;60OA6VjHv-+o8SKLF|Z5VrO&_yNcb=UF;$DL@%*7`iOnS ze&{a_z(8@3I2c33p%^9(7e`>EI0~c1G2&Q^6UU1aFj1U@$>J1oDyE6kF+-dw&cbYQ z4(5vU#Q9htF2o{nvA6_F#bsD7t`JvZmAG15gSFy1tQR+k8?i~;j4k3;aT~UaJFrvS zCGN%^aWD3X`^5t|C?3LL@rZa7$He2}37iy9;k0;0Jd1N8OXI~0;zjWiE{m)L6|aid zL{_?qH^iH`CEgZUjUnC@39c9Kix0$ycqBf?6Y;4QNhUrQ3HcFUimyZh3>YkbD>8a1 zz84us5AvQ$N?ifU4I)R1aQwNP8CgSt{ZsXiJ=4bezyEHyz> zshQLqEu@xcCAF5?psmyn?WGPW1!85A>9JNxjiW>Wh9-e`x>)N`o+1 z8X^tFFljhONF$|D7%h##SZSOz9uuUAm?TY>reLZx4b!C=(oD>fW@C;tSDJ_U(gG}$ z7DiL7o!BMqmiAz;v=95G z1JXepk`CjDbQH&=a9Luxt#nnoCSAu3=_YPTx1~F{ zE3tw>x-UJD9^#Rt1<5{s`ke3pKbe#aMy zCN1f!^rv>E%Y_+N5fpg;ktjb%|4AH&C4rw~KFE?J$uLNYWQ0jFLzOI&6*kEZhvbAy za!Vd~B_I4!Knfxxg%OdWQVek^fn@2s^aDSoUr3Tu$SILZPK`8jS~(rk%NdYS&Ln3> z7C9@j$=T%`$SLPSZaI&f7y0D;C?FR^A-S+z1V!ayC@zdE!dKyHXea$~s(n##@4Ty7z^L@T*9+Q@C?c4#km zKu5Wg+!&%jK17G}$HFjt-@&&L9JAr{Gt~HOo4g%6wzQ!-XtrA7)vW4YC5GY=T)Vu@W@{HWWO9hP!1t1N8~7C zavTXcS^kb6@=yGdlMG3Q6o!p^cCZsE4!4(_rbheao>t6<@P;gNxuZ^IMA zQ^PYn*TMt|e0yaeD#`H1K&S{I4-DKh`25kp@E?O$+Q`fAhA#$MXlYi|TEe~=XlO9} z&Gj1>8kC1Av{9}Q3>=p^Xz=rFkPX_S7>ouJ%mx(}gB3P|-Qa-J;DX!WF?iuK_z^G! z5i*1g5kw6!#0?2UGQJyr;HTjil9UumN~BU!BaM<4>6G+J24qw+A+wT2$%<@BcH~fU zBA1d|$%DK~KIB&lprBGnDU2dYQ4~{(D7sN+ zH>EpzC_T|j>8D&v&#n4nC=BxN$D zC{vYbn6AvgOl6ib8*`Mon5WFg0%f7H2#b{^SgI_;a%F|G604NeSfi}PI%U1G0UMP~ z*sN?(wql#I9Xphr*rn`N_F%8F5Brq^IH(*_4&#V&6vvd~IH8&gw>RBqw6az|lRm~u~HeUI`$TcV*nQXXr8OUSYHVg~juyt2Xk~1THpaHbc4%+xfR4sa=xpp_?22y2?&x9c ziC)Iu#y;q4?1%ow0T^f;WE_kk#-SKy9F7sjkr-thZ5)HK#&H;LoPdeONyf>TVw{R; z#_5=0oN1he*~U4TYn+Gq#s$WOSY%v`CB~&#W?XJuftAKpSZ!Q`wZ?VE_1IwCh)u@L z*kasj+=lJO9oT8yh26$I#=Y2Q+>ZmsgE(Y7j3dUQ#$z~cJb{zOQ#frrV?2v<#`Cyf zyogK2%f>6XYP^Q)#v8b4yk%s)rtyxE6?n#bMix^U9~fB=V|-+MY%J@!;M=^ddel#+wZTxIxK+*WSkueoaO*xH1NUJ9+S}w)o6j$XfxX3FgoEfy5TW;jXwB|0R)X9gpCnI zjWJ^!31c$88-L)Z@s}~llw?X_N{Lja)JS7Wi*%;+rVPkv%7o0OEXZogX3CBnrku!S z%8fjxyrz7}Zz_O-ra~xeDuSY>Vy5CKVJeAIrqU>5Dr+i-@}>%?XsU$DrYfjvs%EN= z8m5}4WvY!jrn;tjsBdb3hNebnY-(a^ie{$fXkluJR;JcyV`^(^hxVop=xFMM&ZaJ= zuIOg!jvl6-=w<4SKBm5=e&}x+fPtn#7;GAXp{8M`;TT~WiBYD}7-Jf18i(drg@leT7ZS7MObWFf~BTqrsY^+T8UMr)mUR%Yg&i( zrVZF=+Jw!fE!b+>X4;M&rk&Vj+KoM?z1U~kZ#sa3rb9SvI)bC7W2WOcVLFLZrqeiM zI*W6r^QH^9Xu5>UrYpE=x`ykf8zvS}n{Js{9&5T|V!@;7p6R~n0Um0LYfO(#Pw-R= zVmCcEy)eDRD-*GerZ*-n;_RJ?@E_9$f-ndoFn!jBZy9;ihRpuZ1}A7w|7N1O()731 z3S|06yQ((*$JGhNXG(VzJxr2zxaKgz&$h{EGMUU!O%_;9HrP!LI882-8y=GvK9e5- zQxG9j*c3t36hqvUK(gsOewcomej&-60x8X@klLIEY0c@(>5;*l5t+=Hk;R-9+05C^ zIgrzw3%SjCkk_2goF4_u1yRUc7)8uQQOsQ2TmmJ{rBK>j24&6VP~Kd@ToIMbl~Ki9 z71hkuQNvu*Tnn|$bx_w_5B1Fr(9qn-+!#&FP0`HU94*W((aPM~+y-sU?aXvB10#i_DA7OR&_u49m?cu+qE=tIcc7 zYq8F}9vjRXvB|s{Tg+Rr&Ai>b13S&Tu-m)`d(Hc>-+aJ)5Qof%am0KS$IQoZ!hF(v z3a8CyaMpYd=gk*z(R|5#8CT3#am{=kH_SJ2%glOY^BwbD^F7=*KfputBRn=g!Bg`y zGokS27iQv>&9BS^lbYX{iSRSOGr!kDhRh$$L~Rgrz<9a&cQZqj=0D7g2ATh)!JYPD z^WSEg-Dn@88G~zWE(f_@(VHpjQ4Hbr(QMF;CH#DAA91r;g~e<&+h8|4;5577HhbVT z`^%t3_AVMNSP#LRId%*p2O_+kEuU*;q=Nll@qL@G74ng(grbV#peKt?qaGOJmT zRn4YmM-DY7a;dqIN6m|TYJL<@3#x@sSS^C0YB3a7OQ57$N-d2tYFU(1%cFu?5tY=+ zsG?R?tD(AD12xrJsIAsPU9}$Ss}0nKXrwkq6SXOtsm;+sZK<|GYqbs9s_oET?SPJI zCv;Z3s9n)b?T#L5PxMlIqmSAb{nY;I01Q+IVX!&`L)Bpzu8zP+b(A_9W7M%2r;f)2 zbs{FIlQBh|s!qdnbp~dtvoKqogSqNF%vTqv3$aLDj3w$)EK`?bg}M@})Ya-5tX0=x zy}AJ#)lJx}ZoyV{o4Or4)ScL+?#3Qw&9>QVu2#%`9a9llslj*@{MRBz$7w$@p_tKP$X^#LBLkF>>6>Jx22i~3Au^?~|A zeTi4p?^U9-)Q?)6jQUw6L_z&sWmH)GLuCL?{Zsv>waC+aOiQQwk4npt z`k%@bxGJbz!BD)GREi|LQgZlKjVeFme8{!OqFP~7?Qp10)djcefmijxuLclQLkO!8 zHHw%TM?y`;cl8H;s=ttANr9A>RF>38V@Zp2mh{MA$%ssr%*bNNYRQJ|mK?}w$%WjO zJjiRwhy0cTC}=5UDU2ePq9|r5juMuVC}k;)GM2KIawu=9fQpt%sBEc%s+MY~ZmEHq zmRgqDsAH*%dY1ZVU}=a(md0peX^Li+=9U&{X=#PlmNsZ>X@~Zf4(MpOmagb# z>5d+jp6F%ijXsvX=x6DV0hWQ5K^SZqf}xgS7;YJXk(N;yZ5d-3i*c6mm|&TRNtVf& zVws9*mg$&bnTc7J*_Jt&Yng}nmIYX7S%k%wC0J@%hUJzOmX%m#S&cQ8wOD6aj}4ZM z*ksv^Etaj8ZP;$vft{9J*lpQ^y_S90Z#jU2mP3}qIAS@9W0vDMVL6FYmeV+6Ig4|a z^Og&^Xt{*TmMgewxrXbO8@Oq?h1-@pxNEs*VNI;%frVv_mPeMycw%{qXO`!9VIeHv z@`{-0H+X9yAd|2{LgOqSEd(c7K3j+jA)-MWivL3!^tAkG`DS4l#PYY+x@`GJYpt{V zr?q-m1nsKPB3dNK7K4S?Ob+APfyHd$gKn`{tgu<^a9EshS={hgycQq)mH>j55WR))Yu-O@-9fG)QYrhxFDA$Y{-k%+@T{tjK1~jvUsU$Ysrq zJl4F(XU&fS)`BQxEo?10^KErYVwawu=DfQr^i*2<`2t%_>a>ZoC@ ziCWg$sAH{*de-`AU~On^gvQn;XliYS=GGQyX>Enp);4HsZHM;O4%Uw7WbKSD)~@Jg z?T#MSp6F%mjXu`C=x6PZ0oH-mK^SZuf}z%77;YVbk=9WdZ5@NL)^Qkboq&nfN!H1j zVx5X<*6EmGorzi2*_dOUi+R@hSYTafU4+HfC0J@*hUL~3SZQ5_)z&pwYh8!+)(zNb z-DKU2E!M5rX5Ee*)}7d8-Hkogz1U~nj|0|&IAlF+J%XdwV>oU-fs@u#IBh+Hv(|Gs zZ@qwv)=Ri-y<)wJYu4+yVZDi4*4wyay^DL+`*>h|h(}fy0$QI~pW>PIx%CBJT3_L{ z^$p%y-{HOW13p?m;jsM`bl;OO;@VAvleOjMsB&8*f#wJ=rxLD^( zQoG8LtrQ9=s_^P*HCfHtk2)WA?Xg+ya9EviS>5nhz3^H62v~y%S;L4}qt+PW)&!ER z-|@rx6ThrUwj^5$TS}y|rA8WCTBNh3M+RF)TP9?-WkFV3He|QuKu%jOHCuIC4b-&NLTy_e z)V0+^eOm)Gv^7FwTN5<3HA8b-3$(PgLTg(aTU)fVwMPeAM|84vMi*OGbhC9w4_i<4 zvh_wETVM3E^~V6)K-(Y;whh5h+b|5bjlf9TD2%p^!C2ckjJHj|MB5}xwoSoQ+cZqK z&9KeHEZc0%vCYLi+k7mrEyN<*Vl1&O#WLG+tgx-bD%)zTv8}bO!+P5WY_x5{X4@8Q zwQa+8+Yaos?ZR%`9_+R4!+zTV9JC$6VcQYgQ5>@!#|hg>oU)z98QWQ$vz^BU+eKWm zUB(sLRa~=O#|_&}+_K%q9ot>oJ>0iFz(d<3Jl570+Ma4_!EDcMFYwa#3a@Q%@YePY z@3lx!+eh0ce75~&BY?;D#YVh@?W>JpY1=n#)Q=%5MpJD6XpPgh|FlLvo1it4*hCvw zmt6Kx8n-DniY>g3YR7fe#?QOWYO~qwaM+x1+1&8hyztrl2-t!M*}{m}qKMhzNZ69` z-Sz`NZNHFYPl1&7RQA+JV^528_VmbL&xlO+%*bNTifs1m$YIZkT=v|^W6z6x_WUSd zFNi|+!YE=dYA=T3_7W&*FNMZuZ7z7I;d-} zhx+ygXlQR_Z;U4Prf6nwju!ToXk~AWHukn?XK#-V_KxUe?~E??uIOg(jvn@&=wz_JQ_67;GPcq4r@IZXbb>_E8vZAA_;>aTsr(fQj}=m~5YdsrG4@Zl8gf z_F0&1pM$yfd6;isfQ9x&_QhCYUy5b+Pa z?bu=8iCy;H*kj*|efIr0U_XdM_QN=0KZ;}a%gv<6T zxN5(K>-HPCX}^Wr_B*(1zlZzw2Y6_Igva(Lcxr!!=k^zPX@7;+_BVDy=I!t71Tfn_ z*a;)Gf3g$AXaCJk?3DeBT?{#StNm|n9LxTXopAyCe|Fke?Sh^5DZ8j$ z*V|<~7ebWIwGyMr&MPX1ckNJP)jsrgyWIh&-37PZ1Fzj@_ak5rB4iIEVvizbk0W7E z#&`P<{IvfN)D8fukWBIU1vhqbZs>nxloIC0aRJqm832+Bw>zgQFulIXa_@qbs^Ox}%4qCwe)0 zqmQF6`Z@YzfMcLz5C%JjV5nmlhC4=Jq+=9DJH}wFV;sgiCSamt5+*yQV5(ypraNX} zrehXnJLX`nV;<%^7GR-c5f(d^V5wsnmOEBprDGLVJJw*WV;$BzHejP;6E-`xV5?&r zwmWuUr(+j(JN96&V;}ZA4&b2U5Dq(z;Hcvmjyq1^q~nz1G|o8A;+*3=E;ugYlH)S2 zIIiNF<2r6QZsL~XHtsm?;-2F^9ynOM?s(*2;j!b1gTn@=ToESZ_^L%?5I(?Yx8rXIgNTfFF(|{x0gc>l7cg;*9FxFz>KB__^nUuRTtO3vP!8UWX5UM*u-b2w_JA zQAZ4MM*_)?@A%>PiC>N+XOc68GbK_vQzMNtEz&vDBZD&|GC4CNi!&>-IkO{&GbeI6 zb0d#4FY-C_qkyv@3ONg-h_fh)Ig6u&vm{D6OQVdlEXp~{qk^*{Dmg2oinA)JIjcKs zpr*4HYCG$ouCpHMI~$;(vk@9Qo1m$)8Jas=prx}FT07gIt+O54J3FAGvlBWyyP&JH z8@fAtpr^AJdOQ1|ud^TeI|pE(a}WkQhhV647=}AXV5D;tMmxt~taBX3J11bGa}p*y zr(mjc8m2pEV5V~xW;^F#u5%vdI~QP~a}gFhmtd)L8J0U&V5M^vRy)^Vt#cjLJ2zmX za}zdWi*qZsIk#hnb0>B=cVmxpFZMb2hF77$+wASbgJ5mEQfpTuw!@f;deTnF1Vc@c%44@odE=$A%vX~ zM4d6joe3m6zvGAVCw@7TTuH7Ju9QgSN{uwGv`FVlj|{Gi$mGh5EUv7`=E{y7uAIo_ z%8fj(yvXOuj{+#@DulwWA}H!AhT^UgDCsJN(ylTn>nexxt_rB=s)Wj}DyZtJhU%^w zsOhSO+O9gN>#B$Pt_Eo6YJ|qFCTQwvhUTsoXz6N&)~+^a>uQJgt`6wv>V(d&F6ip& zhVHH&=;`W(-mX6A>*|O8t^pY68ic{FAsFf!hT*Oe7>QA?(HP?zi*c^;nBba-Nv_G5 z;+l$SuIZTJnu%Gi*_h*+i+Qg3Sm0WSMXtqI;#!JjuH{(aT8UMz)mYH`*x=fT zO|H$@;@XOBuIuB= zy7M8wy8sHh3!yNIxQn8gyEsa?OQMv!G|ITkqMW-tD!416lDjghxT~U?yEsSfdkxmQ*I~VT12(!hVY7P+ zwz{`ryL$(Ax_4o>dk^-y_hG;L01mnj;jsG%j^dd6I8L}v;*|R|&bZIwocla3xG&<8 z`!cS$ui~2fI&Qdc;+Fe1?zr#bp8Gx?;Gz2w9=o65srwn8yIjmbPY?9;^g?e>AN2L~ zLx0Zz4D<}bV9yW?^$f#sjPQ)aD9>n&@r=bd&v;DmOvEJ5WK8i)#Wc@!%<#;_EYEDr z!CcQg%=aw7LeC;B_AJ3t&oV6ctiVdoDy;Ub!CKEctoLldMr`tI#um?3Z1Zf#4$n^P z^6bVQ&tB~F?8gDmK^*cN#u3j^9K&(X37qtt!fDSLob{Z;dCvu0^jyMa&lOztT*GzG z4cx>n&u!fC+{Hc5eLV0y#3RpRJn=lmGtYCp@Vvw;&uhHFThBYZ_k6%d&nJA=V!#Ps z{(?U|L^yi>^bkZw%oFiBp8t7>dC-RDJ^yJ#oE||NBJzk@)4E6Y&=yGp8ErNmlXgwZ z6&8hkkJUrzM|%n8Wk5R+xjcLkJRXl1K93&(PY@wb7!gktF;5%`Pcpvahvz4Ld6K+I z-W1-HNaan9G~TpG=S`0c-i*lP&5SI_>dl7i-W}`Uk-ezcy7T%U<)nU_-UB%3J%q#FBRGm<-s3poJ&9A^(>UWji*w%dxZu5rOWw=4f~($ZxbD4yo8DWv z?Y)D$-g~(3eSnAFM|g}U-lurxeU2AimO^`9d0*p=_buLe-{XV#BR=7?_c#3R{enNd zU-75+8~*bCjsJQ7!N1xlzL$|^hC&&sV;G4M4w~K#P`pN%yk@9g3n=_k2&E+Cb$EF> z@Vc}=1-=a0Z zfQ-IO$n494tiEi>?#qFk$mPq8Jiffh=gW@*zJe&^D~uw(q9}&qz7i}!IizGi6dYk`)&R%q>O zgSKerYmW}Tj_Bm;j4r;e=;rH=9=@LFh2FkC=oL%zc};ya3C zIPN=vlfF|p?K^|BzH>P5yMT+nOSp_HzN@(AyN(;ao4Dn>jXS=(xQF|`2YBdvgvY)o zc3ap%3$yl>Rh+(eK97;HU2wlKd%<(w_>c{b`WapAPB$ z8ITc~{F#x(pB35s*^$Ga6S@4kkq3GG`HaMgbe*Znte(|-%MamRlb_x$(q!2b}B{EzX( z{}j*g-2VbE{jc!a{|0aU@4)It{|7&7%>1AHtnl&w=GPWheDM>;?*Hm1TG;;$fBCi0 z!T!J&o6x=%et!T#e+XfJ1W|tsaU}f7`0oFKpZ;G+3Zy{FKq{n0nm}5l3#3Pe zKt^N=WJVTb4P-<1Kn~;#i@-~~!t1~ryw%p|2Hpog;A7wuSZ>8qAr`p=z64lP5cnD(bUW}ZKxAxyFis*4 z35^T<8_>dxbO9nY0z?up%*_ZaLxYT11r)7iKVS;b^r^MO(I!OWM8Fo{N{x#Jt+?t6 z@KPP%Wg_6!{w(-nXiqQ@LO2jX6tO@Yi9j;G2Y%oueg%?(Nx>Aslt>j!jWofuNQd;n z49FPFgv`M#$QsOs?8p(!iCn?l$P>(qe8?XxfP%q7C>$(;qQPP)juOF=C>1P?GQqMa z7c7qos2Hq-%E2nA8mxxus1dA*TEW_=6ReAR!TM-`hQUT?9BhK7!DeWV7QvQi6>N<* z!M11@Y>y7;80>`3!7k_;?1t{>5$uUx!QSW-?2CTE{uqFP!9f@t9DI%fTzS8oY+MJHE&p0eG|i$xi`G;`lLgn!K}V2_haiPlN>19V zwl~O8D(DaLTa07)2}?M*_*g@Awh?iC@8_P*NyGC?!%MbtnzehSDK@C<8Jg zQz$dCgt8)AC_8c>XDAnPhw>nAC?E2pK&T)Jg$kocs3?k|c&G$QhDxDys0_-6%Aq_e zgesy^s4}XAs-haIhiaf^s1|C6>Yy&_h3cb0s397K8lwrChMJ*ys0CVvTA?-CgxaEA zs69G_I-(Oghq|C^s2jS6dY~tIg?gh;s4x13`eOhFh6Z79Xb6UehG95HghpaiXf(!z z#$p`ChbCZRXc8uereG?jg{EUhXeMTbW@8TKhUQ^@XaN?67GW`#gqC7iXgOAdR$>)a zht^k#h!gLy9)=6*7ew8PJ-kX)>klE@TVQkfB{mb2Z1sM93YY6czGn zuNvC1ioXxOAfZqwj7TVo7~-J>l0)C|BlHu$kQ7dVl;KoJ9ZrL^NEc3z4B?E(6wZt+ z$QsUu?BN{9iCp2_$P>$<=qTymFjuPRLC>1V^GU2i)hw|YHs2Hw< z%BT{qifZBNs1dG-TBseagSz2*s2^^ChG-OSj3(ixXclgc7HAo6h1TIVXp45?_UI7q zh)&_o=z^}{Zs;EFfu7-B=#4(%zUUY3j{)I<7=*#$As8AShT#|y9*I%m(HIjRi*Xnq zo`8wrNthg-f~lAmo{kyenV5yy;W?Nao`?D21z3nh;l)@IUW#Smf7-i$5S8s3KO;T_l+-i6)R6W)t`;r%#(gW*Fs96o}h;bS@ZVuAM*1K88~zWvFtKHXJ`tCr zg^YydFk|ct{4y39W^|0f8b%Jn7OgdvRz0n4iRO*4GtBiJ7Z+N=)(c;lmyd8j`xD_y zqCF8r!!g9e2_)lt_y>N5e<3N70x6Lyk{W3uX^{@;BN>n}k_nk3S&$XkBH586k`uX* zJCX-^Bl(a&QUC=}C{h?jB1KUQ#Umw9GExeqBV|w)njU!Fa6wM;d(IV0ktkHk9nmS$8C@b>(GA@r zJG8$tdV=)fnBNH$&G6|D0B{CJ$ zBGWMgGb6JwJ2D4zBl9pH3nB}#D6$w!ur#s^%OfkW600Jsu_m$>>muv10UINmusN~? zTd^&&9XldBu`9A0d$2dM5Bnnra1e(ghjAow6vuEpasnqKr*JxQ24`_Davm2V7jX%f zBUf-Wat+sUBXSeBBDZlTau@e-Kk@(%BaiSHPa;q8Eb<&L@G|lWuOn~pHu4Vd@j+WP z9{CjcjNkBk1Tx1WQFE$PwZmB8oOXtc_Sk z%n=oq2u<7(8|+$>6OA4&xFcL_Xr)|ASG=V2GQx2y!ruvB6zz#dVu&LVNyhic5B$Wh zNK!N@nj)GKsiLWo25F<|kUp9L8IdWP8CjxPkqz0SIgm4&3%QXeniu(^`B4A`qlHj7 zS_DO-#ZVk2q9su(S{h|gHd+qlqZLpQm7IQ7>8_4WbRv z2#upn&@|c%&Cw#-60M@G(FSd!?a)5j0UgmP+8JGI+oL(<9v`9~@hSQlzv1`j7yJ?Zia+r! z`WOC={ty2||HXgMX(-FL^K)S@gw>ZzoJR8q*w~1jHN>ASQ?~7x>$N- zh-E}3WR7J))>t-VM~+xdd zl}80sj8#JASQS)7wODo3h}A?b)Q;6b-B>-;M}t^HG>SDw6EuxAL-SY*v_z{|YqW{A zMLV>QbwI~hCv--aSXXq5bw>~MjP*kASReF7zgT|^hz-Ob42})K(AY2x$B5WSjEarM z7>tdL!}!<)OvI$vWK4-o#WYNh&A`mqEX>B7*j&tu&Bp>Pj4i@qEQu|}veRJ;WnCjy=KC*fTuG zi`YxNioM1gyp6rX``8D3#HSc*++)9KD~n@a@JH+`{)~MC>ycuAYs)-Xk3iUb>_6yY z0<`!|Q44#E$uTWXh~Sl&Q5)conPZH8Y6Ec$2{AGevuiE1S{q@^6{CS9=Fu(+wTlso zz?8aTLG4vVJBIQ1!WSkMi^Y*Za_l>P;AiX?lHw_l5~<>;ktUuN>5x920U6_&kQrIx zS&=QC9XXIQo(s9-d5{!rQ7m2@CE_Jf3Z>&^P&Qr;A-O&R*hKTbxDT@h|uz{uO`5zk#J<@xSA&z={78*H$$A2VGnMk+p<<5|K!# z91&b`;))1$A@+h$0BvBJG19nI8#B^IF5-?j&9}53YHe3qyNNf>bzYc)JfDuy+nO9K*K~MG)9v|Q#4C7M+>w}v_fmNNwh_~M0<2V$3!P|PIN(6 zbW3zc5A;m*LhnQ$^hLi!e+)$BM*CtV*oL8mvvM!+LB;Y{aI- zW^BRM#5QbC?7&X!O6`m;${=@+s#G%At9Kq4VF&s~vz)74+oW_~NS)9Z9#06Z$ zrNm`iNnFJ>Tu0eQ^?rL^8f7e&8p5C6bbpl2agMaw?=wPJ^^amz*9MkTE$EGACz2R%A=gjvUCD zoC~>=^B^zsCFe&06ihCJ!pTKY6vdK@qXbGOmqO{}GAN63$>mW26_YEWa&i?^MYZJW zsDYZvwNN{`4(g&_a(y&F!{kP2oZJLW(JZ++TA*ceE3{5-gSKdw+#Vg!F}V{uCwD6$wM#{!;*(%1V$#0!sz5N7>jYq<1qmflP6*F z|Jl3C?I^Zx0mFd=f(4Qgcejpq+TGpVU2%7J_u#N`3-0dj?y&I>_l>3HOzz_R=zi|t z9iv8dty*hVQ&-RpbZ_bbdV*d}y+NO*zMvoI-!uRW1cRCegCR{r!7wnqX#^MvMm3EF zW17Z-abSGY1TYayYMKnDG))E5!1SgWU?!N=G#kumY5;S=yr%hJ0a)0y2rO<|0+xb* znwEj(U`5kPu&QY_SOeBJtpn@9hNecav1t?747N0F1>3;(rX64>*wwTf>}lEy_JRFP z2f#sasOc~`(sUFY1IL?AfRo@<(`j&~=`1)0&Np2E7r~{b%iv1WRd5YlZ@K~CQ(r~=i28c+-B0!UB~PysZ+1h4=H z@BsoK0#bksC;=*<0eXM|n1B^v0}kK@cz_=e03i?s#6SY10U3}76hH}70X5J7Z9oU~ zzz{G3Q@{)?z#6atJ8%S?zy;g^5AX(jzz;eFI)g5tYoHtG9_Rsjf?k2%pbzL9=m+|P z0fB*FP+%|^0)_^Lf#G08U?dm?MhC`#v4L@5JeUxe2quBafhk}rm=>50W&~z}SzvZx z4rl;#1M|RqupqDyED9_JOTf~=KVTVH9#{cZ23CR9U`=2xSO?YzHh@O3F|Y}24r~Ei z!M4D5umkK2>;k*Np1@wPFR&jR00#qyz+rGCa1Biod};uOw8LoYzr%n7PyP;>48Slx z7*__voB}WZ4*&Q&5Fzk7@CN)nGWae4M@9m$T^=?G|J@k*cO%%B0KC|TSC@ZZ&A_5_ z;K$#^fxizsf!_hR{rtUv0)apfgaTm@6^sft3pNMQAST!Xv;?uiR-kpT4QLD61>-<_ z&>;w9Oe!>1=02mk?1O^9(fT3Vma5xwNMg~WL(O^t)EEosI2Pc4u z!AW2;m=c@{rh)0f8DJ)u6`T#`fQH~)FfTYCEC36Gi@;*AB)Am(1C|AsgB4(9a1~e` zTm#mEb;0#u1859x1e?I-;1;kIYzuA&JAymGF0ebe2kZs=g8RV%a4>iX90o^%N5Qe+ zac}~h44wj~!I|J$a1NXgUH})trQl_7C3qEF1J{E$z)f%~cpKaScZ2r;d@w2aAPAr9 z2tERjgD_G%_#_CUT7xjqGWa|QBhmg2iweF9!dQ{NLl%N>f^h6M2uBa$;1wJ>34RR1 z=Hei1rG#x`f49fL27uuAzb|h7E=UJ|{#`Km`|tyQ;@~#)_aX`ggCP(OMunn6%|P=| zG>8E$LM=foXccM=+JLs9b|4P44|M?Xp#+c!I);)!GDr!ff;5mG$^e-lE0hg#KyD}x z_eP14GCNOu!tn04uPC z?7#t>As281Psj^=AwTE@I)}P|uAp0}JLmy=hI)bCpiihT=m+|T27rNJP-rk15*iAI zf#IPMU?dn78V$yPv7vEbJeUxg2quBap($W0m=>B2W`t&fSzvZ(4rl;#L-W9VupqP$ zECP!|OTbd_PiPrf4pxL#f>mI3Xbo5!S_jsH4WUM`5o`)=23x?^&^E9g>v3l4O|c1 z05`#{zaP^5`)Sh9-OxR7A3O*>1dqVuzoY3xPr%dAGw>X|2)zWa!2d$80gP^d1MKg> z`_KpQ5qt`L2CxAWwwZkcf42(!_xHul-^J?CuMjLF{CyaLKXh9~6MXa1kg5CE-#~2Fk+~00An)RiGNwglj<^K!)o93ZTOnfCacP9uNRAOakOE z1yBJkOa~0W46^_maKc=`1N^W62!SXp1`;3*%YYmx!b+e5>aYfAfiA2E24D=EfEifA zR$vR;fde?hF5m{9uow7%Kimm)23^8kK{wDn+ynFky~4dgAJ8}45A+8E!UMq|FgQE} z38c z0ayqYg%^V*U}^Xtuna5@uK+8-s_<&C2CNOQ1M9(ta3k0l-UK#-E#a+T8`vJ+0d|62 z;oV>l*c;vl_JafAgWwQ296kb$f@9(1-~>1sJ_SyLGvTw~95^4o04{<{;mhC(xEj6& zu7exlo8T6>9litZf_vfn-~o6Tegqzaf5T6})9^F!9K85D!tn2CxA6b|j{XV10dK?a zzk+LRS~s>n+WJ=Ocdet^B)37dp|w%B>D*>Sn}#-x zZH~6N)#hEBsJ6*%5p8L0)onYs9nrR-ZDZS`ZEv-G*EXtMayvvjT03>S&h19DYiQTl z?r6JP?cTMEic5||#L?o^ah>Bv#5Ke<#vP5j756qS9GBF-ygj+SqP?g6p!U<+uV}xk z{kirJ+kb8!-65qzc?VJlX$O0UJ{`t&SkR%d!=VmWIy~+0twT(FN_<&7K3))Sitidf zEPi_YviNQB$K$WZKac+&ACr)jP@I5HU?->(+zEXX#wIi*tWMaOa5CX$!t;c03C$A| z67v&l6RC-kL~~-7#KDP^66YtbP28P$GVw;@)5On-p~Se3nH|eJVmop=syaG4_UJgQ z7lpQHYQqHH`PI;R0A>~&}OlpVJjMS3U zx>QmsH&vNxN%g1pNgbX#DYYSWS!!eI?$l$c7gO(~K1=`&-yV8%OpG&`y{wV!_>7Ud8q{n2m&q&M2&p>3LGbkCn z3`K@9!=2GRV?f5JjL8`d8A~(PWo*khka05Oa>kvErx|ZEzGVb6T4u&)re_vpA~MmL zluT}>JkyZr%B| zpH-1npGC@IWr?#iS=KCHR*$TKS);NhXU)l4oV7Y@bJp&xBUxv&u4diKdY1Jz>swY+ zR!nwWc5-%3c1d$4r%ow9pn56&K)Jtcci_Tuc-*_*O=WgpHyoqaj` zPWHdq|I7ZA{VO{vr&UgTPI^v$PI*pU4nBvTBgj$Y7;+propO5R49XdmGbv|Q&cd7( zIU91e=j_ipo^w9udd~fvXE|?kzUKVNX`b6AH!(LOH$S&Lw=Nf-OV8!!%5rtNwp?Fs zkKF#b!*j>yPS2g2yEJ!A?&jQGxrcI3;yiVpDbJPHIj?u#puACe6Z2-~&Cgqww>EEc-tN3Zd8hI&=H1A9koPR_ zP2QKhUwPrY*!=eS$@$s&Mfr&Q`g}q@J)f5^%h%>x^4{QQ6N z*W_=?-Al^Z(6%mH#3Cdwx@X^Mcj|@dc>`xdkN!RRyR5VgaLoUmz>c z7FY`01)U3e6$~sGQ82z>T0uj>;)0b08w$1+>@7G_aH`;9!Ht6Z1y2iJ7kn!CuOLtm zUD&2Dp)j>Dr?9xNvar4oUq~zD7D@_Lg~mchp}(+uVZXwmg`*2670xW2SGcrrbzx)S z_QHLIM+#3DUMjp%c)##z;r|Lh7Je^mDr{cVs;EOza#2=MK~Z^8Z4su3T*NFA6v>LT zMdl(`QKzDwMg5D06^$vHTr{(2e$mpRRYi?O+luxU9WFXmbfM^4(cPlQMK6oq6@4lC zRTL^}QQWpTp*XELr?{vXQCwGyEv6K+iUq~;Vr{Xx*je1ExJPln;-ST(izgOOFP>Yx zxOheJy5cRxyNVAKA1gjve7X2$@%`c_#jlD#6n`uJT^ufMQPQp?u_Ub|r=+N)qNKJ2 zTS6{jmhelYCF&AmiM_;I(zT>d$)J)ECF4q_l*}rbU$V4hRmp~uttGol4wf7*Ia_kM zMZq_ zb}#K)I=FOX>A2DcYw51i1Et4G&y-#&y-|9%^l|Bn(zm6bOaCiv zDs5I4TNYQASe90nQ&v<~URG0vE+dxF%Q$7CGDVrT%v|Oy^Otoi>r*zUY(&}EvdLvL z%jT9XE?ZHywro?`jICVlr=ApEsrbjSe{m% zQ(jnJUS3m|Y`Tg=ISGrx526R}eQ5 z_YwahULf8eJ|ey$ej`GN=*m`=ag~Xcsg>E41(l_hm6dgsm`Y+Ly^>QYtdv!%D-D&_ zN>`=7vU_Ep%7K-`Do0mNsGM3kt8!lD;>zWfYbqNnw^i=0JWzSG@>J#d$}5#OEALf4 zu6$nky7EKi*UF!jfy!o8EvwpA#aAU)WmM%>6;+j2Raezl;i|}0j4E!Gs7hX?sWMjC zs@zqbs=8P8sTx={tZH=CgsQ1kv#RD*Evi~pwYq9U)t0KARr{(ASDmOjTXm`Gde!Z! z2USn1URJ%W`c(D3>UUMBD!Mwhx?OcbbxL(+bzXIGbwzb`b$vCinq1AO=2nZUW!36x zL$$TqRqe0tTHULCsj|co>RS`dTI5F>b2DytG89}uHIjLr21s_x$4W+ z*Q@VTKdgRQ{j&OP^{49Z)xWBP)y->SYueSs*Cf|u)a2F_)|Ay$)zsBsYKS$o8deRz zMpC1!(bkx1>^1J1PBq4`)iKW zoTxclbFt=H&Fz}|HUHMUsCixUq2_DNkD8{MsM?s?*0t?x6KhjzGi&o|i)+hkt843P zv9+XHS}nU)P%Eib)@o}_wf0(fZKv99wY_Tx)DEc~Sv$6NQthpn8ZJ;))u0>tzy7qO6b*Xik zb$N9~b>($cb;vqQ9kGsD$ExGiiRYkv-IBT$b!+Mx>$cSGsM}k2u>p?0SB^xL#hbsW;SH z>Yer8`p)&;>-*FXs2@^4qJC`s#QLfAGwbKpFRWi$zoLF^ePjKW`W^Lq>krl+tv^|R zuKrT}wfbB2_v#lAH5j84801y4!se*6}=O^4}A!I41EfH4t)uI4SfrJ5B&)J6#WwY2K@p3 z1^plTH#&%JhG~Imjfuk~V3INEm~2cwrWjL>slwD@&=@?1jG<%L7(Pafkz>>tJ;sc& zW84@&rYoi=rY~k7W+-MPW-MkRW-4YTrUA16vjnpovl_D=vk|iuvlFuya}aYBa}skF za}jeDa}#qH^APg{^8)i4^B(gV^9}P8(}W3QqOq~qw%88Xj@VReCN>vah%LcZV5_l6 zECx%!Qm_mx2P?ozuyU*#tH+wLcB~ug$9Bc`#P-Dwzz)HVz>dLAz)r!=z|O(W!!E-9 zgI$STi*3Yi!S2BB#_q=+#vaF>#-7Js#$LnT!rsF^!al{m#J<74$9~3s$Ns_wuu-@e zTr93Ft^=+kE)|!F%fS`kN^ljpY8(=W#^G^f9398T@o*xX45z~Ba3-7;=frt&opIf9 zy>R_-gK)!eqi|z!6LC{L`MAZnWw=$ib+|^{7TgZp9^8K1Vcc=tY210-CEPXK zE!;iaL);VG3*2kmd)z17H{4HL6E1{nj&F%?gO9@};FIy`_$+)Lz6f82N8oGl^>{3v zfT!RYcn+SA7vtr4HC~4|;ca*q-iz;y?~d<aFTGA zaFK9@aD#B0aG&s)@Rab9@P_c7@R{(9@RQI)2oahSTM%0lCh$o0=i06ryiPwm?hNyd=zWHOmXW|6sM0a-$plhtG$*+jOIon#N$ zPwq$A2N;yF}O*v1wM7c`2 zNx4J0PkBsvN_jzfO?gN8Ncl?nkMfHWpoA&SsV%9ksqLuo)Q;2?Y6dl%nnx|7mQpLI z)zmsFii)EWsT3-m%BJ$DLaLOipsJ~Qs)=f)I;kG2pW21mgW8+gk2;7tlsbYshB}@) zi8_@!lRAewkGhDul)9X{in@;4NZm}`PTfV_OFck6LOo7BMLkEoNWDV6PQ6XNM}0{B zm-?Lgiu#87f%=*HjrxQ7hZ>|t(V}Uwv^KOjT0AX@mP*T@<uqdqoHUx z8j(h!(P?ZNk0zu^XbPH|rlT2YR+@w6ruk@{Y29hPXnkn|XhUejX`^UkX%lHvXwzx4 zXme=`Xp3q8&{oja(ALv7(zeic(00@I(GJp%&`!`!)6UT@(yr2O&~DT2(H_#C(4Ny? z(caMB(>~L_(SFc=(*m?8dNjQyy*0fZJ)Yi?o0RjE>AmUw=mY6P=)>uw=;P=U=~L*_>9gn! z^ab?A^nd6p=&R}L=o{%<=-cVL=zHl0=tt8I#t=@;mi>DTGE=y&N4=#S}7=`ZN7 z>F?+t>0jvI=|Aaz=plMDMhqjC(S{Mnh-V}*QW+VHY(^fVkWs>@U{o<`8TAYd1J58a zXbdKU!{9SS3@JmwP&4!l6T`}IFx(6uqcfu$qZgwuV*q0?V;Ey3V+>e?T;~?V*<2d6K;~e86;|k+C;}+vC;{oF_<0<0> z<2B<2&Og;}0Xqh+;-FTQb`)OdHe5^f3L*F3j%CUd+DC0nEY7Va$=tG0gGI$;@fY znanxNdCY~(CCp{amCQBF^~{aTEzIrAUCh1A1I)wBW6V>`v&;+3%gk%co6I}R`^-no zC(P%}SIjre_smbsugw3LznD$T5VIL8h84?d!-`|YvpTX;SQ)HrRvxR6Rl+J~RkCVW zNEVufV-Z;t7M;anaajVEm?dK=SsIp}Wnx)b4wjqcV|8YAWA$Y9VfAMXVhv@DV2x&t zV@+gDVNGYvVl}Ylvlg+IvX-+}vDUISur{%_vUad`v-YtLvW~Ehvre(jvM#VLv#zmj zvhJ|%vmUXYu%5GCvEH!Wvp%uDvi@WJVl}ZstY+*Ob}YLMJB}UC?#NDIr?WHJIqZCP z5xbOK!LDN0vg_FxHl9slQ`rnQo6Tbj*%G##tzv802DX`PV>{U%wx8XF-JRW&-G|+u zJ%~M&J%T-&J&rw*J%v4;J&WDIp3h#yUdmq1Ud3L^-oW0--ooC_-o@U_KEOWAKE^)D zKEpoGzQn%DzQMlDzQ=yRe$0N#e!>19`z`we`!o9+`v?0sJHQUJn{#3~v79!XI8HpL zBPWHE&dK8Batb)boH9-Yr;1a{spnugcn*m}sSk45_B+gXM49;xMT+RZ{V$MID6`a+awVVx{O`NTq z9h}{qeVl`wBb;NLlbkc0^PEeZtDGC0+njrx2b{;8r<@m@|8d@OK5#yBzH$EJ{Nglm zLY!vY7;Y@L4YwV)12>VI%uVBFa&x$O+(K>%x13wat>GfMXfBpZ;F7sCE|bgQ^0`8; zge&K&xLU4(Yv$Ux4z8Q)<96nD-;XdQO z(3 zF7mGMuJLa2?(pvO9`XL=J>$LPz2?2;ec*lOedGP${o*z8LcC`D7=BBBYkoU^2Yv!S ziJ!{P;Aiu5`33x9ei^@lU&XKG*YnYQ9G}Ri@acRepTp<#MSKZg&R6lZd_CX9xAGl) zH{Z+e#P7=Q!SBWI%OAiW%pb}h!5_^Z$DhET%%8@e$)C-i%U{4>%wNi1&R@k}%U{pm z$lt=>&fm%3!{5(8#6QA6&OgOJ%RkS*#J|eF!N0}7%YVRs%zwgv&VR*!!+*#B$p6Cs z&i}#x%@6QH{APj}L9C#)pq-$DAW@JcNEKuVvIV(<0zt8$R8S$P64VNi0<-`pAPUF= znt&-_3wQ#dKq8O{lmd-FCol>u0=vK|@Cf{Z&Vp`&o`ODteu9C5A%bCok%BRT@q&qh zDT3*OnSwcjd4h$4#e#nXD+H?qYXutwn*>_~+XcG>dj$IhhXh9i#|5VZX9ed4mjqV@ z*9Er(cLnzaj|5Ky&jl|9uLbV}9|WHT-vs{&ehHccK|z!-TG&$9O4wG|UKlUzC`=Ki z2{VN`!aQN2utZoUL4u~qC4yTo2`Cvg{XcX3Z~A8~*2K=BaqF!4z781XpqMDb+tH1Q1aZ1G(2eDNaj z67e$e3h`?3I`IbaCh->WcJVIp9`SzhLGcmsG4V<98Sy#sMe$|vHSrDcZSg(v1My?= z6Y+ELOYv**JMjncXYp6@f8w9wKjNS`EN(7|k;F<`OWH}=OA;hWk`zh0BvXIO863yL?V$(R1%FuFEL6i5}U*+aZ7xXPLi&Y z9+F;?zLNfuL6RYo;gXS(F_LkTiIORjX_A?e*^;@E`I1GFC6Z;56_VAGb&?H|O_D8= z?UJ36J(7KrgObCNW0Dh+(~@(N3zEx{tCAa%Tavqy`;teJe4O4FrT(i~~Nv`|_iEtevs)zVsNy%a6Q zNeNQ2lqzLNSyHZ)FBM58Qn^$q)kt+xqtq<5NgYzR)GO^I?IP_i?J4ae?I#^59V{It z9U&bp9V?w6og|$qoi3dvogc%R*&5k8S)*)|Y>RBWY^Q9GY@h6)?6B;Z?1b#J?5ym( z?2_z??7Hlx?2hc7?4j(j?5XUz?4|6r?5*sB?33)P?7Qrz?6)i+3(2D7(ef7ZR`NFT zIC%$oqC82SB2SlR%5&s-@*OdoMvj*g*PkcS#Fa%kCKm(kC#uBPmxcP&ydfS zH^}G97s{8&|B)}3uad8kua`Hm5SAhb&3s&O^PjwZHk?W-HLsR1Bye6ql)8-lZrEnbBc?K%ZjUt8;VGGAGwEK!yz5y~oMtrDq3DX~htlBlF8X-bBYt>h{NN|92clq;1=wNj@v zD9uW%(ynwVJxZUlv$CtQyRw(EkFvjVpmMNsm~w=2lyaB(?B2}rXT!m0ot7=t96-QEH4DuO_O=YO0!{W~n)9zFMdjt7U41 zTBX*g^=hNqthT8gYM0up_NzOqyQ+Jrd#U@V`>O}42djswN2o`s$Ee4vC#ol_r>bYD zXQ}6?=cyN{7pa%3m#J5%SE<*k*Q*=Vo7G#@+toYOd(`{X2h@kvN7cvGC)H=v=hPR} zm(^F**VQ-GchvXP57dvrc_C{1%sj3!po zTGLh&r-|1jYLYZ5nsiO3CR>xIDbN&YN;Ksfgr-VUqd{s=8jJ?7A!^7Ps)nIqX*e35 zMyL^Mq#C(KrO{|~8l%Rnv1;rZm&T*X$EKpX@+WsYes5DYsP6N zXeMc4ZA5PO6jZR631Lr!(kGI;+mEbL!kWudb7> zi>{llhpv~dudcstpl+~ksBVOAlx~b}oNj_{vTmwwx^AX!wr;L&zHXszv2LkunQoQ+R(D=^QFldmO?N|gOLs?iPxnyw zSocKtO!q?fKiwPMJKYD}C*4=wcij)&FWnzqKo{0E(?{!D=wtP5^zHQR_3`>deUd&^ zpRUi;XX|tI1^Oa=iM~u%DrvzKgz_zK6b-zK_12exQD^eyD!9ex!c1ew==Sev*EQewu!U zezv|rKTp3vzevACzf8YEze>MGzfQkFze&GEzfHeGze~SIzh8e)e^`H1e_Vf3e_DS| ze?fmqe?@;ye?xy;e^-BB|4{!}|3v>>|5E=y{Tux|{RjOg{a5{W{SW;w{U3cmAJ#WB zL>pQdVhybgZ4K=W@rFc0k|D*AX2>*T8*&Z#hC)NJq0~@es5Dd?Y7IyO%78K84MYRk zKsC?}OasTjGYAYKgTx>+C=68#xTw>!7#}%#W2kV3=oEU|3{WV))0f!m!G)#<0$?!LZS=*|5#9 z!?4S+$FR?Ez;MWL#Bkhj(s0^v)^Of%(Qw&t&2YnT%W%hV&+x$T$ndY>nc;=umEpDF zt>L}lqv5mRo8dphPs49RlObpb8=4toj4h3=jBSkVjO~r_#zbSXG1ZuE%rs^jbB+1N zLSu=s%vfQpG*%mHjYuQPh&AGkL?hWqHPVetBiqO|3XCG7#3(Z=j4GqXs52UjW~0?; zH#&`Oqu1y+b~bi1_AvG`_A&M|4loWf4lxchjx>%ojx~-qPBcz7PBl(9&NR+3&Na?A zE;KGSE;TMQt}w1Lt}(7RHX1h>w-~n>cNljW_ZasX4;l{}j~b5~Pa01f&l=AgFB&f! zuNiL`ZyE0x?-?H$9~u8OJ~ci!zB0ZxzBRr#el&hIel>nK{xJSBHW`D)u(6pb+SI}n zYiez3Yl<_)n-Wb)rW8|}DZ`Xy$}#1c3QR?&Qd7AJVX89KnCeXRCbS7_!kb7Yiiu`o zm{=x`iDwd+L?($zW>T2cCapSpR;>SgL<>Sr2Y8e|$` z8fF?{8f6-58gH6tnrxbCnr@nDnr&(@%`+`9Eix@NEij*4*0M*4*A4Z%#BPnN!SZ<_vR|Imeu5 zE-)9FOUz~F3UigY#$0EvH>1s1Gu})zlg(5!-OMzz&0I6zEHq2ZGPA<0GHc8_v%zdK zTg*1I!|XD9%sz7`a~E?rb5C<`b6<0R^FZ@p^HB3}^GNe(^H}qE^F;Gx^HlS6^Gx$> z^IY?M^Fs4t^HTFN^9u7S^BVIy^9J)q^Jeo_^LF!2^KSEA^M3Oo^AYng^9l1Q^BMCw z^9A!I^A+@=GW%8=J)20=FjG@=I`bo=3nMN=72e5 zjShiVqSawCSPof^SdLjvSWa2aSk754ST0$vSgu)aSZ-PFSngRK zSRPsawLG)Du)MOow!F2xw|umGwtTgGxBRgDviz|GEFnvjwYfFM+S1y}+Q!<>+TI#( zO|&LiQ> zSVdNeRc2LKRaT8vXEj(&R*ThUby!_ikJV@GWbI<@X6<3^W$k0_XB}W2WF2B1W*uQ2 zWgTN3XPsc3WSwH2W}RW3Wu0T4Yn^XhXkBbwYF%bsVO?ciV_j$6VBKilY~5mY!aKyrm(4O8k^2$ zu$gQYo6Y91xojSr&*ryvwsp02xAnC3w)M63w+*xnwhgrnw~e%owvDxow@tK7woSE7 zx6QQ8wl&!1*%sIq*_PP;u`Rc)w5_(SwXL@`+BVs?*tXer*ml|W*!I~D*bdna+m70f z+fLd}+s@j~+b-HJ+pgNK+iu!!+wR)#+aB5;+n(5-*R< z*nZjm*aEhoEo^IMkG8k4$J$%l+uGyo9qbAAj`n1Gsy*GFY0tLj+Vky&_F{Xfz1)tl zSJ`Xqb@qBY%8s$)>;yZ>PO;PM3_HutvGeQ#yT~rF%j^of%C52N>;}8ZZn4|!4!hIt zwtMYt`yu-g`!V|o`ziYw`#Jjs z`z8Ao`!)Lw`z`w&`#t-8`$PL<`xE;!`wROk`)m7K`+NIG`)B)C`*-^f`!D++d%zyF zhwaTA(T*05SVwC|TSuIugCoJw(UI&(b)-8o9odc?N1mg=QRFCblsPIKm5yphtpn*m zIWP{KgWw=J$PTK5?qE9D4z7dm5IV#TsYC8iI@AuWL+>y+Ob&~~=5RP%4v)j<=;Y|) z=;r9*=;i3+=~QRI>~ZXK9B>?R9B~|ToN%0UoOYaboOfJwTy|V_ zTzA}b+;-e`+;=>5JaYW&c~*8RywPlwN9iH<-|B~ zPP~)oBs-~2x|8W-JGoB2Q|J^sB~F=B;Z!*_PMy=>G&wC!o73TRIo(dL)9>u;?CR|9 z?CI?7?Cb3B9N-+}9O4}29N`@09OE44oZy_~oZ_77obH_Iob7CI&T}qsE^;n${^MNc zT;W{hT;p8l+~C~k-0a-y-0s}z+~wTk+~+*tJmfs$Jmx&%Jmoy&JnKB~yy(2_yz0E} zyy?8{yz9K@eBgZK{MY%^`P})^`9J3y=R40Abv$z^fbTn?AZ<#u^pephE#S66pe zPgieOUspfZ0M{Vb5Z5r*2-hgr7}r?Wc-KVNWY<*Jbk|JRY*&M8u4}$)p=+^gscV^Q zg=>{-wQH?wy{pl+$+g9`&9%d|)3w{R*R|hu&~?~#)OFl-(sjyp#&ynh!F9=X#dXbf z!*$bj+jZA<-}TV-*!9Hq%=O&$()B;r8`nG62iGUpXV+KPch?WsFV`Pez!h?ZUCrFl z?iTJ?cWZZBcbvPuJKmk>PI9NX)7%;EEO)j$*PZV!bQilz-R15Iccr`9UF$}=QErSI z=f=BKTjG|v6>gPV?bf>WZll}mwz}^|n`Ol8`=`Gp=8=1p9<@j7(R+*@lgHw*c^n>>$K&yO z{GQI9uAc6mo}S*GKAwJ_0iHpgA)aBL;hvG6(VnrM@t%pENuDX5X`UILS)Mtb2G2at z0?#7P63;)LWu6tDRh~7Tb)F5LM$abC7SA@%4$m&nZqHuNe$PSAVb4*|G0zFlDbE?t zInM>pMbBl=RnK+LP0wx59nU?_1J5JRzn-U_XPy_HSDx3Nx1RT&51vn+FP?9n|2#iE zzdV0D0Z+&i_GV?;-CI z?@{k@?@8}z?^*A8?*;EA?-lPg?+x!w?``j0?|tt>?<4QO-lyK@-k082-q+r@-uK>* z-p}4I-f!Ojyg$9ay??v`Z^#?vYwnBoweZFITKn4i+WFf1;(dv}Bww;G)tBze^kw^U ze0jbCUy-lGSL!SGA$(Q78egps=|lN2KAaElBl^fbs*mnt_*g!UkLMHkgg&uP>XZAF zK9x`7)A}|5X2U|4jcZ{~Z5Z|9t-f|04eq|3Cia{uTaJ z{x$w}{`LMw|0e$y|5pEY|4#pI|6cz-{{jCY{}KOD|8f6G|7rgj|2h8!|0VyG|04(< z0IoV10Dxv|*)|uKZF|}Jo_XK3ZN7|U+s2=5x6EbxKPTf{#)XVa8CNo{W?av>nQ=Sg zPR8Ag`xy^29%nqsc$V=Z<7LLHj5itYGCpK{%=nz~HRF56kBna#nS5D%*?if3Ieoc( zd42hO1$>2kg?&YQ#e5}vrF^A*Wqsv+6@8U_ReaTaHGH*vwS9Gc^?iT(8u}Xhn)?3o zHTSjjwf433wexlGb@Fxbb@g@k_4M`j_3`!d4e$-}4e<^24fBogjq;85jrEQ9P4rFj zP4P|h`Fws~&=>YaeKB9c2lzlAD%qw>)Yo$;5+0y>^tH+ z<~!j#pSne=)2^*;=AU%;k)U(?fchv&v)PV(D&H))c4Hy!uQhm+V{ry&iBFh z$@kg!)%V@^)A!4l$)Cla&7a+$)1TX)*PqW{z+cE;*k8n7%wNJ^%3s=F)?eOV!C%Q= z#b3=|-CxsR+h5mT&)>k`(BIhK#NW){+~3mQ%HPJ{&fme`(cjtM)!*IU!{5u_$KTiA z-#^el*gwSow|}^Qq<@rujDMVef`6iavVW>S!|(G4{2_nXAN9xmNk8BR{jeYPV}8O< z`58a!=l!Bz@+*GLZ}?5W?RWiY|8)OM|7`zU|2+Q!|3d#F{}TT){|f&~|7!nQ|9bxh z|0e$y|2F@2|4#pI{~rH7{{jCY|Ns0){Kxz!{3rdV{b&8>{1^O}{8#)}{n!0B{kQ#h z{CEBL{SW+){7?MP{LlUW`Cs|p_}}{9`#<_W`M>zT`G5F-`hWW~2eJgR1#$#(1#$=S z2J#0A1_}iV2Z{!Y1xf@;1B2#gAh35*Sl4@?Y9 z4onG53-|*5Krj#vL<6xvA^-$Z0Vse3umBz)19X51Z~-A81>}Gl&;v%m3OE5TkPgfU z%nHm7%ni&B{1aFhSR7aySQc0jSQS_kSQ}U$*cjLp*b>+l*b&$n*d5p#*cUhuI21S> zI1)G(I1xA*I2||}I2X7OxD>b&xEi<~xEZ(=xD&V=xF2{BcocXNcp7*f_%HA(@H+4| z@ILS%@G0;m@HOx~@H6l`kSUlYm@Sw+m@}9=m?xMoSRhy^_(!lvuvoBouw<}wuxzkg zutKm>uyU|!uzIjYuvV~6uwJl!@XuhQVB=uZ;9tS!!Ir_+!M4G6!4APr!Op?1!S2Bx z!Ct{W!G6L1!GXcS!6CuFgTsR(f}?_Cg5!eYgA;?3gHwXjg1(?X7z~Dk(O@i?2m--W z5DFqeG>8YuAQfbSTu=y#K{==fwV)BSf_Bgirh_wrGlR2(bA$7O3xW%Si-Jpn%Yw^; zD}$?pYlG{88-kmHn}b_}+k-oTyMlXydxQIf2ZR3$9u6K29uJ-fo(i4`o(-N4UJPCe zUI|_c-U!|d-VXj7yc@h9d>DKbd=h*Xd>;HS_$v51_%`@H_%Zk?_$Bx)_&xYD_&b;> zlqHlcls%L)lsl9slrK~uR50{Ms7R<-sCcMksC1}Is9dN*sA8ycsA{NMs79z(sCKAs zsD7wHs9~sasA;HKsClSms8y&Btp-?y!4aGx=5D)@GPzVWOAv{Ee=nxa)LPAIi z$sskQhm4RFazbt>9hwoE8JZoM8=4nd5Ly^o6j~Bm7Fr%!8Co4$6IvJA5ZV~p9NHS% z7TOWo71|xz8`>W_5IPh(96Ay@7CI3+89E(08#))d5V{n)9J(619=aL26}l6;8@d;I z5PB4P9C{jh9(oaa8G0Rh6M7f=5c(MU9Qqpi7WxtT75W{@9L^ff7S0jQ70w;b8_pjt z5H1uh94-_z8!i{F5Uv!i9IhI!9Vps|*VJ)nO&9EJI!d`fK zct&_ucy@Shcz$?6cwu;Pcu9C!ctv<+cy)MfcwKlycvE5f@Z0eF@W=3{@R#tn@b~b~@b7S@NR~*pNcKq1NbX3UNWMscNWsV-k;0Lpk>ZgO zky4Q|k+PBUk&2N@kt&gDk?N6}k=l_uk$RE(kv}7iB8?+WBY#DjM_NW&N7_W%MLI+} zMmk5jM!H3MM0!SgNBTzkMFvC$MFvNPMutU(M@B|QN5(|PMaD-aMkYt5M5aZ25q~5Y z2}dH4SR@fiMp6+l0!PpY79k>3gpRNgJ|aYmwT@n<85x+alW|J0rUzdm{TH2OG3c+=<+c+>1PjJc>MyJdHeyyokJvyo$Vuyo8K=8EQt=8NW!7L5K8EgUTxEfy^iEfp;tEgLN# ztq`pgtrD#otsbortre{ktsAW${WIDy+Bn)I+AP{U+9KL2+9ujI+CJJL+9}#4+BMoe z+B4cK+9%pK+CMrlIw(3M`ge3#bVPJybaZrVbX;^obW(J3bZT^3)E5mzgVAs_8jVF0 z(PT6gg`#j2jp9)vN=4}?8|9-yREjE5HL6FAs18>*(9)yXc4L$LQzi z*XXzCkLb_n?`Y;&mRPn}j#$oE?pU5!zF2`+!PpaiNJTCqB@y0QAP2C;^*#<3=`X0hh67O_^b*0Hv+_OTAJPO;9huCea19hEL9rpRp|N4H;jxji(XlbHak25SiLuGCDY0p>jF>+bjD=#6STq)oC1XGg zj6pFZhQ;t08KYxNjEnIxF(${9m=@DxX3UN`F)x;m&4|s4&5q5D&5JFFEsQOSEr~6S zEsw2?t%|LQt&OdZZH#S-ZHaA-ZIA7Y?TYP*?Tzh^9gH1{9gZD|9gCfaos6B1or#@` zU5H(bU5;IeU5nj_-HhFi-HF|e-H$zpJ&HY!J&iq&y@%7mgQ+7mF8pA?@GpBB%E`{RLlC?1JN`26@k@rCil@ul%) z@fGov@zwFQ@pbVH@s07#@h$Of@g4D<@!jz~@qO_F@q_XI#Sh1i#*f8M#81Uf$Ir&k z#V^D!#V^ON#;?V1#BauL$N!Drjo*(yh(C%yi9d}$kH3h&jK7M%iNA}#kAIARihqfJ zi+_**jQ@&fN@PxCO=M5xNaRZ7PUKDGOB6^HO8k*1k|>%eo+z0pl_--an<$^Ckf@ZX zlBk-fo~V(im8hMlo2Z{?kZ71_lxUJ@miQ~tBGEF@I?*Q4F3};;G0{2ECDAR>J<&7K zJJBc6FVR0SFfk}GB=L7*SYkwCWMXt;Ok!MOLSkZKa$-tiS|TIiPXrU8L?jVS#1qK` zkN^`<0!g3=JV7R?1e0JBd_qV_2_>N>^n{VH5_ZB(q!ZH_HYPSDwj{PDwkLKbb|v;C_9pfx4kQjG4kwNzjwOyKP9{zz z&Lqwy&L=J=E+wuct|qQ0ZYFLe?j-(A+)La~JWM=FJV`uDJWu?Wc$s*ec$0XS_>lOR z_?-BX_?Gyd_?h^f$dt^I%$m%e%#qBM%$>}e%%3cfER_5sStMCBSv*-XSt?m3SvFZd zSs_^|SvgrXSv^@JSu0sPSvOfP*&x|4*(lj0*);iAvU##)vQ@H8vR$%$vSYGSvP-gS zvU{>;vRATCvTw3~azJuWa&U5Ja#(VBa%6H;a!hh;a(r?^a#C_ia%wUo=}QKZ!DKiY zNyd_iWHOmbf=M`uB(Wr(B$ITKNpeX(DJG?)l2ns=(o9-OC+Q~B$?3_N$yv!c$$827 z$$ydylZ%r}lFO3IlPi;}lWUUelIxQjlbe!Tl3SD8lRJ~Ul6#VSllzkgl82K2OCCuc zO&(94OrA=fNuEufPhLn~N?uN0OCzXn*5&pk^GhXoy-hm0kQ$vft)~YAPqI8YKO z4U_@O0p)>;Kqa6GP!*^S)BtJ$b%44+eV_r*5NHH60h$7T0nLGyKx?24&< z0>pqgkOTk#1RwwbPyh!AfC3nR1$aOJBtQmKKm!cG1Z=O4tAVw^I$#5^5!eiD0k#3#ft|oEU=Oet*bf{84grUOBfv4>IB*g; z1)KrS0_TAXz$M@ca22=?+yHI?w}F3wyTE4k}8@io+^kDm5lGHZ?vqAvGyAIW;vkE#*u3Q^8a? z6-mWX@l-Meq`(xELQ-f7PZ239MW@&lml9H9N=_*$EoG$4l$~-?UMiiMk(!yBotl%H zmztmYC$%uOIJG3TEVVqfGPNqTCbc%TKD9BmDYYfFHMKppBeg5FJGD2pFLfYwF!jIG z;ndO8vDAsw$<*o8nbf(|`P9YKrPP(wwbb?0&D5>boz%and#U@Whp9)YC#k2Y=cyN| zm#J5&H>tO&_o)x5PpQwTuc>dTAE}?I->J-C7BCx_9n1;l0`q`*!TewWun_nMSOhEz z76(g!rNGi)S+E>f0jvmC2CIP8!0KR4uohSctP9o$8-NYLMqm@L8Tc320&EGk2HSw` z!1iEAuoKt?>f=qLXDs%P*dnHs5#UUY6Z1{+CuH2 z4p1kkGt?F82K9h?LcO6rP(P?YG!PmD4S|M2!=T~NNN5x^1{w>EhbBOipvllwXd2{$ z{7?`IK@lhl#i0ZQKq&}$9 z;W_YJcs{%UUI;IOm%vNm90sAa#-YNCTuH(g^hNq31CT+;U}OmLH!=(vfs8~(BV&+p$arKTG6|W2 zOhu+4KE#g%kq{C=qDUM`AOMm=AOuEG1VeCyL@0zoScFFeL_%alMKr`fOvFMQ#6{A` zbYvzn3z>t=Mdl+5kcG%1WC^kqS%$1YRwApBHOM+-J+cwmgls{!BHNK2$WCN8vIp6R z>_-kFhmga_5#$(h965oULQW%Rk#ooe*TgYwXU*s-wA9;X0LLMVe zk!Q#Y6C0%$?B z5Ly^5f)+!Iqb1Q&Xc@FDS{|){RzxeKRnTf^b+jg03$26JMeCyt&_B^eXk)Y~+6--u zwm@5e(H>|ov^Uxp?T7Y92cm<}A?Q$a7&;ssiH<_YpkvW- z=mc~kIvJgUPD3+LKN>(oXcF*J@QQ2+%|2t`m7#ZUqzQ5t1X4&_l1l~4s$Q4KXv z6SYwX_0Tjr1D%P^Lg%1!(fQ~CbRoJ3U4kw}m!T`rmFQ}84Z03pk8VUapo# zJXl^VKUM%Mg#CdP#)@Lauo75FtTa{zD~pxKDqxkc%2-vb8dd|ViPggDV0E$jSOcsf z)(C5YHN~1?&9N3(E37rv7HfyK$2wx2ur63vtUJ~N>xK2k`e6OA{@6fl5H=NSDGb7348brA$4HFA7>va@ zOu$4;#uQA$bj-vo%*I^I!=__1uvyq_Yz{ULn~(j2EyNaMOR%Nba%=^*3R{h>#nxdP zu#MOzYzwv(+m7wPc451*J=i{MKXwp1gdN6?U`Mgz*a_?ub{ad2ox{#!7qLs&73?Z@ z9lL?u#BO7Eu)Ekj>;d)=dxSl~o?_3j7uZYe74{l?i@n1>U>~v1*ca?8_8t3y{lb1@ znei-mRy;eN1J8x$#`EI&@cei|ybxX(FM=1ti{mBmQg~^+EM5+;fLFvT;Z^Xecy+u6 zUJI{{*TL)I_3=OPhInJV3EmX{3vZ6M#9QHQ@V0n6yaV15?~HfByW!pO9(XUjH{KWT zhY!F9;)C!Z_)vTpJ{%v3kHSaeWASnL1biYs37>*b#WQdp9>9Zm2#?@VJdP)D08il{ z4&w-p;W$p<6i(wT&fx+s;u5alDz4)OZs9iW;2xgFXW%pO+4vlME+ucvCVVr#1>c5m$9Lkp@IClmd>?)QKZySiKa3y6kKxDhllUq8 z41N|rhhM-i;+OF&_%-}GegnUS-^TyN@8b9H2lzw$G5!R9hCjz&;4kr4_#6B!{to|u zf5boIU+{1Ecl-zb3;&I0CbAG&iR?rUA{UXH$V22K@)HG#LPTMr2vL+MPLv=@5v7ST zL^+~7QIV)bR3WMo)rcBIO`@Hbgt3J<*Zq zM06&)65WU%L{Fj@(TC_u^d|-ogNVVz5aMrQ7%_qvNsJ=K5Mzn)!~|j@F`1Y`Od~P~ zKM^2;M3{&WF(OVR2!Kct5CIbife|=C5)?rbEWr^1Arcay5GtV)24N94;Se5?CT0*b ziCM%PVlFYCSU~(kEFu;YONnK~a$+U1idaLeCDsueh>gT%Vhgd2*iP&qb`iUYy~I9Z zKXH&aL>wlL5J!pQ#0laQahfMNLC`NkX6ZQWDT+=S(~gw)+Otc4akOMBeF5s zlx#*eCtHv$$<|~WvMt%3>_B!RJCj|=Ze(||C)tbaP4*@Gkpswq&@d4aq{UM8=Q z*U0PSP4X6boBWr&OWr3RkPpen!(7P&KLAR2`}wRiA1=HKZC*jj5(oGpae&f@(>%rrJ>LsPPmH^x>G%= zUQ}nVL#XqcSKz6`(>?n2Jy_ zDo!OSfJ#vi1ycxxQ8-0X6h%`k#Zdw!QWB+5Dy2~dWl}cfP%f3Grc*PhS=4N5E;Wyu zPyItJq!v?4sHN0$Y6Z2DT1~B?)=}%J4b&!TGqr`IwVygj9ik3X zN2sIJaq0whiaJf5q0Uj~sSDI4>N0hex<*~6Zc?|X+tk0*UFtsdfO<$hrk+qwspr%S z>LvAxdQH8h-cj$VkJKmX3-y)yM*X0EQopE7bY?m$osG^;=cIGdx#_%gKDq#1kS;_Q zri;);>Ed(=x)fcSE<=~2%hMI;N_1tqDqW4PPS>Ps(Y5KibUnHO{U_a!ZcI0!o6>*L z&FPkOE4nq^mTpJ4r#sS}=q_|ux*Oet?n(Ee`_O&qe)Ir(AU&8KLJy^f(ZlHx^eB2X zJ(eCvkEbWnljzCxRC*fiqy2P%4$)ycLdWPhouC0aMME@9BQ!?iG(l4|O|vvd^R!4y zv`nkCM(eamTeL&Fv`0^;XV5e0+4LNG9zCC4Krf^h(TnM&^fG!my^>x*x*i zMtT#yh2BbUr+3gh>D}}mdM~}7K0qI&|3@FDkJ88J%t&SwGlm(oYDcg+wi*3QSWLvRq*tTqYwgcOd?aX#zyRzNc9&AsxH`|Bp%l2mnumjn_>=1S+ zJB%I9j$lWzquDX+ICeZck)6a&W~Z{#*bLUs2G}4QW+QBrjk5_h$);G4g;<0|S&SuE zlBHONWm%3DSdo=kg;iOdHCU6iS%-DmG&`N0!Omi5vvb&a?0j|syO3SPE@qdq%h=`Y zN_G{ynqAATW7o4A*-h+bb}PG$-Olc0cd>iez3e{r0DF)<#2#jkut(YB>^=5A`+$AKK4zb=&)Dbe3-%@Ziha$#W#6&y z*^lfe_A~pH{l%6;R$b3eFW z+;1)upM}rLXXA75Ir&_C9zHLhk1xO%@4|QE zyYoHxUVLx958sdP&kx`S@q_sx{7`-vKb#-IkK#x3WB76WczyyuiJ#0*;ivH#ypIp? zK|aJs_$VLa6MT{f_!JNEFpuyUkMjgi@ifoy9MAItFYz+3@EWi425<2;@9-X<=BM*B z_*wjHehxp6pU*Gg7xIhv#r#r!8NZxg$*Z{fG`+xZ>*E`B$^ zhu_EV=MV6Q`2X>T`J?&`fA9v=CYft%Np0TcMrMLFg!S61oUog>FI*p{LMG=p*zM z`U(Aofx;kRurO5kTNoyc5Jn24gfYTcVVp2tm?%sVCJR%AX+nnJ7Xm_12n!J*D#V0@ zkQ4v`6d(Z>PyrKgffOi#78rpOctH>(K^7E26Li54EWs8W!4=ZNbYX@tOPDRp5#|Z= zg$2SwVUe&{SRyPFmJ2I{Rl;gvjj&EwFKiGt3Y!d79sutV4>>=yP2dxibN0pXx< zNH{DU5snJSg%iR_;k0l@I4hhNE(jNeOTrc5s&GxXA>0&h33r5lg}cH%;eqf_cqBX# zo(j)|7s7wSOW~F9MtCc{6FvwZg-^m4;j8dX_%8euehI&Y%wiTXtC(HPA?6fwi+RMn zVm`5eSWqk^78Z+$MaAM`39+PDN-QIm70Zbg#EN1iv5Ht#tR_|$Yl^kR+G1U?o>*V} zQ*0RZ$al(G)Gw79G(O)8ce-rZ`KSEzS|=iSxw;;zDte zxL8~&E)$oFE5udeYH^LYPFyc;5I2gO#Vz7ial5!f+$ruB_lSGNec}P}pm<0;EFKY$ zipRtg;z{w8ct$)co)a&K7sX5BW$~(bO}sAN6mN;Q#XI6%@t$~Jd?-E=AB#`KXX115 zh4@l@CB7Elitoht;s^1Q_*wiSeiOfoKg6HnZ!wdUS;{J9ld?-Wq+C*NDUXy-$}bg= z3QB)Sg{2}=F{!vzLMkbhmdZ$FrSeh*siIU_sv=dDs!27Zno=#Pj#O8wC)Jn!lp0En zq$W~RshRYb)Iw@0wUXLMZKZZnd#R(;N$ML>M=21o;?!O{?E zs5DF(E{%{zN~5JQ(pYJ{G(nmuO_HWaQ>AH=Px4CvDJX@dh!mCLQbI~ffCNgA1WTxd zNw`EvltfF6#7Vp)NTMW5ilj=qWJsoDNsi=7o-|#WA4bDrIwhTv&PwN`3(`gDl5| zC%uwhOK+ri(tGKH^ildOeUZLO-=rVXPwAJGNzN>1k+aI#JS5xJ;bOfDgpluOB_<+5@)xx8Fat|V8MtH{;l>T(Uard(UDBiEJd%MIi|<%V)& zxry9V{!4Byw~$-Pt>rdyTe+RwLGCDblDo)V#`|ZvMsx^C#U7<@=SS_JX@Y4&y(lN3*?3JB6+dA zL|!H@msiLu<<;^Ud9A!&-XL$3H_2P%t@1W`yS!80CGVE^%KPN~@&Wmf{6G1yd_+Db zAD2(aC*{-f8TqVyUcMk-lrPCwXSbiculb_2k z@QpXD#|SNWU#L;flMl7GvYl`KkDCA*SC$*JU0@+f(gd`f<$ zpi)TrLn)#ZRf;Lam6A#+rL(N8|updMdq?K1yGupE5uhs0>mD zD?^pPm0`+oWu!7n8Lf;_#wp{K3CcuevNA=Ps$?iW#jgaEkP=oRN>qs}2_>lj3aCH| ztRM=e;0mFT3au~-tMH1Th>D~rimGUeu9%9Y*ovcgN?Mt&%ur@2vz0l@JY~MJK>0^m zq%2mJC`*;)$_izrvPxN_tX0-2>y?elCS|j-McJloS9T~nmEFo7Wv{YdIiMU=4k?F~ zBg#?bm~uinshm&MPS=H=n4mGEmOU3R4c1h)T(MVwT4<#t)#FtC z`f3BUq1s4otTs`bseh@>)fQ?iwYAztZL79dJE$GiPHGpmtJ+QNuJ%-WslC-cYCpBV zIzSz$4pxV#L)BsGaCL+_QXQ?1QOBy|)CuZDb&@(+ovKb#GgO}%P=jhn4XaT#rpDET z3aBX+R3Q~nQ592hl~gH}RvDF3c~wwFRaO;MRW;R6P1RCu)m1$;txi{GsKt{R zI$vF&{-Z8Z7pqIurRs8Zg}PE*rLIxes_WGC>PB^wx>?<#Zd13bJJg-(Zgr2kSKX%` zP!Fny)c>hR)T8P#^|*RcJ*A#j!r^XdilqIy}qqFz<6sW;S{>MixQdPlvh-c#?Z z57bBMWA%ypRDG_#Q2$e3s;|{I>Ra`l`a%7ueo{ZHU)68wclC$*OZ}~8(lTpVwQO2; zEr*s%%dO?n@@o0D0$M?>kXBeLq7~JOX(hChS}CoxR#q#gmDeh0m9)xQ6|JgPO{<~R z)M{z9wYpk8t-jVkYp6BS8f#6oX4+p`bFGEeN^7mP(b{V5wGLWGt&`S8>#B9rx@$eP zURrOhkJeA?uMN-!YJ;>P+EDFpZJ0Jf8>x-bMr&iWaoTuof;LH;tWD9TY8jeO^J@Vu zq=mJJ7S-ZfLQ86ZmeL>%)({QVaE;JNjnWv6)i{mUL`~9UP0=(>*9^_nY|YVJ&C}A_ z3~i=1OPj6D)#hpQwFTNjZIQNETcRz~mTN1tmD*};jkZ=>r>)mEYMZpp+7@k_wq4ty z?bLQ_d$hgUKJ9>ZP&=gkPdlO=)sAV$wG-MY?X-4AJFA`7E@&6EOWGCfs&-AguHDpb zX}7gI+Fk9Qc3*p-J=7j+Pqe4nGwp@;pY~FFrM=PKYVWl7+DGk^_F4O)ebv5eKeV6P zFD;XvSUs^mre0gGqu15z=?(Nh^@e&Qy|La@Z>ImHH`iP0t@PG<8@-+0UhklH z)H~^2^sah0y}RC1@1^(F`{@1j{`vrYpgu?+q7T*o)`#gM^pW}~eY8GSAE%GkC+HLP z$@&z1sy3%(+2lcQX(W81ykLyVt&{I07!#bj)I;ImksZ%z zYr3JEx~1E?qkDQ>JM_;Hf(wFE<^=0~UeWkuiU#+jv*Xrx_ z4f;lXlfFgYs&CV`>pS&b`fh!XzE|I`AJ7l#hxEhx5&fusOh2xl)KBTB^)vc8{k(ob zzo=i4k_>i6{f`UCx;{#bvaKh>YX&B$ToG;$fajXXv^Bfn9=C}{j)6gG+&MU7%c z38SP@$|!A=HOd*~jS5CZqq0%OsA^O*Y8W+*T1IW7j#1C3Z!|FeG#VL=jV4A@qnXj% zXkoN8S{ZGOwnjUnz0twwWOO#V7+sC-Mh~N>(aY#<^fme!{fz;}AY-sG#29M)Z45U? z7$c2Q#u#I)G0qroOfV)HlZ`3HR3pRi8Ga*R1dXr}F``DyNEk^2Fj59+zy@NV24>&} zX;21jFa~GvhG2+>WGIGeXohZ>hGp1>W4K1zm~PB4W*W1NImTRLo-yC}$5?1AG8P+4 zjAh1hV}-HOSZ%B^)*9=K^~OeHld;*@Vr(^BY=hm8Ljhm9k~ zG2^&#!Z>N1GR_!hjdR9%YnZjn+GZWI zu36t~VE$<~G#i;s%%)~D^Dncx+0txfwl>?C?acOO2eYHu$?RfwHM^PJ%^qeiv$xsD z>}&Qj2bcrRLFQm{sQI@!%p7ixFh`lA%`xU!bDTNBoM=umC!15uX=aA$GyP`J44Gjw zVn)rlnJ|+kV5UsSgiXXmP0S=r(xgn^PG9!ykK55FPT@&tL8QHx_QIA zW!^ULnE#r0&HLs9^P&03d~7~7pPA3i7v_KFEAzGa#(ZnOGe4Lg%}?fM^Q-yI{BHg* zf11C|Ojc$qi``S`Dp6R%5HF)y(?KYHqc#T3M~FHdb4! zoz=nWXmzqWTV1SfR(Gq1)zj)_^|AU|{jC1h0Bevn*cxIDwf?q-TO+KI)+lSVHP#wu zjkhLP6RpYC6lg}kLF$G*oI%}P?E?5_>OV(xUignGpZr!kMTDPn_*1y(W>z;MrdT2ee9$QbWr`B`ph4r8H z(t2gRvEEwmtoPOj>y!1_`eJ>xzFFU`pVlwyx0T7xY-hEz+1c$Jc1}CDoyX2==d<(M z1?@uiA9i88h+WJsZkMo2+NJC=c3HceUEZ!>SF|hJRqU#EHM_c9)2?OLw(Hn+?fP~D z`%k-}-N)l+ptaBwjJBGJv(jBuxHw{ z?Ai7ld#*j-USR)YFSHlgOYEigGJCnb!d_*sw%6Ee?REBgd!xO{-fVBNx7yq79rjLp zm%ZEGWAC%~+Xw7}_96SQeZ)R$AG43!C+$=AY5RWJ^Q}>zxh@9huvNBfig#r|r4v%lLv z>|ge8JCl>y$>L;nvO77PoK7w$x0Bb&=j3+^I0cr@B+asp-^mYCCnEdQN?(f%B)+$Z6~}ahf{KoWGnFPD`hi)7oj{ zv~$`!9h{C%C#SR1)#>JRcX~KIo!(9#r?1n`>F*41204SBA;0XM!`)ndD4yraIG{49DmAouCtP!cN4AIx#2VBptv>IiLeOh=V$qgFA#nIkdw# ztiw5iBRY~JJBp(^x??z|V>!0tI-ZkuraLp7na*ryjx*Pp=gfB&I18Oc&SGbYv(#Dc ztZ-I3tDM!&8fTre-r3-6bT&DgovqF`XS=h*+3D-O_Fux2#*vE$>!vE4h{3DsEM`np@qi>DF>< zyLH^UZauew`={H`ZR9p~o4U>1zue|-3%8})+HK>ub=$e^-41Rix3k;D?do=OySqKz zUT$xF6(kG?~1PE%C6$7uI3u9=~}Ms zImJBczM12UIDM5SIGOrE9@2Z zih0Go5?)EKlvl

y`7$dlkHjUS+R}SJkWLRrhLmwY=J19j~re&#Uh>@EUrJyvAM= zuc_C}Ywor1T6(R#)?OR0o!8#$;C1vmd7ZtkUN^71*Td`S_44|7eZ78Oe{X;{&>QRx z@rHVTd&9ip-Ux4$H`*KHjrGQPCCwq#gdb($Lre}G!=XkD{_NIF?yqVrCZ?-qr zo9E5<7I^=73%$kO5^t%u%v-gE|4ymE|mTwT_jyJT`XNZT_RmFT`FBB zT{c}VT|QkQT`^rbT_s<`gTbT_asHT{~STT{m4XT|eC*-7wuK-8kJO-89`S{a3n0 zx@Edmx^=oux^23BxLGyx=Xrix_i1ux@Wpqx_7!yx?j3~dO&($dQf_BdPw^3 z^sw~s^oaDx^r-Zh^w{*c^!W6I^u+X}^py0}^t5zF+L!jH1L;sYoQ|ZU=~z0RPNso$ zDh;NgG@M4#SQ<|gX);Zv=`@?>(tP^=4BZ2>7&{aI;M%rr+qP}nwr$^HZD;LmZ*AMQ z?N`5@?=NIBCplUTt%24;>!1zLMraeX8QKCRKwF{h&<+TKUI>< zNPt90f@Da60Hi@WWI!flK{n(-9^^v-6haZS6H0`3LwlgT&^~BCbO1U89fpoTN1 z1($)#!sX!da0R#`TnVlMSB0y=)!`a&O}G|Z2d)d(gX_Z$;D&G`xCz`8ZU#4pTfi;h zR&X1*E!+-n4|jk&!kyqQa96k++#T)#_k?@Fec--uKe#_U03HYrf``CE;bHJ_cmzBW z9tDqq$HL>_@$dwAB0LG60#Ajf!PDUx@Jx6XJO`c&&x7Z~3*d$DB6tbB6kY}|hgZNW z;Z^V&crCmRUJq}8H^Q6XEpP(772XDKhj+jbjKC<2!8lC7Buv3H%)%VZ!vZYA5-h_2 zR$&d+VFNZ{3wB@^_Fx|l;1G`BU2r128{Px&h4;bx;e+rY_%M6~J_;X$kHaV6Q}Ai{ z415+o2cL&8z?b05@D=zfd=0)1-+*tyx8XbRUHBe+AASHof*-?A;HU62_&NLnehI&Z z-@tF-ckp}o1N;&G41a;Y!r$QU@PF_R_!s;e{saGo|G`O+q)2ik1(Fg;g``H(AZd|w zNCqS$k_pL-WI?hb*^nGaP9ztS8_9#@Me-p9kb+1dq%cwhDT)+BiX$bFQb=i}3{n;; zhm=PuB9)NJNEM_iQVpq&)Ie$>wUIhVU8EjTA8CLzLK-7Ykfum8q&d<8X^FH(+8}L_ zc1U}q1JV)cjC4V|BHfVgNDrhZ(hKQ>^hNq1{gDC4Kx7ay1R079Lxv+GkdeqJWHd4s z8HbEVCLj}$NyubmDl!e3j?6%2BD0X$$Q)!IG9OuhEJPL|i;*SBGGsZj0$GWyLRKSd zkhREqWCOAh*@SFHwjc?}He@@p1A!12K@b$d5CS0)3ZW4OVG$1D5fPCP8Bq{`sECFb zh>2K;jW~#lc!-aLNQCS}b|Hz#Ze$O#7uk;-Kn@~@ki*Ckngz{@W<#^1InbPFZZr>?7tM#}M+=|@(L!huv?y8(EsmBzOQNOFGH6+}99kZ& zfL25+p_S39Xf?DtS_7?#)>yc8af@FfzCu{p|jCB=sa{jx&U2>EbHgpFHp)iV|D2kytN}v=tu(Vh@EIpP1 z%YkEtTI*wtBO^_YG5_7T3BtY4ptYdhtxuQkdSiXCzF2>305%XCgbl`qU_-HC*a&PSHVPY!jlsrZ z)9hGPUqVl>8JEXH9xCSW2aVKN3V71J;s zGcXggFdK6)5A(4A3$X~>iS5D?vEA5SY#+8CJAfU;4q=C}BiK>wICcU%iJihuV`s3l z*g5Pxb`iUTUB<3pSFvl@b?gRq6T6Mw!R})Bu>05p>>>6DdyGBBo?*|i7uZYe74{l? zgT2MxV;``O*eC2W_67TjeZ#(EKd_(JFYGt=2m6ct!;;`h@#J_4JSCnAPmQO+)8gsy z^ms-*6P_8*f@j6E;o0#VcuqVwo(Ip1=fm^k1@MA+A-phN6fcGs$4lTP@ltqcybN9z zFOOHiE8>;#%6JvLDqan*j@Q6z;kEHPcwM|6ULS9OH^dv^P4K38GrT$80&j`8!dv5Q z@OF56yaV15?}T^8yWm~%Zg_XRC*BM1jrYO(;{EXc_yBw$J{TW@55~!^h(j@QL^&d@?=-pNdbzXW%pOS@>*x4n7y3htJ0s;EV9Z_!4|6z6@WEufSL0 ztMJwMT6`V89^Zg(#5dua@hx})z75}w@4z7(#t|IFF&xJUoWg0G!C9Qcd0fCnT*4I` z;3}@+I&R=5Zs9iW;2!Sd0UqKJz7yYtC*r&DJ@`I+KYjo|h#$fa<45qL_%ZwheiA>0 zpT^JNXYq6RdHe!?5xS!gA~TVN$Vy}*au7L*Ttsdn50RJ1N8~385Cw_CL=mDWQH&@~lpsnH zrHIl*8KN9fo~S@nBq|Y=i7G@@q8d@1s7cf!Y7=#cx0uU;p5jtTICSef{;SwI<69Ewt5wVllMI;itiM_-=Vn1nVL*PrX|yn>B$UaMlv&*h0IE3BeRn^$ed&@GB=rr%uD7c3y=lLLS$jG z2w9XYMiwVakR{2|WErw7S&l4ERv;^qmB`9u6|yQ>ovcCDBx{ki$vR|RvL0EVY(O?7 z8kk;SbvNs<&vlPt-RJSmVODUmX%kbqQ4jWkG;v`CwDNSE|TpA5*5jL4m2BDtH~L+&N_ zk^9L5 z4tbZnM?N4Ql8?y8>m6l3JrKd7b8L3QEW-1GnmC8=#pmI{VsN7T@ zDle6f%1;%b3Q~orB2-bT7*(7qL6xLRQKhLeR9UJVRi3IyRiY|WRj8^|HL5yQgQ`i@ zqH0rhsCra=ssYuIYD6`rnov!tW>j;k1=W&jO|_xgQthbrR0paf)rsm%b)mXa-Kg$V zPpTKyo9aXLrTS6*sR7hLY7jM;8bS@DhEpS`k<=(^G&P1AOO2z(Qxm9()MRQ3HIKJvLIzgSJPEn_+Gt^n?JavJ(NL`{X zQ&*^~)HUilb%VM|-J)(&cd2{Sed+=Aka|Qtrk+qwsb|!4>IL^^5vV{h|I+|EMH%QaTx(oK8Wfq*K#r=(Kb?Iz63%&PZpX zGt*h6bbh)3U63wB7p9BQMd@O63A!X*iY`r;q07?c=<;+0 zx*}bPu1r^#DbVIrk-I#7lH=~==E$EhXE4nq^hHgu@ zqubLR=#F$Jx(nTv?nZZ~d(b`UUUYA|58apUNB5@((1Yl~^bmR|J&YbskDy1=qv+A} z7q$kmn=_&M7dKx{QoTBCK^piSDMZQ7w-+M|6sphJ2my^Btychh_5z4ShMKYf5cNFSmP(?{r|^fCGb zeUd&!pQg{yXX$hFdHMoFe|j`X+sgzD?hu@6z|^`}70)A^nJcOh2KY z($DD^^h^2`{hEG5zop;N@97WpNBR@}nf^k5qrcPt(Ld;)^e_51{fGWb|D%&INtt9! zawY|nib>6+VbU_`nDk5rCL@!H$;@P7vNGA2>`V?O7n7UG!{lZ1G5MJSOhKj)QTJOj%m+yU^+6Ln9fWWrYqBp>CW_EdNRG4K1^SxAJd;1 zzzk#tF@u>Q%ur?+Gn^U0jATYLW0zMV-24*9(iP_9-VG@|F%r<5_vx9*c zm_Zqg!5M-f8H%A9hG7|w;TeGu8Hte@g;5!e(HVm=8H=$QhjAH?@tJ@LnTXlR>|zp` zJTC_RCR>ZG!`5Z%vGv&oY(usY+n8;_Hf5W!&Dj=gOSTo;nr*|jW81SG*p6%` zwlmv>?aFpzyR$vmo@_6+H`|Bp%l2dYvjf>d>|k~XJCq&94rfQOBiT{xXm$)cmL12A zXD6@|*~#n_b}BoKozBi+XR@={+3Xy4E<2B%&n{pWvWwWo>=JevyPRFYu4GrStJyW| zT6P_~p54H1WH+&!*)41WyOrI>?qDGnW)T);F&1YDmSicGW*L@cIhJPyR%9hsW&x|R z8mqGgYqAz=vkvRB9_zCK8?q6*likH8vU}LQ>^^osdw@O29%2u(N7$q6G4?onf<4Ke zVo$SY*t6_8_5yp6y~JK-udr9yYwUIQ278me#olJ`uy@&e?0xnD`;dLaK4G7-&)Dbe z3-%@Ziha$#Vc)Xv*!S!Q_9Od={mg!0zp~%h|JWbwPxcr4oBhN7W&g2BxTIV%E;*Ni zOUb3;Qgdmzv|KtaJ(rQo#AW8Pa9O!*Ty`!8my^rI<>vBmdAWRCey#vlkSoL$=8AH~ zxZ+$1t|V8AE6tVR%5vqn@>~V3B3Fs4%vIs4a@Dx%Tn(-kSDUND)#d7O^|=OIL#`3m zm}|l{<(hHLxfWbYt`*msYs0nW+H)Paj$9|MGuMUd%5~$qb3M48TraLS*N5xN_2c?; z1Gs_QAZ{=>lpDqk=SFZNxl!C`ZVWe;8^?|3CU6tEN!(;^3OALT#!csDa5K5t+#GH$ zH;l+%@hx zcZ0jh-QsR@ceuOUJ?=jDfP2V2;vREPxToAR?gjUfd&Rxx-f(ZZciemK1NV{p#C_(z za9_D^+;{Fj?g#gi`^Ej{{&4@eBz#gn8K0a_!KdU?@u~SVd|EyopPtXaXXG>SnfWYy zRz4e_ozKDNcfy#kc0$^6mKc zd&DW_=Wr;elfpm%dg|t^Bee${3d=gzlBfWxANQg?L5T8Ji?Fn@$U z${*v8^C$R|{3-r4f0jSTpXV>|7x_#4W&R3(mA}Sc=Wp;g`CI&L{tkbazsKL_AMg+P zNBm>{3ICLT#=qcS@~`;U{2Tr)|Bippf8amzpZL%G7yc{%jsMR7$N%7e^1t}s{2%@= z|Bp{1Bo&eg$%T|cDj~IyMo2596VeMAgp5KaA+wN0$SPzLvI{wcoI)-kw~$B3E94XM z3k8INLLs5BP(&yu6c&`fA9v=CYft%TM>8=R zVVW>qm?6v*W(l)}Il^3Fo-kinAS@IX3rmEh!ZKmGutHcVtP)lWYlOAJI$^!ALD(p4 z5;hB4gal!$uua%5><}OU77zgyFaZ|`ffQ(g5m5+egtNjq z;k@IZJdJQAJ=Plac~bK!;XQg|i2 z7TySNg?GYx;e+r|_#}K5z6f81Z^C!sKjDY)Q}`wP7XAo-g?~a4F{zkLOfIGnQ;Mm? zG-6sYotR$CAZ8RZiJ8SLVpcJmm|e^v<`i>@xy3wUUNN7TUo0RN6bp%k#Uf%+v6xs~ zEFqQ@ONph$GGbY=oLFA0C{_|Hi&ey`Vl}b4SVOES))H%rb;P=2J+Z#nKx`;B5*v$6 z#HM01vANhnY$>)9TZ?VPwqiT6z1TtQD0UJ%i(SO7VmGme*i-B!_7?kyeZ_uae{p~~ zP#h!<7KeyK#bM%bafCQh93_qx$B1LaapHJ!f;dr}Bu*Bmh*QOB;&gF_I8&S@&KBp0 zbH#b$d~u<;NL(x~5toX~#O2}&aizFQTrI8<*NW@J_2LF`qqs@jEN&4K#I52sal5!f zghW_GL{!8?TqHzNq(oX|L{{WPUKB)8lto1ZqAF^lE*hdKTB0pFqAPl$F9u>LM&eF! zmzXH-7WasI#eL#_@qlcNa!Yxnyiz_Xzf?dfC>4?lOGTujQZcExR6;5#m6A$JWu&rF zIjOuTKk}6A8q^eRisk&4{swvfy>PU5^dQyFLvA-`bd4Heo}vFfHY7VBn_5^NJFJz z(r{^nG*TKRjh4npW2JG@cxi$(QJN%8k)}%1r0LQOX{Izwnk~(d=1TLV`O*Sup|nU^ zEG?0iO3S3>(h6y%v`Shnt&!GB>!kJ425F(l%+kv_pa4J1ox+Gneu1Hs4Wr1`Ye5szDnPu z@6vzL59z1$OZqMSk^V~mq$F}uIhmYXP9dk1Q^~32G;&%wot$3IAZL^_$(iLWa#lH; zoL$Z#=ah5Fx#c`^UOAtfUoIdQlncp)GICkDoLpY6AXk(t z$(7|Qa#gvSTwSgq*OY6?wdFc;UAdlIUv3~blpD#71ygWgkC{L0n%Twg3@-%t6JVTx-&yr`$bL6@5JbAvnKwc;>k{8QM^^mkF7aDVdfT znUy)2mjzjrC0Uk%tje0K%Z6;qmTb$8?8=_(%Yhuqk-SshB`3%(ud|tjFUz9J&m*p$+Rr#8HUA`gTlyAwm}_x{9gVbf0RGTpXD#|SNWU#UH(u0 zA^((r$-m`4@?ZI%oJ2{gBvXivMV{1 zoJuYww~|N6tK?JiD+QE-N+G4NQbZ}L6jO>TC6tm%DW$YhMk%Y5Q_3q9l!{6vrLs~* zsj5^{sw*{=no2FDwo*r_tJG8KD-D!}N+YGQ(nM*hG*g-@EtHl@E2Xv4Mro_GQ`##X zl#WVgrHj&4>85m7dMG`WUP^DJkJ4A^r}S3_Cjxtx7r_5ItC<~QE%3@`SvQ$~7ELT=2 zE0tBsYGsYGR#~U4S2idcl}*ZKWs8!aY*n@?+m#&(q`(TIpbDno3Zak+rO*naunMQ} zilB&!q{xb*07X?aMOO^PR4m0-9K}^U#a99)R3c@kvP(%+b}M_7y~;jizj8o1s2oxb zD@T;0$}#1*azZ(&oKj9JXOy$bIpw@^LAj`0QZ6f3l&i`$<+^f1xvAVzZYy_`yUIP~ zzVbkMs60|0D^HZC$}{D;@pf%G5)$Otll%peQM3bKLhAP2|^a)I0+ z56BDhf&8EVC7%Ag9U3aWwXpa!T3 zYJu9I4yX(2f%>2UXb2jC#-Isk3YvlDpap0NT7lM}4QLD6f%c#S=mREYgArgP7zIXyF<>kh2gZX5U?P|VCW9$p zDwqbQgBf5Zm<48oIbbfB2j+tXU?Erp7K0^VDOd)UgB4&USOr#tHDE1R2iAiPU?bQB zHiIo70c-`^z;>_$KmY~^Kmi7DKmZa@fCda;0S9;>01-$)1_}T`1sc$S0Zd>48#urP z9`Hc`LJ)zSU>8UPyTKl?7wiN3!2xg(90G^I5pWb71INJ$a1xvXr@t??1J}U~a1-1Dx4|877u*B)!2|FRJOYow6Yvx~1JA(=@DjWNufZGe7Q6%R z!3XdWd;*`r7w{E)1K+`au^-?k_yvB0Kj1I;2a>2s)nsaNHHDf|O{Jz*)2M0HbZUAv zgPKvzq-IvLs9Du)YIZe;np4fC=2r8ldDVPsezkyFP%WeuR*R@b)naOKwS-zyEv1%L z%cy15a%y?Cf?83nq*hj|s8!W!YIU`ST2rm1)>iANb=7)meYJtwP;I0(R-33z)n;mQ zwT0SJZKbwW+o)~Tc4~XIgW6H;q;^)js9n`=YIn7V+EeYN_E!6-ebs(ye|3O5P#qK- ztPW9!s>9Ub>IikDI!Ya_j#0;|{^n>I!wGx=LNGu2I*j>(uq?26dylN!_e&Q4`dy>Na(|xT4hvLy6|+@GbyZLG)j$o^NZqOK zQWMqP>K=8kx=-D&9#9Xeht$LB5%s8gOg*liP*19-)YIx2^{jeMJ+EF+FRGW+%jy;N zs(MYmuHH~@s<+hJ>K*m2dQZKtK2RU3kJQKN6ZNV3Ont7tP+zLA)Ys}8^{x6&eXo8{ zKdPV9&+3=hSM{6vUHwn}q5f2VslU}f>RcVw2E3Kt+G}{s~W4ORo7~0HMLq=ZLN-0SF5Mh*BWRIwMJTFt%=rDYo;~V zT4*h`R$6PVjn-Ccr?uBQXdSgqT4$|`)>Z4Kb=P`mJ+)q1Z>^8kSL>(s*9K?S7{Mrb3oQQBxNURb1!)y8S#wF%lpZIU)wo1#tCrfJi)8QM&3mNr|Pqs`Uk zY4f!O+CpuSwpd%DE!CE3%e58SN^O<4T3e&7)z)e2wGG-vZIiZH+oC0CTeWT4c5R0S zX|RT9sD^2{7AH70Qlm6lV>DLdG+q-lQIj-TQ#7Ednx^TRp_!Vc*_xxdny2|%poLnb z?bLQ@iP~;$kG5Car|s7cXa}`J+F|X8c2qm29oJ51C$&@BY3)qxtaeU2uU*hCYL~Rj z+7<1pc1^pk-Oz4ox3t^Z9qq1mPrI)@&>m`!w8z>L?Wy)md#=6EUTUwj*V-HHt@ciP zuYJ%yYM->v+86Ds_D%b){TKV8{nUPGzqLQwU+tfkL{F+G)068d^ptukJ++=jPphZX z)9V@ZjCv+Lvz|rIs%O))>pAqCdM-V;o=4BC=hO4+1@wY?A-%9(L@%lr(~HMS=q2@1 zdTG6kURE!sm)9%k74=GbWxa}CRj;O3*K6oC^;&vuy^da2ucz178|V%7MtWntiQZIi zrZ?AH=q>eDdTYIn-d1m?x7RzwI_jPD&UzQUtKLoTuJ_P;>b>;ddLO;7-cRqZ56}nd zgY?1r5PhgVOdqa~&`0W{^wIhleXKrCAFof)C+d^*$@&z1sy;0?U7w-P)Mx3l^*Q=n zeV#sFU!X747wL=jCHhi*nZ8_Kp|8|e>8tfM`dWRRzFyy;Z`3#GoAoVvg1%MXrf=7G z=#UQUNDS369oGq+)G3|T8J*QRo!13j)FoZk6&>iRuIajN=%#Mzw(jVz?&-cB=%F6z zJM~?9qP|<-6Wgor)A#EK^n>~#{jh#SKdK+okLxG&llm$Bw0=fEtDn=)>lgHk`X&9c zenr2kU(>JaH}sqOE&aBBN58Azi`~~B=nwTr`eXfx{#1XaKi6OAFZEaYYyFM>R)43z z*FWeV^-ua|{fqup|E7P}|I>fyKlNYwZ~c${SO2FcF_IWbjbui0BZZODNM)op(imxt zbVhn3gOSn5WMnq77+H;MMs_2Ik<-X!KgTo`bGnzq0z`_Y&3~A zHJTaCjTS~rqm|LxXk)ZB+8OPQ4n{|#lhN7eVstgS8QqN@Mo*)c(c9=_^fme!{fz;} zKqFq9YYaAq7(Sw(U@dRHl`R;jcLYoV}>!)m}Sg1 z<`{F0dB%Jr9{p)7G!_|)jU~oXW0|qsSYfO*RvD{}HO5+Fow457U~Dut8JmqQMuM@` z*k)`ub{LQW8;B7Xml>FW8-zg`ltCMe!5W;w8-gJkk|7(40Swj94Bapc)36NNa17V* z4BrThFcuj*ja^2fvD?^V>^1fo`;7y}LF15d*f?SwHI5m_jT6R6+$E<7CGwYiTVhznk zW@EF7+0<-iHaA}Ga1dzd|Az0BTbAG5F7 z&+KmwFbA50%)#ambErAY9Bz&bPa=4Nw? znP6@;x0&0`9VTSLCSsx{X5uDcl4hJ>H))eGS(7t)Q!qtSGG$XSfvK9BshfsrnwDvs zj_I17>6<|;G$V7Txywv6cbj|6z2-i1zj?qsXdW^Tn@7x}<}vfQdBQwto-$9HXUwy) zbLM&Tf_c%rWL`F}m{-kf=5_OidDFaQ-Zt-;cg=g|ee;3&(0pV*jy*A-n$OJV<_q(s z`O184zA@jL@67k+2lJ!($^2}7F~6GM%q&8-$zORJUD+G=C9wc1(jtqxX4tCQ8)>SA?`b+fu#J*=KqFRQoJ z$LeeKv-(>Dtbx`bYp^xM8fp!*hFc@7kygAQ(;97!vBp~CtntDCNurZvl&ZOyUfTJx;=u?5ybYmv3sT4F7=mRZZK71l~?m9^ShW39E;S?jG0)<$cS z6*uHsTdV|YtF_JAZtbuj3$_powJ-~}2#d5Ri?$exwK$8n;tyg=v?NQm6br;*q-N=s zVVRa?*_LCumS_1^V1-s>?TqcR60P0V9&4|)&)RPtunt;>ti#q3>!@|iI&Ph?PFkm| z)7F{TS?ip2-nw92v@Thftt-}5>zZ}lx?$b4ZdtdjJJwz6o^?O=z$&y9dTG6~UR!Uhx7IuBz4gKRXnnFi$G%u!t#8(M>p$y<_0#%g{kHyCf31I35<97# z%ua5nuv6NpVyW#kc3L}~o!-u1XS6ffne8lgRy&)W-Ogd>v~$_H?L2ngSUx+yUBE7A z7qSc6MeL$>F}t{3!Y*l-vP;`#?6P(_yS!Z?R?)6xSGKFzRqbkab-RXL)2?OLw(Hn+ z?Rs{7yMf)%ZWL>5H?f=A&FtoO3%jM=%5H79vD@11?DlpCyQAI7?re9lyT-cN-R&NB zPrH}h+wNocwfouq?E&^cdyqZY9%2u*huOnpBkYm(D0{R$#vW^rv&Y*L?1}ayd$K*n zo@!6Cr`t2^nXy^+YY^Xh-%=dzYPP@3!~E_S*aG{q_O-pnb?bY#*_Y+Q;nU_6hr> zeab#?%NOShxQ};vHiq; zYCp4|+b`^w_AC3f{l?O*n9`;YzC{%0p~ zk~m47WKMD?g_F`r<)n7fIBA`9PI@PUlhMiKWOlMRS!3Cp>`o3Rr<2Rc?c{OtI{BRZ zP64N&Q^+ao6mg0=#bU*s5>82{lvCO%(q1VI}MzMP9vwW)5K}&G>bKNS~xA8R!(cDjnmd?=d^b^I31l%PG_f!)79zb zbdU9LdOE$F-cBE9U&N63tY=yJZS>>#D);MdO zb~{`02V;kv!_E=ssB_FY z?woK=I;Wh|&Kc*dbIv*MT!>wCE;*N-E6!EtnseQ`;oNj?Ik%lV&Ryr8bKiLod+0oJ z9y?E*r_M9yx%0w#>AZ4YJ8zt~&O7IQ?1S^s`Q&_dzBpf{fBBy4Bq3ZVk7l zTg$EO)^Y2`>bdpZ25v*Qk=xj9;x=`gxy{`cZcDe7+uCgtYwNai+q)gyj&3Knv)je( z>UMLxyFJ{VZm(Exw~yP`?dSG)2e<>>LGEC8h&$9B<_>p9#74TK+|lkBcdR?k9q&$X zC%Ti|$?g<)syoe{9-HCLbZ5D<-8t@Dcb+@nUEnTs7rBewCGOJLGIzPV!d>aEa#y=+ z+_mmHcfGs8-RN#|H^;WP3GP;Ro4eiJ;X*F#A};D;F76U;T%_qzF6}Zd>vAsd3a;o% zuIwrlw*K>V0a6>n8ce=Y`iSBN9kGt31=k9k8xCh-s?qT=HPr9ev)9xAftb5Kq?_O{(x|iI`?iKfH?3#Psz2V+;Z@IVKJMLZgo_pVY z;68L8#U8s)+^6m{_qqGRed)e(U%PMIx9&Ulz55~d(f#CpcE7k^-EZ!9_doZC`_uj9 z{&xSw{<{C%BwkW4nU~y4;idFad8xfLURp1mmp+!k%jjkDGJ9FPtX?)RyO+bu>E-fr zdwF7cy?kDNuYgz3E94dSig-o6VqS5tgjdol6)WwP@ydGTyz*WJucBAUtL#aiMLO|O<$+pFW%_3C-`y#`)GuaVc-YZ7bfHS?N#ExeXqE3dWJ#%t@f^V)kIypFL> zUT3e1*VXIhb@zIBJ-uFDZ?BKn*XtMS?+x$d-Vkr7H_RLEjqpZ#qrB0vG2U2j zoHyQ^;7#-9}KjyKnv=gs#PcniHn-ePZww=}lQTkfs! zR(h+v)!rI!t+&ow?``lldhxIpZ?m_>OYpXO+q~`G4iEBR5AjeB^WtKAkMKy3@@S9o zSda5~Pw+%f@?P7*OZ0Yod%V5gK5xHw zz&jW_|OD$de^+`-VN`jcPn<= zyW`#U?s@mU2i`;Pk@whp;yv}A#h!aFyqDf9@3r^Fd+WXP-g_UskKQNmbL@-v)%)gs z_x|&Kct5>g-f!=Z_t*OuOX4T>lljU06n;uSm7m&A&xoPI7pcPx*e*U#tY_Y3$1{X%|WzldMdFXk7ImGDdYrTo%<8NaMw&M)s*@GJV2 z{K|fnSXIB8U)`_a*Ys=owf#DNUB8}R-)|6W=r{5k`%V0&elx$h-@T`}+O-{{8@epg+hT92?>f^@sVx{Sp31 zf0RGkALEbp$NA%96a0z(B!99$#h>a=^QZeW{F(kNe|Bt+Ki8k<&-WMj3;jj@Vt}f6zbVANG&Ux{7yuld*g z8~#oImVev7=}_doa_{ZIa9 z|BL_C{}%i1|L6bkfBL`t-~J!}um8_a5+sc!3z7#Zf|Nn3Aa#%?NE@UJ(gzuWjIm5X z<{(RuHOLlZ4{`)KgIq!GAWtlBkT1v|6bK3ig@VFCk)UW$EGQl;5tIx{1*L;BLD`^O zP(G*-R17M`DhE}9szJ4&dQc;%8Pp1D2X%tFv3f!Mph3_uXcROKngmUQW!bI~q(4rUlc38Ntk8Rxmr56U+_f1@mJI zf`!4NU~#Y{SQ;z~mIo_>l|ej)Hdr033DySdg7v|MU}LZ;*c`;eYl4JeYp^ZY9_$F9 z01l7<4X_|?;0cI;45)w(n1BtqfDeQ~4B}*XAO}hS0yWSAJuu=&t`*op9GC=d;01mV z1Yr;bJA++8Vz4{5C)gY83-$*Gf`h@K;BasxI2s&_9S=?fCxcVL>EKLoHaHiY4=%(m z2A6`%!Ij`@a4onV+z4(4w}RWTJHg%HUT{Bn5IhVX1&@O#!PD5Y;Cb*Ocp1D3UI%Z2 zx52yMeefanG58dG4!#6mgKxq2;J@HU@H6%+_#ONS{s#YoBw^ApS(rRb5vGi#3R8z^ z!n9$!FnyRI%ot`0Gsm)oS;K5$_Ap17Gt3p{4)cV0WBJ1TVS%t?-d4;zFH!$x7_ zuu0f7Y!)_;wFp~=t-{t}o3L%zE^Hrm2s_3)g`LALVb`!**gfnK_6&Q4y<>gCzG1(x ze>fl<7!C>theN`lv0>rxa6~vV92JfZ$An|UapCybgm7XwDV!Wm38#kB!s+3RaAs^) zI6IsZ&JE{<^TP$R46JO z6^V*Q#iHU-iKt{$Dpoow6P1n1MdhOkQN^fIR5?~9sv1>`sz)`Vno+H&c2p;-8><)9 zj~YY`qefBVs7cf`Y8GoAwTN0qt)kXZo2YHnE@~fjh;@uQMV+HAQP-$j)II7E^^En3 zdPjYtzEQuZe>5N(7!8UB$A&~hqhZnTXhbwJ8WoL>#>B=(KNAaNdXl1l2S{<#4)<)~1_0fiC zV{B8jIoc8>L|db6(e`LZ1VwT4e1t@3ghhBnL}WxobQBl7M{LAJd?Z9-Bt>$hL~-&p zQX?(WBO@{+E3zXeii6w8i~J~v!YGP%M!TZKXm_;de~RvbeU7sW19)uPwr$(CZQHgR zqe z4Ty$BBcd_U1ey}fh~`8Kq9xIaXic;s+Cn>`J<);aNOU4P6J3a|L^tS8^dNc?y@=jK zAEGbOkLV8rh=If)VlXj;7)lHyhQkPABr%E@O^hMN661*R!~~c~Od=)|Q;4a=G-5h2 zgO~}kh}py(VlFX{m`^Mq77~kKF|mYLN-QIm6Dx?7#42JntRdDC>xlKl24W+ziP#KV zh^@pnVmq;e*h%anz9A3-MeHH=68nh#!~xgJn)Hth!9aCBw|FINDxUlOPnLl6Bme!#3kY~aRsgt z*NE%H4dNzoi?~hPA@0IG;y&?!ct|`V9urT9r^GXOPP`yq60eBY#2ex*@ebY-ABc~{ zC*m{lg-AuFhBRbaG98(o%s^%&Gm)9eERdDVMrJ2-kU7a*WNtDKMKvpCxk(J3RWL2^nR3~eYHOX3JZL$tom#hc% z$p&OYvJu&sY(h3An?ZB31=*5pMYbl}kZs9!WP9j9b|gEIoyjg_SF#(~9eR*G$zEh{ zvJcsp>__&80pvh(5IL9}LJlQ|k;7pGIg%VjjwZ*DW65#kcya$YT4mp>cN6se~z(R5nxtLr+E+v@&Wicm$VVpMUe1XYqM1*NGnR9UJVRi3IqRfI}ZWvU8Qm8wQnr)p3&p%zt}szcSK z>QVKn22?|6L^Y4!t*JItTdEz^p6Wn#gicgvsteVX>PB^^dQd%~ z7uB2UL-nQlQT?d_)Ibaz=r8G*X49cV|uqlUfDUb50fQnF22&otqrxH|>I!m2{^V9|EB6W$n zOkJU_QrF-*b%VM|-J)(&cc{B?kGfAipdM0>sK?Y3>M1;MQVm((ljHT8yi3-74+ z)CcM#^@;jSeSuVTYB~*_mQF{fr!&wQArqaM&O&FUv(ee<9FUXFMdzmT(0S>6bbh)3 z6r>B$h3O)6QMwpi97@n7=~8rQx(r>GE=QM#3Uo!f5?z_DLRY1$L3O$YU6Zaw*QV>x zb)g1p(IdImicX3?|hIrLn59zCDNB58Udy@*~+FQJ#x%jo5>f?i3lqF2*v=(Y4ZdOd8Q zH`1Hv&GZ&}E4>Z2(>v&$^e*}vdN;iX_R{<4{qzC)Abp5N#07nX{+2#UAEUpczo&no zF&;$!NdH8ipik02)4$MYIHOO|ztO+bf6#x@f6*uh(5LA$^#AC8aIE@|#zzlL&?Fuq znr3L0=4c)STBIdfrWIPHHPC5;Hff8tX@_>fqkTG{BXpDw=@`W61f8VM(&y;&^ac7N zT%s@2SLmzsHTpVz18&l{=-c!i`YwHsz7G%Rhx8-*G5v&oN^6jPch!<2<`OnIgPQ<15}RA#C`Ri+wKovFdpWNI4loCqL(E|~!hFjdWsWi5G2b&7 zC1j2>KQcctCzz8AT9%n#m|vMw%x`E>`GY|Tn)wSwrPItA=6}pT3=ZeafB1AJ7?PnF zn)&);8IIu@fe{&rkwIZpMq_lwU`)mWn{gPI@fe>8m;{9y3pvr_3|vIr9QuGOw7|%p2w{ z^A6rKADEBKC+0Krg-OMxVpFqe*tBdqHa(kx%?O#;%xo4mE1QkY&gOueY%Vr8n}^NI z=7aof0k$Aph%L+(VT(dBwm4gYEy=MpVe7K>*!pY(Xvj8V8?#N=rff5|IkaF~vaQ(GY#X*Mv}4<|9oUX+C$=-&1-i1` z*zRl(wkO+*?G1g{zHC3XKRbXO2!q(c>=1S+JB%I9j)0NuD0VbEh8@d}W5>e;b|O28 zoy<;Qr@}OLIy-}%$}GZgyOl*iKD(XW!R};tvEQ(}**&nA-N)`{53mQ>LvWZq!hXvhWskAn zu^2mMe_)TZKe9iuC)krLn%~)9*k9RG>~HMvEZSSxKiR+7zuD958TNl)uLRkD+5gxS zd`y!p{?b^QWmuNwSe_MF5hPY-6;@?6RtJMMS&Ow?8IuJYk=*&)Dbe3-%@Z z3SP5s*thIE_C5OnKC++K&+HdA6_=Vz18KQ*TzW18myyc^nYk=nRxTTtoy!3^xm;Xs zE)SQN%g5!10$f3^5LcKh!WD<yd@hSCT8mmFCJoS*{#co~yuBLarL1A*N|((HRhUdO`#droNK|gMciU;3AYrMam%?C+)8d0w;I-P zYq@pYdTs-^5jJs~xh>pQZX36qLu@>^liS68!|mqwaC>1Nx1T$}9pnyih;ZVLaNlxA zxntaS9ERArAGqV(kK9k(3GO6^Q3~!C?pN*<_Z#;+hh{16Pwp@7Z|*dAhC?ZW`-l6N z`;SZE2o4|E9L3Qb!?7I4@tgo6Cvh^Ta4M&P&KaD^S)9!|;Bp@4a{(9Oq7ZU1F3u&m zBzG3hap$=U+(qsZcbU5aSGjB4b?ydtle-1CxjWol?jCoadjJo)N8DrX3HOwH2G6+{ z+)M5i_nLbHZ@G8ed+r1Gk^2Onxi4HQJ~f|)Ps^u+^n3zA#^eFUl8#;(Q6dBwvaz&6k0)d^x^6UxBa4SAxoX6}~E8 zjjztvfSPW8^%s1hiLNmTO--2(+x8hqv8@?^yj&IL*;5$Mm zzBAv2@5*=MyF(AYC*Oj33UA;77tJel$OZAIp#9 z$HN4EB0q_r%unH`!Zdz5KZBpi&*Epp9DXi8kDt#k;1|Loelfpppa1Q?q|0{or|Be5hNBbTBC;u1!H-DNx!=t#x|HJ>w z|Hr5B1dk&dPvL`!;aQ&Jzy1VXe>t zpMQHh+h|3-|c@`~&_W|A>DKPxz<&GyXaM zf`19G_}Ba!{w@EGe-9t{kNhY8GyjE8C8QEk3u%P3LOLP6kO49ZnS{(j79p#U4YCV4 zgq%VyA-9kR@(THc{6YbtAQTb`3q^#YLNTE@ln_b^rG(N#8KEqc6Uqw}go;8Xp)ynv zstVPF>Ou{nCe#vY3w4CLLOr2AG!PmJjfBQR6QL7S;%Bg>}MuVFPRwHVK=BEdrMH2-}41!VY1luuDMXyRci>BkUFS3HyZua8NiT z92SlU-wKF65{?Pq3EvAp2*=?^;V0pQa8meLz}S!Qt8hyAP552-LqH3k@R#tna9TJc z{7*n3O!!y$Pe>7nuh#$qCC~yRuy{@3p%Mf^6eK|w6hQ?|&;>&<1q*D!5nRC&d?A2{ z5EVipCd7pVB!#oWIpMr;0WJ!cgv-Je;i_;At_wGWo5C&Owr~gT3ipKj!UN$UJQ5xY zPlTt!GvPVB5MBzegxA6w;Vrxq-U}aukHRPTEPN4CiK)djVp>QirWZ4a8O2OuX2>FD z6|;%i#T<}R%q8X)^N4xHe2`x(AQlu0iG`tvSX3+~78gs1C83m9S}Y@$70Zd`p@LXZ ztRz+zt3Xw;npj<|A=VUYL2a>)SXZnk)`tdSL$Q(ASZpFTg=S)Nv4z-DY$djaHey?` zo!DON03F3nVrQ|7*j4NX-NhbaPqCNS8~TWS#eQOcaez1w28n~kA>vSR7z`Ijh$F>O z;%IRUj1|X;|)#cASnm?6#-XNj}LIpSQHC(aiahzrF`SrTBkDl+DEd#1xSbNfB2BA}uoboZ>`Y#DgV@q9n?qBC4Q?x@d@|Xn`#{ zqAPl$F9r}1qhct=#5g3xqQZ~pg<&bhpxuo2XN6IVZlk!UiprBMpDl8R|ib}8 zR8A@{Rgfw|C8@GhMXD-QgX&TZsiss*stt9dx>7xNIRun5*AfR zyQMwSUTL4S9}Y+dr9;wT>4=07KIy1*O!`jxUP6G3bX@vT`bjz=VL(~>S^7o#RXQd8 zCSfo^`a}9t`b+v-LbI53M*5%hkMysEBAb*V5fUj;5-txUMq(vS;w3@C!zD?QEGd#I zX`oAnWJ;D~gCn_;C;3tU5h*H#QcQ|NLP|lAoE=iZAE7DcCCS8|qNH?Wh za9g?~-IeZ1_u+x`PspQmh z8ab_;4${jR~an{C*+cI%X#Fyaz4l}7my3eh2+9eL@p{9lZ(qG zprl+%E-jal%gW`Tyj(%9C|8mzLlwEITurVn*MORGExEQ_N3IL?l%B|$qavNwXx0Bn;9psL3C+IA9k-N&>LARo*6Vmv_im zDd0CJ} z84s8&%ZjYZnyiB%o3bU_vIDN{$-W%O5s1p69Fya60+RAs`J8-Sz5o~HOY&v;ihLEW z$=Br@@=f^`+?MaicjbHXeRv>0lpo2DtV%W|JLFJuD!G*0N*>6o(DXElFN<$f?tWr)XuT+4FN+qSTQbnl>)s*T=4W*`13u-HMl)6eir9L!J z8Y+#H#!3@tsx(uYD=n0k&`N2ov{BkB?V!EVLFuS;QaVEyrK{3S>8|vEo=PvJx6()H z3;mS-$^d1cG6)7MLzJP)Fl9K5P(~`Fl+nr<7^{p^#w!z)i7-i-tV~g+D$`)PGDDfE z%!1j<9A&ODPni!3l!eM7WwEjZmMY7X<;n_WC9G0bD{GXs$~suDY*02To0QG4McJxs zQ?@HRV5hQ6`9|5TU@3^QSJ|iRR}LtM+*S@Lhm|ABw+dn>m1D|x%J&Llu$1GhniE(rRIh_YF;&;nqMsd z1=T`oVYP@_6pE?E)e>q+D5aKG%cy15a!_8apjK2XsgOggnIv9qiL)BsGaCHQXR7a_!)iE$u9jA_0C#Vx)k~&$PqE1z( z!E|+oI#Zpc&W1VaTy>s09~P(!)kW%JbqOq0m#NFu73xY@rLI=jsB2X$l2_NO8`O;| z)(5Mb)h+5)b(^{!cBnhmUFtXLZrG#lRrjg;RfO}a2h~IBVHFX?>bL4q^_cpdia6jGdPYTCr23EgFIrMkR1}I; zQl(T{#U-iAsvM4v!q-nRRZ?YDQB_p~T{TowwZK*#)m1&!hd_;}Q8iR!5LXjwQauaj z)br{E^`d$SE~{76tLioNI^0lis<+hJa7VqX-c#?Z58$EtNPVn6QJ=yy^||^&eF?AB z*XkSft@;k$s~^;l>L>Lxd{I+rskJnaR!gU)*D`1sA(NI_%c5n~vO#t&hn7>z1-Z36 zT3#)mmLCde1+_w2VXX)h)rx7wwGvQLE2Wjz%4lVwoK{||pjFf=L1nFqR#mG8)wLR0 zO|6zz8|r9vwR&28XrMLJ8flHSCeT!CrZv}EXf2_Y)>><$wS{(Cd#!`kQR@VqwJus$ zts8XLdT2ehURrPHqxIGLY5lbUFi;z$4c3OhP;HntTpOW{gi+dPZHzV+#%bfV3ED($ z5=_>nXj8Rm+H{zq&D3UTvtf=lSDUBJ*A~D+ZIQNETLMe9W!iFWg|-q_X{)t0+FETL ztk*VZ8#Sz|);4Qfw5{4U4eKnm9okN9mxd)`+HP%+wpZJy?S})}LG6%s7>;P)YDcwW z8lpM1@3kMaOf6y+6 z_9ra`O$nri(jW>ujK<>P3)d8ypo#c2!(XSS;9=7=T{FPcEX~#&a5Yc!wLptNR13A3 z7KenE)Xr+>wDWL5yQp2#E^Alds&-AguHAr}+AZz2c1ODl_q6-k1MML^(jIG1w5QrL zc&@$BUTUx4wf07PtG(0S!w2o7_DTB;U$j(uYCVmf7SieI^$dDO$fRf1v*=m%Y>-{g zq36_dL2f;do>$MO=Z6A%LA{V(7>ejc^>CK^q-coO+w}v))TfLp$Uhe=M z^-g+cy$f{JyXoEa9(qscrT5nR=zXD|-d`V}57Y<2V10-_R38Sz^%43=eH4t=$LM4A zar$_epik5%>62lKK2@KlPuFL_OnsIPrZu|8Gbq3_gp>EFO^eUH9Z->2_~1NuSzkbW4B z=-=u`^<(gz{=NQ#eq2ZRoc@!3LO-b^v_}6$|5ZPw|E6O|S^q=-Q~yiHSdo4jg8~23 z(afv=tN*8`=!A||2A$Gro%woguX8%D3%aP|D1c8jMOSq^aJsG=x~W@W>yGZ~p6)}S zNA##3LQIeA2|WpC^>g}p{epfGF6o!`EBaNqreD`@=r{FSa9h8l-_`HIef@#{P=5rE z^(XpM{h9t8Ug$6NSNdysqrcVP>F?o#{!#y=f7ZW1DkHU##z+h4jPynZBO_!oG88o401k;lkuN*bk%(on`IYm_s}8x^3U zQOT%mRDr5SHKV#w18N$zjM_#Wqb}4l>KhG=hS11pY&0>NLNlYe(ZXnHw1U<~8>6k! z4%!NY4kFBLm#8B(a-1)1B`*jAY-sG1cn;JjN!%z7-@_$ zMjK;btTE0QZ%i;I!X#s|F~yh)(~RlH3}YtDGG-fdjJYt+m~SjF78;9Sv9ZKhYAl20 z#tLJlu?kijYmBwVI%7R-Fg6;SjLopc*lKJuu(H?KVeB+^8CaBP>^AlodyRd@emGzp zG!7Yu;fV3Aanv{l-x=QPsm(NIS~DG_H#3+S z%}kKl%wlFWvq5$W=m*g zwl>?CZK0jn-t1s@gidB>vy0gkx|!Y09%fJIW%f4vn0?KD(BB+j4m1bBU~`B$)EoxG z%@O8Ea}%`LFi+-7b!cfd|_m-&sk8}^uc&3)#6 zIA9($51EJIi21E~)I0{?nctf~n8)Eq^C$C!dD28cm-&nNt9i;qIEVSW`G@(ZiQ!uF zZ}YTy#>9XN1{eM{|1;4jY7!=CQYM-vOvYqQ&O{Nw6im^SOdLy1#Z*nr)W1G&Y4^MZL1E}56jE9O-Utn`q< z%4lV>GD8+CtCh{l4mqrxRxT?y&8lwIfSOhRR=z`c?yIXf?7LTTP&;)y!&cwSbmZ zE338D2HINftoBw1=xBAaI$K?!tJTfwZuNkkRxhi!)d%`o{jC1h02pWuvIbj2V5l|B z8g7k%k=7_{v^56CTH~zo)&!VnO|m9iQ(&q!&6;k_fSJ}TYqmAVnhW!+`PKq!AuO^M zTT85^u*_O+t*}d1###&Oto7CgYa?v3Hd|Y)t+377ZtbwJXxZ9jePivmu)@;X zYwff4TUhaB9kdQvhb=5JvA(sATE{Geu3O(*KUl{tguGfmStqQM7Q*4IU#wrPQx<|@ ztlzCatUoObc3Xd2r>!#$-IV zZd$jj+twYpYu&T%TMyu&^~ic`J%OjzGwZqa0$y6Ltk>|ydTYJ2-opp$qxH%93}38N zc4|8fq_xx8>Fo@V(avONwzEK1JDZ)|&H*{?Ty}0d59GD;+4=1PP|z-97q*K)QM;I3 z+%5qn?NWAWy9|`I%h~1a3Q*CmWLLJUKvlb%UEQt$HSJnjS?FP`$Ze%yM zn?O^$ncduO0WIxTc5Ay0w6)vW?V*F+(e7k-hAwtjyPMq|de}YfUUqNjWB0ZD+5KUF zJw7_u2dH12z_v*@x`I_7NM4SL~zqF&m-(_V@M=_Hi2_!}d@13Hzjt za6=y1^c3X z2`<}L?5p-QxNhIDZ`!xuwtdIGYu|(W_5=H&{Rkf0Pwc1mGk9*luwUA*;I;k6ehcsH z_x1<-BYd(y+h6QdPAVt0lg3Hwq=WQM1}CGF2{JoboUBeZ$nNBDayq#nx0A=o3;CS< zP64MN6mkkXMVz8g%qi}aa7sccr?gYXDGTMC@=gV(B2;oJJ5`{nQ_ZRF)PR~!EvL3q z2kJWYocc}!Xy`O@8aqv(sng79?zDiGPAjK1v~k)x?VR?|!RhF9aymm7r>oP==?*=d zo=z{PH}rA(I{l!(Gr$?>41&SV5ND_}42C-+oRQ8b810O4#yaC*yfeX>2$P)2&J<@V zOmn6?Gn|<)%bD%WapuB2XTGz*SqO`q#m*8~>MV1XJ1bzNv&vcRtbw)8I%mDJ0X90D zoXyS_*y?O^w!;o*r?bn!f^28Ev&Y%%V7aKX-#OqMbPmB`=ZN#IgGDjUG3Ptyd-%aQ z?)>N={Mb3+oOFJ65R&Np>YPF}76Or+Kb$|mMq4<4JExs94hBq}f1H1v{~QczID|tw zl!K;4hjCamb)W?A2#)BW?0{=w#ZkW=Z5`b)@b`;Xpkq6Z<2oMrPT)i!>V!_ri9^Ck zI%l18aNfD#Ty!qMW#@`>6|Ongog2v!&SU2ZJawKq&z%?W z(s|{)cHY2S=biK3`2ZiCPtIrf;-qp@yJ;Y;o6b$|W`K-tCO5O21+u!?-0YCU&FSWH zb3-0Cuba=!4+Y$UZXvfY6mg5X#h|!b!Y%2Rg3@jox2#(Z%DWZZicra|>{fBBLN&L# zTf?mhwcOfn9k(vjbL+beprPByZR|FIrfxI0x!VF-x~<&S(8g`+wsYG<2e+f!$?XhX z+^%jnw>$K3d%C@#x7)|<>-K~G?f`e7I|v55L)@V-%pLBIa7V%@ceFdk9Sh^!@$Lke z=uUDcyHjAQJI$T$&VZTjEO)j$2j;r--1)G;UFa@y7sC>Fsk_Ww4lCT1?kZUAu5s78 z>tMaR!QJR?g3az0cPngjx4S#sov_RO#@+4ifxYfNcRw6(54wlk!*Im?);;PTgYVq$ zU93-VkGns*Ke>oxc2BxLyT7;yYjjV!zqttJa{oZ^&R<`HG~Cnf8H_n&eAWHe{m)Ht zG5Fzr_)@5AQ?uuh_L+$Nl>AT;C1c2t?h` zjk$41xJmacoO92+7u<_*$-V4eaj(KP_quxnZo0SJ+wL8>>)vzkyAR-@`^bF^Pu!>O zGxs^Xa9_Hw+}H5Nee1r1_wEPxqx%UyyICO|KTz_Ud?bp`KUYYv476MqXpDiPsdGdCk2R(9&z=wf5RTTd$qh9y)j( zy-r?d=;C$tx_RB9hu72V1--pKUSF>t^!EmM17VOi*c;*vg<;-sZ-h4zMtP&XF)-E} z=Z*I!z(jA7HyNgQQ@v^4beQ4I^k#XpVU9P~n+Nl~1>Qn$5iItWcuQfKx7=Idt%OzH zYHy9V7S?&|y$!I@+vIKbw!l_zo3|Zycsspa-Z!w@+v8zPwztpQ?;Y^4Hq<-h9rliR zScm2v^^SR1#p8YN{ooz<5dQD|-ml&%_|5y>`@=&7l=qkSw};>k?~M09 z?;j82vEF|eUBd{HM|zY;duYH#+a%|qEy)u+(UUwB$~?tWJzV#Bx@UN%htER%Ejym; zef{}f;6)(ngj z54}g;V|d~{^`60V?}hi$dj+q(H{M%#=e_qncpu@D_u2absr=M_8b2+h^V9noAfunj z&+KP`tbR5>JLK?l`nmkvkjKyK=kxPJ0l%PM2nzc}{Gxs_DDIc=OF}8Xv|q+A3+4Rs zeg&xLSMn?SRiLV0&94qM{F;6(zc$qI>-zPezTd!a=r@AKeiOebH1nJLE&P_y%5Uwr zfwq1-zrEi9I{Kab&d|m0>UZ5uY9`(t3NKh7Ty6a0z(B!4nY@u&LJV7foUpXtwn+5Q}VF3j`i`wRSqu*hHRFM*~0 zGJm>Ux4#GW`uqI-aKJz4 zAMy{w5&v5stK9r!{&)WO{ts~6|Ix>K0{?`6(*M~^a^@IpP+Bh5BdiKf`P#x7#s`$dsX2Pssb}%QH3-f~c!2(zqED9C} zOJHfRELaXJf|bFlU^T1>)&}cfeXt?e2%Cb_gG=B9Zh%*0;0Hkv38D}Nu^a5cCVTn}!* z&EQsW8}0;mgL`m4cn~}c9>L?_N$?b&1K9V7lF_H-~N3uk+LbgcuNDjyu$rZ^R$pd*K`6BtDK%`)#5EPCSi4=_# zgW{1Ak&;j9~W zMv=ymCeSp}EYciWL|R5#MOs6fNZUv|Xdmei=?I-7og-Z$U7=f~d!z^SjP#21hCY$L zk$#c>Fd#B8G6)7ohD3(Ku*mSph{#A76&W2F17joOBI98~WMX7eWHL;NOpQ!~>5&!}qh-`#Sk(rTanvvCvrD(5AH`EL>@*S!Q;r2$WwS0 zc^-KIFC(uauOn~ZZRB0#J$#6KjC_L6kuQ-{(NxjY(KOMt(R7eLnjxAIGDR~-vq08p zwrKWf4#*kJ70nHKqIskFAb+$#v>+6U7LFE)7KLKb;?WXNGFmEH8p=e=M$19@XoYCS zXeFo|trD#Y)uPp-HK1m+Ru4Kj z8*LYD4;`W%qn)60v`e&Wv>SAf_K5a`UeVssKF~MXFWMglL=+Nje7#l=q~ssx;wfD_D1(b z_rrnc!6??4M-NAjM8Az5g=5k0qF4YJ{ULfhiiJ|qpQ0zCC*kMlFHwZiM^8n6i~b%( z&}{V2=wDF;1x8Ot&qNX96#XaqZxj(4(Ud3=CBF`rM(HRM#V8JjC-^9ulA~f&ilX@n zjWKEzCFH0cHKJw|SB<#paiTchM7^m0^@WK?Ga3yc7L7*}kc^&@Q zC3+REMXyJ1z|H8b=NFwD?r7tQdl{x0#(CmVRfhx)(mSw?XXT* z7wU!e!v@eWY!o&Qn?TdBS=by}ge}8X&^l}rwuN?K`>+Fa3_FFLp-b2`>=t&19%0Y0 z7xWJMgngl3*gqTq1H(b#U>Fh(4Tr(-a6~vV90jApG2vJk7mg1nz{GG;I2opdQ^RR6 zJ)9BFgjwP2a85WE=7saa1+Xw&6fTA(;nHv!EDu+ND`8c*I$Q&5!*${Ma06@%H-(#F zOSm=M2HV3O;ZE2UeiQD7J>lMPAM6hgga_eJcsM)~ehWv#V@TU+9 zk;0SV&mmT2gujNTLIk9TzlVQ>e})J+4gU^LhiBk_;Xfe)fx`d7ln}8CAsJF3Mpr{7 zWJ8ShgnTH3Xl)LqP!5$4?KGi=f-{PLp^1xdTttRW=!Q7*gnk%=cyZ$K48s`2!$g>b zv*Ee$JX{DbhL^(2a3#DNUW4o5jqoPi3U7yZ;BI&?yblk;hv6f596kx3!n5#s_#%7> zufo^i8+aSO3*WJF#u^!Mf)+^Q<`o#Li`a%ELfY?A76dN2H0z+fNV#8rXY-DT{jE;?o zjfHWs@v#Z9i7+WPIW`5R#-_!l!;IL>*esYGn-iN0^J4R33t(YvQEV|Ri7kySgXOUm zv6ZkYwmP;3*2dPw*29L_#@Hs<9NQAx8rufjV>@CyVOQ*%*lySp+Z)>l`(p=U2jNic za12Z5W8cP(#*V>vvF~G8uo*iZ`!V(toQR!_VUbVlm)NhdQ}A2t_ZXtOV}Hi}iXmz> zb~<(@_P?+3X0d-`|HTj}6C+||4CDD2u4cZDGRC+VAH!HjOpHk}w6De#G<=~6C#J`Y zuVuoR6|-ZwsEoNWFNR}KEQm#7c!A;(jm6@Sh$UlZ;au!|>;hbjU5Z_XE3vDwYj8bw zBX%=(3vS2m#O}hq*!|c8co=&WdkjxvPh-#EdF(~(CA^Bgj=h1mv3Ife@FDgw_6a`6 zzQj_+Q^ixq)5Oz4x_J6{2FMuC6weG<;#uR_AbUJVJSXIe=Z@!ryzzYT{7@iXFkT1> z$BV>^La})9cnK&OFBLBxF9T)c<>KX`LcC(U5>$>?iC2Yc@#^s!P%~aDUK{Gf>&ELr z{dj|TLueFl9B%?mB_Q$HDmcg!n|56rUWQ0#oDD z;?rS9d}e$W%#P2A&xLvM`SAs?Fuo|h7?#AB#+SkJ_=@;SSQTF#Uju97>*DKSLwsX= z6KsxeiEo8%@$K;)urt0Z{tfJo?}_h)eewPA18^{YD1I1@#J`PW@p}AN{JS_7{lZP# z;)no*WpI|X8ac1j^Byjg?sV)@dxlQ{wV$! zp2VNVpTYC^i}*`;6@MLn18?K+;_u-@{A2tRe2#yKr%I$sq)wzsq=j^e^ob0RF_9^e z8L}j@CbB{HM2}Q5wo5$|lM| z`9y_8MW~dhoTvg-6V(#cp+=%+q88Ln)JfEZdWrgp2GB6kDA5?2B$_6gLGwh5L`!Iu zXq{*SZ4>Ph?V&@WW1`HtCyAyj7 zdtqNyZ+t62B#| z3M27H;?D#^%@cnoPAAU5{}TTs5CWL^FM)`z1d$*U2s}y92_}Kz@C29OzmCZz#Dw&9 z93Y`2)C8Iu6MDi(m|&s!=zyE>5nKk*>(5FRBSC!WC5#IwY6c#(LScm=N$ZxV0eUE+P> z1AI(;N_>VdiB!qdkS3WH(k0U;GeE{L%+Y>qCQN!(=09oNSV83eA$ulP#cSvK6#Wwn?^ycFFe14$v{#DcKpiB)cZNLHA^j zWKZao?49fbeUtr?{b4|IU~&))P7X;9g<;9zFd{iJISNK6$0WzXxa9cc1elndl$;Dx zl2en@V0v;!awg15&Q8vOxygCS`LH0l5EdmDCzrs|Ym#eWU2=VL z18hugN^XWN$*sw4usyj0b|!Zvzk%J!J;}YWFS$Q?01hS(B@e@q#H{n+DcJdC~P2Nl1hX=`r z$w%-w`6T%io+Y0rU%<=ctK@5VlY9&BlJAor;A8Sr@-uu%raGG%(wt3uHXWoto8fFm z$aFUI*({LtY&OV#HpkhVkn3#jvw0xz|0%i$hB>ls4a0A2+qP}nwr$(CZQHh!$z&!O z=bJNyuCA`Ot@zwk*I#JtT6?W$33-Kl$S)KS3ZjruSSW&`LNOEOqL0O@k zP#zV8ib5q+7ODtUQB9~W)Id$47HSK1gu19F)E63{q0mTZj3z=;p&6PBErgb6CA3Bx zp{>vk?S&3PM|2W83tiAv=q7YW522^f3%!Lt=qvOS`eT4FP#A>4!VqC7h6%%k5f~|q z5=LW;Fc#y4@xla56ebCiF-4dvOv7|xhAWuVYRRZYlU^fdTbCj3Y)N5*dlDjHetK413QIX*e&c4_F|u~UpRn+!Xe=>jtEDE zV>m9H5KiKha2jWXvjS^oh4aD%f%SvJCE>Eb(kbDpa80<58v@G#gj>RGf#_!8u5eEe z%@iI85A`@Q;j!>UAjU;_rpF}+FNBu@m`Y5IG-6t$6VrrNq)=8I%>viRDp2tSDAO zWw8pXiq*vGs3F!AYoWGSN34r_Vtq6a8;Xt4SZpFTMKiIv*a9uZR$^>_CQaum)IM9#J*xb^cM$+12IS(EDpg?aTtb+BgByyC5{%yV5~S! z9FGa&L`)JVi&HRFoF-1k3~{D73$w*Jm@Cc`=VO7mP+Ww?;u3KwmWj);LR=}X!fJ7i zxEAZg_2LF>6gOeBxJBHGZQ^!u2X>0P#NF5hRw)~@ zOF5*R$R*{L@*uC25Ba46Qb81w3QI*$R4OJFM+vDUN=c=qGAJvRlggulR1uY=%2E|n zm8wbAQA4UJ)k1Bl4(dwvr21$eHIy2mvD5@jrDjrdw2)d#tj_ zEOn8(qMOtmJ*1veFZ7oBNPW>y>MsqzKxq&LOGBig7$yyuMqs2g3Ztbl(pZd>#!C}0 zQJN%8#uRBPrb*MK8JH=}l4fI$G#B%v`O*R`lom;gu|!%bEyHqY1y)L{q}5m>t(Dec zy|e)vrA^XiY>~D~+pt~Qft}JWX*c#rd!>EYFCCB$;*fM0N2H_DF&vjpNGEYhI*l{Z zS&6mY(s}8E#JX4Ml5`nYq^lCkyrk>W4T)te(kAce$*ri{k_j)s(^qWKriS&o`r^Ka{^fx6| zy@>TciB~V_|D^x)pIb?kBuR!rQec!+$plR@OBPrqn`DPWa>6CKB@eukPx2!m1rd_M zQUp;cCdHAElK3QjmcHPt^iBGXA5w~(BBzp5%W05SPA8{F200@#$(iLW$SP-(vm=L` z6S?HvavtQB^U3*9KrV!@QA@5Z*Fjymo?IUd%!OP(#y!CZNsJRb|>g;*pnmX~0uybR0b74k}~l2^-XuvT7&_3{RJ zBR0vKhKz1#hCGJ6 z$Y;op0)~QyLMUt~f})0EhTThxUdJhK}fD=!`Cgu7+;t zZs=j?iC%`@=ws+>=!gD>0T^f)WEhMghM|UG7;YGWk%m!*(HLVGi*bhWh6$Kxm}HoY zDTb+-W|(f6ftiL`m~EJ2n2ULa`Gy5pXjp{Bh9!ojSY}v`6^50DRak9UV_1uIhV|HB z*l5^<&4w-5YS?Djjva=bhF#cg*n_=>eTMxwU^s|FhQo#;lAMk9vU7Q9^;AOse!OL z!*c_1PKK9;R|Wzs3~%(&Z^K)|&-&1s;THxuXr?!OFwja$3*R4ln+i<<`t|EShK~jc zw3NOmDDa9V=m!Ub#0OY^6oV0}!DP^2HdtUa*bH_!3{JQVZi5G2gU{eczz{^p5H>^* zHN+4%Bn(M>GJM7t!&k#Md^h|sq$nv$DkU}2C~1{+NUvl-MkSMy8CjI9$fjghav-OY zOUaEqN?zns@+$>UP$`7MN)e?fiYdiWLMf?~LTROpQWoWu@~EIxR4SpeQUz6&YD#s~ zP->!1HtrO-maMJ-oh%Kwyq70P1D ze+n;cipU{?pJBzI=#NoR6%#bY42xn_Y_Ka1I2D)ThDY(jr}&isf=WmUBcenRQ{qYj zN#zqhD_@ka_@;cv4<*Hz3aO20kk**am>wC78Ij4D*_Z`cjoFahn8TP8xs17y$C%fc z5BZG+P|#S&SQtf&MN!OH+*kr7jipf9SjJcu<&5P~!C28)36+giP}Nw?SRFNtHBrl0 z+gJy6jrCC9*udBjjf{=a#Mso>49$%#j4jd1*cxq&ZH?{F-q-;hjh&30(Z$#m-HhFh zJL;^NjP23$W0*2#bwNj7zc1xEw2tD~+qL+PDU5jq8l-vB9_z zn~a-{Td>u*4cm=7j61Q*xEp(ndyV_B-*^BAjfaefam08O$Bf5u!g$hn3a5={aMpOv zcpevw7jemW*?0w4jV!k{UN_z_-o!2AZQL>5HQvL0BP$q;4~>tEkMYE)2gyD&J~tAs zXMAaVh1bS6Mxr>3Z;e0W9Rr;V)*0U$Kj1gx@5Vpyr;#Qto>cjFJFsHxP{NTa4jIyJqT0U6ay$gE~jvm%?C9XZsTYA)nf^B}L9 zPtA`4YC#lI3!{iyR4s<$Y6+B7OR1$%MlFkSYI(H+Dyo%GS*@a0MK!fLYN$2UTBxno zL0z?;S|1J6hG?WVMiaHE+6>Lr7HFxqQd^^q+7|89_G$-oR6C)w+C}Y(ZfbY*P27OFj5_*j>Z^uEXJwh)d`rWPQqk$iaHh3)ajU^ z&Qxb%wmJuM)p?k&E>IU@k-8X5)TQb&ELT@xrMgO8jWz08tW(#k8?aH`gw5&}bt|^1 z+p$C4iCyY$br1Hc`>WtT9>Ji# zQ-4tz6J=D7Q7QE|l>q^ovj5Z@4%NR^8fesy>i_g>HTD1W@~t7=p2aHvkWRJZDZSM|ZK1`t$3Y8VkUikKQ#6G*C`@LBz$e#JNSJASAs zrW8{uQ)*Khq&1~OdQ%2dMr1N&Mix_6Q#NEbnTF61`lG37-*Q+^aM6*Lt>VN($l zH5D@zM+s9&lroh@8B@)4h0n6z)d=>=ZuVS)s{y)hA$ zWO{2NRD_TR2JRVres5y471gxrS`F0HYN57PN2`l^T75Lo z8lsWbSZji&S~E1)TA-!YN^6ZaT3fW!+G`!qQR{@xS{HQHx@q0fL+goNT5t5x`fB~q zUmJjd+8}K(hG;`EOdF07+DL5_Mr&g*RvU-$+5~MPCTWu~MVpFg+H`FOW@@uATbrZJ z#XN037HA8xNL#Ee!BTA*mTN1pQd_01#u{xc)@kdpLEETp!e(s?wrbn7?bxC1#4c?& z_Go*xeb}!Zz(MU04r@oWqd2A=#|iBuPHCsLGdQcA!+GrjE^3#w%ebOl#Wjru>e>zM zrgjUrwL7@0-P2eVrajPD-=jU!muP5Dw5NLDxb|FoftT7Vyw=|6@n70oji?;$okom= z_N(?@A86Hn(-`B@{?Hg!(Eif?)@XC2kxXyX`B&poS^G~D^y0B5X_PT|3FP>!8Tpy! zL#=6=85Ye7n`YM>aB42NHIL?nPxB+71rgH1S_DxohPakMQv0NR#ux1?zG>g_LrXEI zLTYmwq&25QdUFPIMr1N)Miz5cWHVVEHf|13iC?yDy%lI!CLb=tT%5kZ^S0^W^6HU#WwSH^A7AZ@4{~L z9_%&mGw;U%^FbUkAI1^$Q5-WLH=n>s^C_G*pTSx4IrDj3Fki$a^JQEyUo~IDb@L6} zG~dE)^BptmHO=?TtiUrrFteD-{K(9D81obJQ}Z)CH^0D3^DDeIzcCYRN8lJCQ08}f zJc{{O^LsO++UDQP3@DoaFf)c@{>%KgnFe0-M>FkbH1^Og!38wed1lcp>E$`IL4Pge zIL!gXY~q8hKNhnUHnSZLv(xN?+w6hY?1SGNFb5GbhY>MH5i`e;FelBQ@Y(zYU(Mg} z-TcFxVo9;2vZO{DOIoC}q(=rzMoT7Swq!w8OEzS;RJK$>RZBHgx74uIL@i5g)Unh>JxhH{ z12nWWLSsu4G_^FdG)D_dOSH1IMjK08w6nCgbU;T-Cv>)SL03yROLz3J^h7U9Z}hSB zML$b_%K!|t48mZ`5Dc{p!*I(8%Seo}jK&ztSd6obw@ko9%Op&;Ouik3d>5YvaH4$%UZ0ntha2yM$0B_wrs&x%QkGc z?6B;_F3WE0vFyb@%YGcN9JCz5VapL5wH(87%L&U#oU)w88OvFmvz*5T%SFp2T((@n zRm(M8x7@%@%Pk9ws4aIaERVI^v#{XN^1$-Y@(7Rh#Wj|vmS=db2eDgTT3%UR5$%8$XXahtVL1GS{x;;C9S1U+FAx>t>sYOS^*WUm8_Lf#ab2BtkqG&S`)RbwXJnf z*IEzttqsu7+6axUO{`7P%-S3+tS!;X+8S-FZLRIl-r4~jt)0->+67&$-K^cw!`c(Q zti934+86z-{V~8g&^ic%twS)>It;_DBQVlB$~qcjtYb0GIvx|O6EVp;**XPNtx(9o$`>@}700*sytcP*LdKAa3$8o}X5~r-Et!Hr7dJgBU7jV&f z374%`tXFZ(dL1{cH*w2)8+WX%N4DOx-nTx$L+c|vwm!jA>oYvJzOWJsZ+&GYUfKG_ zN-(MQt(6Er>pSZ&ddQIVy_Kj9Vh$KDxBg*eh|>C(mC+#UKQy@0K5YHhO0yg7Lo{P> zt8(tZsO$Uihqj1gt@82w`gkQELow zYXV8@Cw#Vkv3|uj>v#OHrr1(!scfl{#+KHW4(V+fkkOV2nQd8+)s_v}Z8>Z?k;|4F zd2D%+&z2trYz0xsR@hbqMQz1U+*Sf5ZKY7!R>oEqPRu$E3)onFU z(^d<$ZFNxBRuA=U4bafm$krH5Y)#S3)*LNtEz!!>+SUebZSBzB)&U)DozU6V1zm03 zY~9hr))T#Kz0t?k7yWGgF~By^HVA`lLon1f48v_BFw!;(qithsV=>M)9usU6G08R= zQ*2W)%{JXO12b*2Fxxfunpb z(Y6VjZCkL_whh~DJ8V0#%eEVPY}gb_=&{cW_r<>ukGkdw_?wM|f;|qA!lJJ<}Jo*k0IJ zePDZKdyO}?pYYa3P^Imi?H3zSTDJFkoQ&-^8zBm|KWvN&+y1gK0B8Hh_EB$zc zPTPMrT8?a@jVo}QY~u=s;=N?+Vdg5y#NZ@3!$*Rh`lI^*^8ruy(CK6OQVdvEXvu-+bf`=y%H+htDvgA z8milCpr*YRYTN7B>!O~$J{s5?qLIBZn%JA7nY}q$*jw6Lp|!mY+S=Qpy}biE+B>1M zy$ib9yV<*=hrK6y*?Xgpy)XLN`(uE8AO_h7+lOGNeHez@M_{CV6h_;}V61(deLN=E zCt{L)GN#z4Vw!zAX4q$9mVGwn*yq~kVZMC<7TOnKv3&`a+LvLueFaw9SJ_u%jeRZF z+1F!(eIqv6H)D%^E4JCU+jn56eHV7y_h7GmANJc1;Gq2w4%?5|kK&m9I8NA4;*|X~ z&e+f5oc%m5*e}{I;j;Y-uG+8Ry8Q-j+Hc{u{SNNh@8Q1vft@w6_D6P>HQJxppW>PQ zIbPUb;+37SeES<>rr+XcI{}%56%ra}e{Ux^$^M(2xDX;5^r85_^g&PiKlYDyhC%HA z>aEN6|Mb>6yQsH%*k%2y(XQByQ0*o=ubCXi^#hC5&IjFYw>#jpyWqBa;I;eUw+HM& zgzRBN>`}z*aU|?Xe6oMW7yDOyvwye$K#C(3QajQhts@=MJ2D`nBNH+^vLLG?n>Z5_9AsRUvJDQ-WqZyhzTA-z)6obj{ zwB*s)L~95a>s(3dS2?PKLLo&JUOgQahgJVk=fkc)PKOI_hX-DV4}M1gK}QH-M+8wv z3~@)ok;Es*XMAyd#W%-y{BWc=Q=F-ssgcH+7U`Vnk-?b}nVgxOS&-G44cVPJkkgq9 zxt)2C*O?Fbodr5a zoHd=bP}^Atb)EH4-`M~SosH1g*#u3U&CuN00xg}b(AwDsZJq6$?a{&65uKc!(Z$&n z-JIRg!`TzPoW0S<*%$qs{V~8f5QCh9okK9xISj*{BQVlA3ZtE4FxEK^vCO#~E1WB_%DEbAoNKYpx!$<} z8=af5*|`N5bD) zQE%jP%6cP-Q*m;2$z>0vai`{_*uv|meq6UX`FVFboKB|;Zl?!crw@K-06}L6VP^zU zXAE&?0!im5e0F}pSLZi;cm6<%D-}|^(zwzhohv;uxH2M>D>JgVvLc%+J94;kB9|*S z^0@LMpDRBKxC)|>t1ybVilUgSxT^$8x=Nw6s|?Dz%Avfg0xG&Hp|YzAs=BJ7x~m3i zx@w`es}AbA>Y=`?0UEj*p|Pupt0|hfnxloQC0e;!qm8RA+PT`JgR3JtxjLhZt1G&> zx}%4yCwjShqmQdE`nmdJfNLNIxdyw2V5n;thPy^!q-zvLyT)LwYaGVACSam#5+=K* zV5(~xrn_cfrfU{vyXIi7YaZsi7GR-k5f;0axRzp>YdKc9R$`TFHP*P+Vx4O}Hn=uo zlWQ}!xVB=OYddzhc4C)nH}<&pVxMb24!92Dkn1pxxQ^nO>o`uhPP$IvwCfDcy3XOe z>jEyiF5$B43a+}Y;kxSvZn|#aw(Ab=y6)k=>j56R9^tX;37)#1;koMtUbXbm^fgf4hhXaD8+!;OhEUAIEb2=VDyI zCAw%|b;&N;r(BADUGGv|TnJG**Gr5R7q6%s-t|L`L;ujbTrM{}E-!p8KLW0xD}=Bs zf~YHoxGRCA>k~e^zTm6t8@{`KAjO>usoiOi)}0RN-5HS4oe7!US&-G84cXl}kkg$D zx!rk?*PRdf-33t4T?mEUMNrgT48`3gP|{t>T^eQFWl_#u9u?dbQOR8yRoqok&0QTe z+%-|lT^n`Wby3e<9}V0M(a7BxP25e<%-tL<+%3_{-5PD&ZPCu%9v$2r(aGHzUEE#K z&D|Y6+&$6D-5Y(}ebLX|9|PP2F~~jGJp@DD!!X=E0wdj{Fxou^W8LF0-aP>m-IFlc zJq1(U(=gpV12f&TFxx!`bKUbW-@O0}-HWi;y#!0$%dp(N0xR9Cu-d%_Yu)Rx-n{`E z-J7u4y#-s{+pyie13TTju-m-{d)@o6-+cfF-G^}4eFR6{$8g+z0w>+4aN2#weHQ23 z=W)S(5trPTam9TV*WA}}!+jIC+_!PZeHZuK_wm5}5Rcp}UUxrnv+&sc%*|p^_X{@* z``oYe#aApQ(wD}(b+cZ<{mxDFxcgT(F}&^%ZXzh%zw2>p1Y8j&+}kd`YNRotq65$e|5l-4Oe>cttGo5Q@@;pXR_55E3*++O(H zegxb>gxq07+)>2baU|SHd~$!r7x!0ubAQJVcZw&)lgg7CX*_9>&XXP)JQ#*Ll0UJG=u-UT(Td~cv9XmWbvCFd?dpvuw&$Ax~JO^>ea~MZFM{&$^949;{ zamsTVXFO+d&T}3YJQs1va~W4WS8>gA9XC8Tam#ZXcRY7-&vPFSJP+~6^B7M&to!yn z)0gUcUU**OmFG3ycz(iL&(C=0`31jv-s6LZ09McM9>NAae|m`0^8D=~7{>Eak2PRm zoH1O_fBKM^N7RQFJhI+2>rwP3EgD;B1Mq12wUWo;p)l#O=`XSz?)Aft%fpA?AAr$r&K~Zlp6!(@uNpC5X_Lf0eZ#k6rRzO8> zB~ZsK_SQjNZ#~raHb6sfBQ*9lK~rxtH21bZOK&T*_O?M=Z#%U2 zc0fmOCv^69L04}#bocf^Pj4^u_Vz(vZ$I?+4!}U~APn{n!BFoo4EK(}Nbe|&#u)Ed zjPs7i1n)#l@=nGS?^I0lPR9)IOw97m#vJcl%=6C20`EdB@-D^_?@}!DF2@S*O04p( z#v1Qhtn;qN2Jc2}@@~c!?^bN{ZpRMqPVDmT#vbop?DOu&0q;Q^@*c(!9Q7WKr|L=Y8{Qy0J_;)XHo8CXYgvojT_7aWc{pcmw!~3s3 zhVK2(%a|ksXN>JI_(0n@4Y#x@dR4s*#;bX`SoK=;!aPMwN;zJKm%~2C9{uOv$moh&PIuH;x37-cR`K{erLFZ}{&0ffQdVr1qsjT3f^z`*YZ(kqu_4Px4-vA8s4Z>jG5DfJV!*JgSjP#AdXpHfV#W>%1 zOz=&_B;RCA@lC}v-*n9I&BQF1>Z$n@?FLi-&I`mUB?aIP2BR`#vR{X-1FVX1K&eD!eiePJoP=pbKeWR z^u5As-y8hodyAia@9>N7SG@Oqz;8a{k9~jm^pMZL^x!(;!Sn#8|KVTX|KUHM0MWlz8-4UYCy}kk8}eOW@NVzb}BGFNCl! zf~YTsxG#aE?-M@zzTm6x8@~H~AjO{wsr_k?)}IdPk-?u4nf#fN#h(?~{MnJipA)(K zxsk`87y118QNUjih5Ut4#9tJ}P~2YvCHJUjY^Ul~CDV1y%jkP~Bex zHT|_v+g}HDQO{o=4g3w!$ln-E{7uo!-yALcEz!!~8g2Y-(azr<9sC{9$=?}W(AD1! z-Tgh#)87ld{e95a-w*x$12E7(2!s7YFw{Q`!~G*L5~KX1F~&a@!9Nj`{F5=o zKNZvb(=o$86SMrYF~>g_^Dy7P01N$#u-Lx@Oa05R+`j@V{j0FrzXogl>#*Lx0UQ0B zuo+wYTd~c*9XtFxvCF?3d;EK`&%YlB{0DK!e;7ynM{&%594Bzne+sAlXK>bk4(I(B zaM6DWm;G08)qf4w{Woyae+##9$A1_1{P*#|{}7M-kMYF+6wmz6@xuQSul%p^#{U!E z;%EOm{Nn!=@BJU}n;rvB`0}6l%TI)({~td=WW+oXpX2{OKQRyb(7a#NhdBMRK1AeK z^rm&c>ZdJ|1~S@g{1*M1mMbg@`+kR?(vSWU%*%j&AoBY8BKZCO0D}Gy!u|-N{utu^ z1d{$w`0W3JulVNwjvxM%KuRE0AT`ni(jr|TJu(C`B2yqUvIMds8?py-AZH*KatHDt zZy+D?2MVBIpb!cNilAtq7>c7rpd?BKN~27mEXoDSqe7q}Dg`Q|N}wvL1*)S4Y6fbd zcAyUG2I`@HpaB{N8liEZ37Q6)p?RPMTB22;HQEH)qFtapIs`hRQ=l`t1iGSIpgVd5 zdZHJ42l}9Ipdb1N24G-d5C#W^U}#_%h6hGqBt`{BV@zNy#s$V>LSP~$1tw!kU@E2s zrej86CT3xFU=HR6=3#zd0Tu=pVR2vymIjt#d0+)rVpU)@)&$mKU0^*n1U6z*U^BJ^ zwqjdgJ9c1aU>9}=_F!*dANB_h;9%eo4hN3lXy6!*<3!*jP6bZmOyDfe1 z-UWWauYvdYfZqbYl){o3J^!1zUsLusyf~JA=EhJGcjX zu`jqE2Z9H2D0mo0f=6*IcpN8iGI$E7gJ*Cycn;@-7jQ9n373Oca23~r*Ks3w6Sso5 zaVK~e_k#EF01tzY@HqGcPlM0!Joo}HgRk&9_y(*OX6-AhID_wkEU63r8f0Np@I#Pg zCBff=MDqv##9w;AFEOeg@xLJPYQg{0Lx6%pkZ=k{{27wgM@)l?KGGCagEY<4UaYra zT7tCk(BPn7{Bji*bOtGsQYzA4m%TxbNkM;*FGMgH3?Yn2Fp5|(jzlntPr=Xl68ws9 z_#XU$lu#<94y8fbP&%X!WkAMICS*pIP*!9MWk-%sPUH&ZMxIb!)lfB557j`;P%YFB)j{1*J=8~oP(w5d zHAa(AQ#1=TM~hHPv_k7p8?+6zL;Fw%bPRPu=TH}PMYm9Q^a%AtuTXFF3H3$4P=5@- zz|bHJ4h_N3&@c=Sjlf8Z3XR5?&{&KMjmL!0L`(`z#uQ8qO~dri49pD8!tBr-%ni-M zd@Kkp#G=q*ED0^eve0s@2(82_tPZWg+R!?z4{gB4&?aogme5vg3vI`a&`#_M?Z%$a zUhKpE&;c9_9m3(z5gZL2!|~7woW!ZnX`Bh2#ktUVTnJsnC0q_&!PU?;To2vA&Co5} z4&A|B+zZ{ugU~}f3O&Y?&{I4MJ;w{Y486kZ&>Q>|dW)Y!@4)KD(61rZn1w!sSm6`; zJ)|$J_%lQpd+6^F(ZZpR_+Lm59sIu#(O3ix5!n)w^x%LHgV!M?#GqeD)dxvJT8P$U znm6_4HrjqdcKv#o>oWc7z#XE56!Pk?4jh^EzX|ZlQ}iCW>>sDrxUdZ-_6fQI2l zXdG^Wrf3#!juzpTXccaaHsQ8thxXwP=os#V&fzZT8t#Vf=n?LTUg6&86Yh(C;r*csl1-Qhjh z8{UWgI1oOFL*c_X54vg@v#lW+a7))Cdzvz%Vx>unY|{Vinf(mi@3L zOw*^{4o901jT2#Km@73d7WCq(H_S_Qn3sugK>xGgi=jV}a1^m{90?@DpYS>S1z*G8 z@Et$GDUp;&sz_?2iKIokNP1*I#z-b)j$}dBNH%1T^#Hh$Z##jV#0R$O^2)s>o`riLAxC$a-vuY{VvPj%>l!$Tn<`?7&X!itNUo z$X@J=?8kw~K^(&2$PpZk9K-R*37o{K$Z4F3oW;4wd0fE7$R%8kT*1}IHC&I}z)jqW z+{T^AUEGV@#{)c!Ji_D16FiMP!*jfdyu_=>YrKj4gtrlvnn&K@m&mVpANhdaBERDg z{2BQRe@Fho#|VMS5u$1%{}&-pF(O0=`XVlgz!~B?2s+Tm!y~Fbe#od611+@t>n+hV zi$?5vQxQ!TTsKGD5iTAg6kaJg>95*>2uG<%D8iQ`qCe3{4Dm<;$;c;s#+S%fe2aX? zk4Q>1C7LRl8flO=nhxos8IUoW37L^4nibii*^wif6SMZCebF!49|NKT zF$jaBLohTt48x-%FcPDpqcJ8r7UQDhF#!{!lQ20t1yiHbFdZ|ZGchYV8*`#_F%R>j z3$QS{2#cdjuoTOp%dsN5604%Cu?B0S>##n$0UM*6uo+vTTd^&=9Xp~su?xGSd$2dU z5BsABa1e*0hjAo&6vv{+aRMi!r*JxY24|z^a2^+;7jY?i8CRlLaShj_H*hn03%8?p za2NNY_wgY55Ramd@dQt!&+t6@0xzSl@EUKTKjCfkXS|F4f?x4I`T@U1f5#vC+9iF- zP4w?5D;o&K*CWUOjS`R>C32JiN22hek{-wuHRwSgQ34bgIcGpOs_6q?QA?DO0lk@; zCR5t(qRuD{8Tz#}S94rUM14_8QPF_@s-YjN`1{}s5{*VQ&>kIP9nmS)8C_ys(GA^WJt;{V^am5QAca zF$6uhhs-@GToH)1z&D|Q=qa5r`j z_hS$65RYPy@g(*X&tlK<0xx5)@H+MeKjCfcXS|ECHa_-i>^(l|3vtb=uaGpSQ4LNpYa7>W8d&S_5&&LR7j09@w7-6 zPmc`97|(>v@hr$1&xY*C5zmQS@!ZIRyzzXbw>((y7V z8!w0Qs1UD+O7Y65f~xUqs2;C@n(gvRkEXc}*Z=4cUbiB|E} zXoI%#c4!~(fR5-C?~E?-uILu;jvnY4?}gs+KIn^n@%|VPABaKm!5D&}@nIMqAAyk= z6(5Z;@v#^eACC!`7@vg6@hO;!Y4Pcp5ub@!m>r*kx$$|JA76llSQKB3CGn+LhUM`U zSQ%f1)mRf>i*@n!*bv`{P1qdYg01mw*p40Xo!AxMjXm+b*oXb`12`Bzgu^%zKZ;}V z<2Zqn@l!Y*KZCRJb2yI+@r$?=zlv9DjnR zcou(-7x9;Ph1c;n_$mGtKgZwU7yPQP8jpX7|Ayc3NBmFx75^Lm;A8xM_&5H4_zyyy zU{&HN3HXZ};si^@jZovnJ;XJAd{`f`j9cS2*yA*D$DMHLO-?j=c;Sn4v7wi8DP8fB z&dUhLtvG)td{Oi#5l`Y1KF7b{Yy2C&<3~Itk&;N2NR2d!v`B~ai44e?$b`(ulE{i| ziR{RMoQYh>oydc{$d|~E0*Qhsgu;m;D4HmS;)xO{iBgHuD3d6QawwmufQpGqsEjI! zs;HKzjvA<$sD;{zI;fkdhx%xcXoyCM#%O}3iDqb?Xn~e!m1vDNiMD8m_K6PYnCOJg z=#uD)Zi(*bfu4z8=$+_;zKMS5j{%8+7?c={AsCt%hT(}37>QAd(HN5$i*XpAn1G3i zNtlc&iK&>Dn2s5knV5yyi8+{?n1}gTkXVRCiN#ogrHN%&o>+mESe00fHHo!YhxLgK z*qGRa&DfIIifxJQ*nypiUD%!2gT2_7*pCB=gE*8pj3YRjIELei6F7-eiPJcfIE!;Q zpSXaFiA%VQD~YSPmbi`^xS67iQn)${z&|ZzY>4rAAC&w5C10q5C0_uh>-L!%!EM?VM`ERMcj`G zTEYwlk{Q>u!Jcq{25_2TX$eet^p+6)I-G030D=ihTKbDSFDLrpE0*BPlF*-I;uAh6 zzThjqCBEZFA|;uUOoi0RG)S9FhxEvh%!o|M%*cYQ$!y4;%z>Q9mCTJi$-Kyi{K*0+ zm@I_CD3UCSV#(qtfs)BmD4i^WvM84ms)T!~d!om_*p$#qzd4atqzl-!Ih*qYpi?a3Y3iCxLv z*pu9geb}EofP=|HIE*97qd1m4juSYUJcZNAGdPQL$@937yogJ3~3W>rff})}rii;8`iBh68%Al+$hw`EVDx#98 zj4GlkB2i6LM-5REwNP8sL0wS~_0d2yL?bj7P0&;{LvyqcEzwG}MjNyh?a*FyKu2^E zozX>fMK^R8J*(#CS}=L@^1I z#S~1%G%+1B#7xY>Y%vE>h!%4(Pt3;xEEJ2dSS-O(EECJILaf9ptQKpq7VE@%Y!Dl< z37f?hY!%zE9XrHM>=L`N2Yban?8gCd5QoHJ9Klg>49CR@oWv<{8fU~=oWpr>0T*#e zT*eh~71wZG+`vt73%7Ac+{HcI7Z31IaM8DTj3?qLo{8t+QZeyDa0QNdCHxf)Z}3*U z1C_OueNvG~sT>ttg1RC~U8udF6u=*t<`}8?)*mzSM=rz|R4 z9G53>Ql7$ToRMd7PM*gFT$Gn^8CT?0T$9&v12^R@+{PVw7x(0SJitTw2#@i^U$iTq z`RgJjm+^7U8J9=NSCXqu5%~$RVHLsS&$XkRCeS* zPL&I}RUYI;K9wH@P*4>@VO0c0QA`y_36xZ&P+FBiS(H=dQ2`ZIB~(^bP!*A?8mgm) zs)<^vHtL|Rs)zb$pc=ds&N>P32GuHsmYjvscIUgV}_cE zS!y=sAWB7JF6OEESfCbS5f-Z@Sc+w8Iaa8ZScTPU4c20vT8|BCBQ{~P+JddvrnX~; z+KFA*t@dCq_No0ipbp{?4yz+Lieu_HPT-_Eh12Q`&f=Upj|;e{F5$Aef~&ZuuHy!7 zs$00N?%*!&srz_E3??6$ydao$vRg{@h{YXik z`lu*dQk2n9(?NNG;y^dYTm4bL9~4JE{J=ZIzJAD&u`czS->&!DlIl;z!ZJ%{eks6z z-u&lZI{x8NN@*Bn;Six?ASPnz*odR!A|B%F1W1TPIx&(UsZNIEIt5Z9l}?Q`NUPH! zz0QD)$fPqP3$p5L$c`L3CvxfB$b-B(AM&GsE{H<9Fp8k4E{5VLp-Z9^O6xKxtIMH0 zD(H%+gvz=Ks_IBoLv>vPHBn2~Mjh1E^-y0oKtnXrjnM>6bu%>AEzlCJbZfLhTip)r z(Lr}aC*2uc&{cOscl6Lb(F?tGAN1Az&>sWzKn%iQJp@DbFbu~CJrbiZT93h4jML*W zK~KaaOx9B{71Q)|%)m@N3$yhcL?K$w#XQW{3$PH2^kOX0OR)^g^$M)SD!m$OuvV|b zdc6S~u}N>n7HrkquwC!KPVCaVu?KtgKJ3QR-eOp zT+kPB377R1T*Wng9XIq%+`?^r2X}E#-^T+y)Q|93KfzNx)6c|iklKBiBhIC%Al+%hw`XkDx#99j4G&VB2f+1O%2pUEmIqHOkLDNebWF9(a1DL z6Ero=&>SsHOSCer(FSczJG4g!(-ED}*>piy(+%Cx!}LTi^frCa7yV3s48TA$2!qWK z48<@r93wE&jKXM)F=H_fb>!+BgV7jX%f%@tfV*Ki#-%uU?FZF2{AanIbx13Wa3@Yp=T zQ#>=z@d;j-mw1KO<_)+o$-FaM*J0k{Gef1e`CzDOH56K!FAbGwzAVaoW2hDJB@5d3^pS&A+ybbtjK1w zBZtk2T*z(nATRRS{3w8ewh#)Vh%Jg@C~ixjq%DQgC}Yc_9Ln1YsEA6oGOD1ejYKt6 zw>41H)MYhU-Yy6F#rSYAPmM3I~2n(+>XFVjIyIK24n3wjK>5!5tHm>OuQT9u}5(X$L$H6#3_3kXK>b@!+CoF7jem6#uZ$(*Ki#- z>`mOlZF>iIanIhz13a{k@EA|*Q#`ZJ@d;k|i*)_fr1rIagSU8RKgD}|=Bw%L2Yg{a z;!AvGzs5KC)_w=78#uuJBYv_!;}`sDe**)UjG6rjKPvFI-`Mes)%HKjGJ^jY;tw6K zBmW>-YaJq7gp1)~A{Ju1IEd@wAwCkggh+(ME(ww%nM;loNa<1`HPX1WNQd+;12Q6$ z%Zx0@>arob%YmH8<#Hnr^16J;j{>eB3Zbwof}$wqilYQdx>6{OGOjGjp}ebrim2o& zql&AFNK|vxQ3EwyE!0LGR~PkA-!(u(G;)p61WjEtG)D{960OkMwLx37bM4Uq9bG4M zMi~?#w7yI0P9B>D52#4Jf9K|tr94Bznox*9Hac6N3=iLQd#3gqb zS8&x`!*$$nH*pKM-5uP;J$D}u@X$TNV?1$B@eI%1CwPIE?iF6+jeCoC_|(0}XZYNG zz!&Z#zQk9)!q8W{xo>^-kNX}!xF7Koes&xHbHCy@_dEW;pML1Z4-~k6+`sq_!m+gA z{O6H&yq5gK^hNq11CW8pAY?Ey6d8sLM@Ax}kkQB(WE?UcnSe}0CL>djsmOF>1~L$U~AaV#fj2uCZA;*yu$Vuchat1kzoJTGo7m-WI733;%4Y`ioL~bFsk-Nw}CGr}1gS*B$S348@)h}pd`EsFzmVU^ALJi`peTx=1WKY5 z%AhRDp*$+05-Ot#s-Ze+pcZPQ4(g&l8lWK>p$X80Xd*N*niNfjCP!1EsnFDD8Z;f6 z9?gJeL^GpV(5z^7GzXd!&4uPc^P>6C{AfY65Ly^5iWWnQqb1N%Xlb+zS{5yjRzNGF zmC-6_RkRvf1N{%JiPl2vpmouDXalq%+6ZloHbtAE&CwQUE3`G*25pD7M?0V$(avZW zv@6;T?Sb}0d!c>MzGy$RKROT{gbqfBpu^DN=m>NaIvO2=jz!0#6VQq1Bya%dI!CW z-bWvx579^H6Z9$i41IyVL|>t=(YNS3^ga3k{e*r-zo6gH@8}QoC;A)xgZ@ST!3V)G z93wFbqcH~KFdh>y5tA_mQ!x!QFcY&d2XiqG^RW<%uoz2#CBhP8Nw8#Cax4Xw5=)Jx z!O~*sunbs6EEAR)%Zg>gvST^1Tv%=_50($hj}^cQVui6HSW&DvRst)DmBPwkWwCNt zd8{H<39F1%!Kz`^u^L!StQJ-qtAo|U>SGPChFD{)3Dy*AjsgXY!EgC8;T9XMqnecQP^l~EH(}sk4?ZPVUw{b z*i>vfHUpc9&Bo?nbFq2Y0&F3+2wRLT#g<{qu@%@VY&Et9TZgU3HeegE&Da)fE4B^W zf$hY0VY{)t*gkAOb`U#+9mbAe$FSqr3G5_x8asoX#m-|Fu#4Cw><{)A zLvR$wa2zLb3a4=v=Wreua0!=j1y^w$H*gcTa0hpB4-fDVkMI~zh$q4m<4N#jcyc@i zo)S-ur@_F^AAMm!Uq1<#6S!?WW#@mzRrJP)1^&yN?t3*v?GB6v}}I9>uTiI>95 z;AQc0czL`cUJ0*^SHY{{)$tnme|RmtHeLs>hu6m&;0^J{coV!S-VASnx5QiFt?{;a zJG?#K5$}X|#=GF%@a}jIyeHlp?}PWn`{4udf%qVNFg_F?h7ZR_;-m1<_!xW~J|3Td zPsAtVQ}C(yG<*g=6Q708#^>Vm@cH;cd=b7FUxF{gm*Xq&mH29W4Zap%hi||);+ycz z_*Q%yz8&9*@4|QEd+>eue*6G_5I>9`!H?p{@Dun+{1ko~KZ~Ek&*K;IOZa8{3Vsd0 zj^Ds<;8X_H$p2$FCBr+3Oh^$05A_tL^$VKEP@)G%o{6qnw5K)*YLKGv4 z6D5d}L}{W7QI;r2R3Iu6m59njRiYYEov1<7Bx(`0i8@3*qCU}pXh<|Bnh;HiW<(33 zCDDp#L$oE@5$%bNL?@y%(S_(nbSHWcJ&E2#AEGbOj~GAtP@x%mTA~BhmLQEy55i^LH#4KVqF_)M}%qJEQi-^U<5@IQ_oLE7uBvuh?h_%Ez zVm+~u*hFk5wh-Hh?ZggZC$XE@L+mB?69qpNP-I z7vdZ7o%ligBz_Zrh`;c2M@fvtNs^>Unq)|hB zlTFB`WHYh_*^+EUwkF$>?a20I2eK2{ne0M#CA*V7$ev^`vJcsp>__$|2aEf z7&)9AL5?IxlVixS9lXu9wJ|>@%&&cQG3-T5DntVgP zCEt@D$dBYF@(cNu{6>B!f0Dn*-{c?i9~50Eg;4}WQWQl~EX7egB~T(IQwpV08f8!> zWl=WeQXb_~0Tod(m4He}C8m;4NvUL13MwU)ib_qTrP5L9sSH#mDl?UZ%1ULYa!@&` zTvQ$^FO`qVPZgvJQH7}@R8gupRe~x>m7>Z}WvOyhd8#5+iKPU5>x=>xIZd7-wC)JDUP4%Jr zQT?d_)Ie%5HG~>U4WmX-BdJl;Xlg7qjv7x*pe9n2sVUS{Y8o|znn}%~W>a&idDMJr z0kw!)Of8|7Qp>3o)JkdmgxW@-zymD*12pmtKbs6EtPY9F{W7Ki#1a*=+MV+C}Qs=1i)J5tNb(y+CU8Am3H>jJ`ZR!qnm%2wipdM0>sK?Y3 z>KXN%dO^LUUQ=(Vx70i81ND*mM17{dQs1cW)DP+x^_%)b{iXgrLP0$og(+thh zJT1^7Ezt_C(i*MP25r$c?a(gm(*YgQ5uJceNGGBb(@E)MbaFZcosv#Xr=io*>F5k} zMmiInna)aQqqEaF=$v$JIuD(f&PNxZ3(|$?!gNu(7+st$L6@RS(`D$gbUC^LU6HOt zSEj4d)#&PU4Z0>>i>^)Aq3hE1=>~K|x)I%kZb~GkvmdLzAw-a>Dsx6#|_ z9rP}GH@%16OYf%-&1*_L`UZWA zzD?hu@6z|_2lPYw5&f8cNCNnc2G3%L)%qC_tvxV8pY-e^bJDFX~9%e7IkJ-;0U=A^dnIp_m<`{E=Imw)2PBUkj zbIf_>0&|hM%v@ovGS`^v%uVJNbDO!t++*%D515C{BjyS7lzGNHXI?O`nAgl3<}LG{ z`M`W+J~5w}ugo{*JM)A2$^2&iFn^hU49a3G&Jrxi(k#QWEXVS!$V#ltDy+)ttihVB z#X79ZdaTa|Y{bTF0yZI=h)u#KWs|YV*%WLlHZ_}uP0OZdGq4%iOl)R0E1QkY&gNiq zvbotjY+g1Wo1ZPn7GevtMc86&akd0ok}buSVau}R*z#-zwh~*Jt-@AitFblM|Ja&r zEw(mWm#xRvXB)5$*~V-Wwkg|;ZNau=Td}R#Hf%e#J==lp$aZ48uwB`1Yt3nV~4XN*pcigb__d~9mkGmC$f{+$?Oz%Dm$H>!Omo7v9sB^ z>^ycpyMSHDE@qdoOW9@Ya&{%Vie1gFVb`+j*$wPQb`!gq-O6rbx3fFgo$PLQ54)G$ z$L?njvWM8i>=E`Tdz?MNo@7t4r`faYIrcnzfxXCHX0NbU*=y`|_9lCaz0KZX@3QyV z2kb-k5&M{Z%06SCvoF|}>}&Q7`<8vjzGpwOpV-gr7xpXro&CZ7WPh>0*}v>R7U571 z;|PxAD30bBj^lVv;6zU16i($dPUj5H;%v_0T+ZVHF61IE<`Qs;xWrr%E-9CcOTne& zQgNxdG+bIPJ(q#Y$YtU(b6L4;Ty`!8my^rQ<>B&j`MCUCL9P&2m@C2+<%)A9xRP8c zt~6JcE60`RDsUCK%3Kw$Dp!rG&eh;*a<#bHTpg}1SD$OZHRKv`jk%^=Gp;$;f@{gO z=Gt&=xprK8t|Qlp>&$iGx^msP9$ZhZ7uTEX!}a6(a|5`6+#qfUH#jvLQSGxhdRKZaO!Eo5{`MW^;47dE9(%0k@D_#4X{La?7~o+zM_Lx0+kS zt>xBn8@P?!CT=sgh1<$)=XP*Axn10DZZEfw+s_@~4swUNBivE$7>Fa-X=*+!yXE_nrH}{p5aezq!BMKMvth9^(m~dtHD2cp z-sCOb;a%S2eLmnLKIRkf3Hd~PVm>LKj8D#|;8XIc_%wW4J{_N)&%kHmGxJ&atb8^; zJD-!!#pmYp@OkNf$zw7=DYA+`EGo7 zz6aln@6Gq&`||zx{`^3G5I>k7!Vl$#@gw+={3w1jKZYO2kLM@w6ZuK}WPU0?ji1iX z;Air)_&NMsejY!cU%)Tq7xPQ_rTj8}IlqEm#jobq@N4;X{04p_zlq<>Z{fG{+xZ>* zPJS1^o8QClV)DUV4wS?M29igsJPpB_6 z6dDPQg(gB%p_$M^XeqQ3S_^H2wnBTMgV0gvBy<+K2wjElLJy&*&`ano^bz_A{e=O- zKw*$DSQsh{6NU>TgptB1VYDz-7$=MuCI}OSNx~FisxVEMF3b>S3bTbd!dzjVFke_8 zEEE}MuVS}(q*eq-jwhG&X?ZQrBm#|ydBkUFS3HybE z!Xe?Xa6~vN921TUCxuhOY2l1;RyZeI5H1Rrgv-Je;i_;=xFOsWZV9)AJHlPzzVJYJ zC_EA#3r~cn!gJw;@KSgsycXUFZ-w{52jQdeN%$;$5xxoEg&)FC;g|4R_#^xi5D^tI z5f=%O6lswWS&yO=}FDdrM$i+RL+Vt%oJSWqk^78Z+$#l+%b z39+PDN-Qmw5zC3?#R_6Yv65I>tRhwutBWji!;QT;w*8tI7gf-&KDPm3&lm^VsVMM zR9r5u5Lb$;#MR;&ajm#c+#qfgH;J3YE#g*jo47;VDee+?i+jYq;y!V|cu+hf9u|*? zN5x~}aq*;hN<1x|5zmU}#Pi|>@sfC1ydqu|uZh>i8{#eTws=RpE8Y|Dix0$y;$!iN z_*8r*J{MnzFU8m58}Y69PJAzZ5I>5a#4qAk@tgQv{2~4ne~G`vzv4d;kx&VfaEXve ziIy0Nl{kr)1WA-+Ns&}ZlXS_DOv#cQ$(20GmjWr2A}N*cN@<@55d{Ta?fK*T_Bo&s5O2wq&QVFS~ zR7xr>m66Ix<)sQzMX8chS*jvcm8wZKr2nLvQZ1>rR7a{S)sq@X4W&j>W2uSMRB9$Q zms(1#q}Ea!sjbvbYAc&io77$EA@!7cNqwZgQa`D`G(Z|C4Uz^+L#1KT zaA|}zQW_mmo`WnrA^XiX{)qN+Ai&oc1pXX-O?Uuue4t}ARUwrNr$B) z(oyM{bX+zIqAG~LAoeimaa%wrEAi4>4tPux+UF~?n?Kh`_cpHq4Y?4 zEIpB)O3$Si(o5-;^jdl&y_Mcc@1>8@C+V~FMfxgzlfFwoq@U7n>5ueR`X?bWDq}J( z6EY>!G9$AxC-br(i?SpuvMOt`E*r8bTe2-XvMc*?Act}!$8rKWp`1ugEGLzd$;ssu za!NUsoLWvJrBjl0tD0#FzMjk7VlgG;wB&zmq@6ALUQ-XZef#RsJS_mw(7VMif-R6kH(`QlS)DVH8f`6+sadNs$#rQ58+m6+MMq-0jIC|Q+kN_HiOl2gg0cQ3Q9$#l2TczqEuC?Db zsjJje>MISDhDsx)vC>3orZiVtC@qy%N^7N!(pG7wv{yPP9hFW>7p1GxP3f-mP8}h>1}TG;A<9r?m@-@$p^Q{UDWjD!%2;KbGC`TBOj0H*Q^E_<&bh%Iieg@jw#2L6Us^DlyXKntDIBLD;JcD$|dEp zaz(kSTvM(qHSIQgZt@2KJuY6EG zDxZ|k$`|FU@=f`!{7`->zmz}9U*(^IsHlpmxJsy`N~yHUsI1DVyeg=YDyxdBs+y{+ zhH9#oYO9Xws-Ehrff}l@nm|pcCQ=itNz|ljGBvrHLQScrQd6sG)U;|kHG`T_&7@{l zv#43sY-)BjhniE(rRG-isCm_VYJRnVT1YLd7Ez0;#nj?z3ALnJN-eFHQOm03)beTt zwW3-{t*lm6tEtu18tQ*)O|_OJ)XVI!&Ff&QNEnv((w@9CfZbPo1wWP#3C;)g|gub(y+c zU7@a2SE;MjHR@V*ow{D#pl(z*shia;>Q;4|x?SC=?oxNFd(^$^K6Sr(Ks~4)QV**~ z)T8P#^|*RMJ*l2jPpfCtv+6nZf_hQCq+V99s8`i%>UH&odQ-in-d69Zch!69ef5F* zP<^C6R-dR()o1Dp^`-hseXYJx->UD__v#1rqxwnxtbS3ys^8S_>JRm&`b+(-{!#y` z|5QZ7G+ZMzQlm6lV>DLdG+q-lQIj-TQ#4i6G+i?^Q?oQ%b2L};v_K2BNQ<=uT0$+6 zmRL)oCDoE?$+Z+(N-dR^T1%s))zWF{wG3KDEt8g6%c5n~vT511oLVj|x0XlCtL4-3 zYX!7|S|P2lRzxeR71N4qCA5-SDXp|tMk}k8)5>cVw2E3Kt+G}{tEyGkYH0syHMLq= zZLN-0SF5Mh*BWRIwMJTFt%=rDYo;~VT4*h`R$6PVjn-Ccr?uBQXdSgqT4$|`)>Z4K z_0W21y|mt1AFZ#}PwTG@&<1LQw87dCZKyU(8?KGeMrxz9(b^botTs*?uT9V)FVCC6X*%`M0#R9iJnwXrYF}^ z=qdG7dTKq5o>ot%r`I#+8TCwhW<86ZRnMkp*K_DO^;~*xJ&&GO&!^|t3+M&)LV97n zh+b4LrWe;s=q2@1dTG6kURE!sm)9%k74=GbWxa}CRj;O3*K6oC^;&vuy^da2ucz17 z8|V%7MtWntiQZIirZ?AH=q>eDdTYIn-d1m?x7R!99raFnXT6KwRqv*E*L&za^*LUbU^2`_59kN=L;7L;h<;Q*rXSZ&=qL44`f2@)epWxH zpVu$w7xhc}W&MhNRllZR*Kg=I^;`OF{f>TDzo*~VALtMDNBU#^iT+f72A=CL^q2Z8 z{k8r^f2+UK-|HXrkNPM5v;IZ@s(;hJ>p%3L`Y-*r{zw0-|I-lzH82A=2!k{zgEkn0 zH8_Je1Vc0=LpBscH8evv48t@m!!{hlH9W&N0wXjcBQ_Ek35`TXVk3!>)JSF|H&PfW zjZ{WzBaM;PNN1!sG8h?+Oh#rSi;>mHW@I;V7&(nxMs6dIkr(7M@*4$=f<_^uuu;S) zY7{ey8zqdAMk%ATQN}21lrzd36^x2TC8M%Y#i(jjGpZXkjQ@<9MlGYZQOBrj)HCWE z4UC3HBcrj=#As?XGnyMMjFv_#qqWh-Xlt}H+8Z4}N28O`+2~?)HM$wyjUGl%qnFX! z=wtLX`WgL=0meXMkTKX8VhlBg8N-bc#z@oHl`@nwVfN{_`WE?h*7)Om`#&P3>and+toHouFXN_~l zdEHm(>~jcdkr$Odz<6jpG9DXGjHkvk++*%F_nG_61Li^Vka^fVVjeY*na9Bi^Q3voJZ+va&zk4V z^X3KfqIt=@Y+f<1n%B(h;D&kAyk*`t@0fSZd**%ff%(vUWIi^Zm`}}T=5z4Ed}+Qi zUz=~tx8^(Zz4^iXXnrz3n_tYY<~Q)&{9*nyf0@6{KjvTapNUwgg;}^oSfmAGOpCS{ zi?uk5w**VHBulmwOSLphx8RbnOv|!t%duR`vwSPCLMyUjD}j~JN@OJlNvxz+GAp^2 z!b)kSvQk@Vth81-E4`HgWVA9_nXN2VRx6v8-O6F*v~pRwtvps z5v!RS!0hE^l1F=%2nwVGMYtrk{GtCiK-YGbvv+F9+b4xppe$?9x%vASB_tnOA1tEbh= z>TUG_eXV|0e`|m>&>Cb7wuV?ktzp)1YXlf+jj~2tW2~{(IBUE$!J24IvL=Hm)>Lbn zHQkzF&9r7&v#mMSTx*^+A1tsIT8pg3))H%}wai*>t*};FtH5e&jkVTVXRWt3SR1WP z)@EyqwH0i$wp%-_oz^aEx3$OGYwff4TL-{F>yUNWI$|BQj#!NkZx@=vsu3Fc?b?b(8)4FBdw(eMWt$Wse>j8LZJ+dBKPpqfbGwZqa!g^`F zvR;EX)?4eH_1^kmeY8GVpRF&}SMbgHZvC)+TEDE{)*tJy_0NKcp^e&@joXAx+LTS( zjLq6GZm@YDqwbR+@?F=BJoypE@XR))|+3f6g4m&5vW#_i@*m>=Ic7D5nUC=IM z7Y0S_qINO6xLv|7X_vA~+hssmyPRF#u3%TRE7_IpDt1*+&8}|Ou>Z4b+O_Q3b{)Ge zsAt!=8`ur)Ms{PniQN=5vzyy3?3Q*byS3fMZfmy#?d=YBN4t~V+3sR@wY%BfK@Yp9 z-OKK6_p$rh{p|i=fIZM2WDmB7*hB4M_HcUy7-^5PN84lUvGzE7ygdO-v?tk~h?EUru`ye=EAGVL!N9|+war*=~X`ixB+h^>v z_Bs2!eF0pwFWHyvEB00Untk2A0dCs2?A!Jo`>uV@z7HPQ5A8?xWBZBy)P80^2QTcG z_AC3f{l!cCF@9z>?n@vXpZhcDdL!p<=BqnxQ^%eAaFt_a$+Zelh8>75<5wpq)sv? zxs$?4>7)Xwoit8bC!Let$>3xJnVif{7ALEd&B^ZM06CppPHrcUlh?@y@;e2bf=(f) zuv5e-3W_~{7zd!2pGesI7!=p1qmJ4c+O;FxpVIpLgiPC2K+8Rx8X z&N=T~a4v#N&SmF{bJe*9t~)oJo6argwsQyEb?!O$od?cC=Mi}9JaL{n&z$GZ3-Ho; z<-B&@IB%VI;Jx#~`RIIdK09B)SLd7a-TC4Cbbf)~&L8Km^Ur~(vx~Zzi@StNx)8l_ zX_s+Xmvebn0HQ0ova7hztZ+3~cMaEcEf=mL*Ku9fbA2}ep&Pleo4`%zCIX4wByLhS znVZ~A0aCiD+|+IwH!Vo#rgt;A8Qn~7W{}0r>SlAZyE#BkHETg|QR)&T#xHQicnZMP1n z>(+DYyA9liZX?jxZQ?d{o4L(F3%8})%5Cko0d3uOZhN!1mcQhE|j&;Ym`rl~ zf@$t_cZNIDodssQbKJS^Ja;}=;4X9*xr^N;V5z&zUGA=MSAtdUYIlvh)?EkIyBpk% z?k0CL*y3(=x4GNh9bl)s%iZnn0ejtj?tb@xdk`FQ54%U)qwX;7{Q5AjeB^Kg#9z7&du_b7pq)>?+oxILo7q2Vm=5_aa zcs)TcueaC7>kImM{k;L+KyMHj><#gTdc(kQZ-h6}8wEytW4y86I56Iu;7#-l#9O!sDZGrd`0wl~L{>&*l6y#?MvZxLARE%BCm%e>`ag}2gM<*f#5ytUpsZ#~%H zZS*#Io52=utGCVD4t97uy9pS;iB7w;?h=6&~mct61}@3;5I`wRYgh>!XZQ}=P7@FDK$ zQ$Fo8K7?<4&gXr>hw-H^`LeJ0(2DgnU-#kR?wh{l+yDPr`Ys$j-w#0OM}7^l`sqM=KZBpq&jd32S^TViHjv%V;pYUo{M>#XKQGAV z=l2VMf_@>tuwMie^^5t%{Su(0U&=2H%J^mda(;PG!LR670+szLepSC3sP5PB|MP2t zT7GT64yfzb^XvN!KtsQg-xxIUoBGZC=Aeb&(r*P?`)&NTeml_K@8EX?o&3&z7r!g$ z=6CmdfS!IYzqj89^!5As{lNf#pg+hT42Jka{b69ZKf)jBj{>9pG5%OE&L8hj02BR5 z{$zg&nCegSr-K>(On;U?8_e&~|EPZq9QRN7C;e04 zw137w3(ooH{R`ltf62cLuJ~8|YyNd`!@ud@0=NA;{#|g-zwbW)5B*2}WB&Ob?J zgBSiw{}p)czwzIKcm8|-gZ~kH@<01uz*qm9{~i4BfBL_`Z~u?~7yR>)01YsJ2Sfk? zJC6eJFkfTTgPAUQ}8qzqDl)IpjcEl3xn4>AN9L8c&ckOgE7vIW^ejv!}{3*-*+ z1bIQeAb(H*6buRlg+Y;^Xiy9k4@v|jL8+j0PzIC@$_3>?g`i?k2~-ZM1XV$`pn6aP z{1?;=YJu88ouDqL7t{|LfQCV%pfP9?G!2@8=0S^~C1@424%&dWLA#(m=n!-aI)TnX zm!K=?7IY7KfSy6Gpf~6f^bPuf{=tA?AQ%)34u*iC!7wm97!iyFqk_@F7%(;%7mNoJ zf{DQ-FgchKOa;?|>A?&zGnf_326KYB!8|ZOSO69Vi-N^qNw73n29^gaf|X!ZusT=+ z)&}c>^0xEVufezAJNOa&41R&%!5{E9_!qz$T!@BP z2x}!F5t4uk=@0^@Asccb4}?$*AwUqyAq*-*HPk|Ae1}G8hS1i4$0|HHLO1k6xI)7q z48sV-VFHjaOcW*tNy4OIGLSq>0aAvk!qgy5m=>f9(}x*A#xPTu8Dt5wf^1>-FbBvP z<_dFzJYimtFU%hn00qNBpm10uEDDN+#lsSyWLOH64$FjPLAkIzs1Q~RD}l;k6;L&- z7FGu}!vDgWpjKEL)Cuc`^+5fw0caRD3LAqaVN=j7Y#z1%EyGrzb=W3s3)+S4L5Hwo z*a>tFyMV4?x3D|t5%vswf!<*s&^PQC_6GyPfnZQLI2;0ohQq+{a6~u~j0#7CG2vJ+ zE*u|D029MWU~)JmoC>Ce)4_~zW;hGX4(EWm;k60kH}29}2_!j)iE zxEibp*MfE7`fvl-7;XZa!!6-fur1sUc7!{_U0`>(2kZ^^f&Jlu@E|x89tKCkqv0`d zJUjtThNr;k@Jx6XoD0u`3*p7^61W^*0awFo;Cgr?ya{fFx51t8F1Q!o46MfeiD3SWnBz}xU0cprWMAHz@KXYeKb3ciKk!H@7~_zV0F|A4>YKY&DN z1S@M19>F?JL`D>#BPN09qJp4MR5&UEiblmi@u&nS8I=O1qcTxhP%bJDDnu1Q zrKmEf5>*A&qUuo%@LyCD)QW0@I#FFvFRBk3L=B@xpmEd$G>w{p=1~jKGHL}{M{T0E zpk34+bci~FPElvjCF%;gMcqMD|8Xcm|q%>i?xd0>9D04$6afyL1huryj0 zEe9*2m0(r08mx)df_2e)up!zAHbtAkmS`*37HtPRqMcw@v^&}Z_D1`_{^$TW7##wK zqa)yGbPODiPJolqDR4SE1I|Y0!1?F`xENgmm!m7-YIF@;k8VUa!L8^vxD(w4_oDmY zLG%zjiXMX}(Npj&dJbMhFTtzmHFy)f1@EHw;6wBge2PAUFVR=voQpkV?Kt+Wh}-J{fgxnA||mK!;m`GV;Eb- zW(+Oq*p8uD6ubX_ctQCKrA8daaNOfKP5=_di9q5w2}l|z1IgnQAZ45iq>j^ov~fC+ zKF$C##+g9oI19)cX9L;e93W?$3*-iQ;=CYVoF5d33xYy%VNfJ43W~+WL5a8|C>56m zW#Y1+TwEShh%16hab-{?t_rHf)j8TSIcL7%uU=oj|~1LA>TP&^n6 ziHCw=@o+FA9tlQ)(eW5CHXaAY#}mNBcoLW#PXSZoX<#~-5zhp(;@MzMJQvK1=Ys|D zLa-=a43>bU@iMSHUIA9dtHA1b4Oknm1M9(tcq7;pZw6c9tzcWc9qa%*<6U5Pya()! z_ksQK0dO!r1P+5E@lkLrJ`PUAC&8)sG&lp!#^=EK_yV{XUjmooE8r@)7GDQ9;+x=B zd>h<}?}B^ae*6GDj30r=@e}YAJd2-$7x7E*Dt-;##Baen@IL+kKE|KG=lBcw3ckhP U!H@VS_!a*Kf56}PA3zfP4@B%OzW@LL literal 0 HcmV?d00001 diff --git a/negpy/assets/portable_cms/lch-reciprocal-u16le.bin b/negpy/assets/portable_cms/lch-reciprocal-u16le.bin new file mode 100644 index 0000000000000000000000000000000000000000..ce5bffbf0e84d8d0575be81a370b596cfec7703d GIT binary patch literal 131074 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≪^CGyQY?^Zg6`OZ+SR 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

{_k)2e|yIN-+lgPZty>w!vD@m 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 None: self.scan_started.emit() self.scan_batch_requested.emit(req) - def start_roll_preview(self, req: RollPreviewRequest) -> None: + def start_roll_preview(self, req: ScanRollPreviewRequest) -> None: """Preview strip slots (results via scan_roll_preview_ready, then scan_roll_preview_finished). No scan_started — preview is dialog-local.""" self.scan_worker.prepare_scan() @@ -2320,7 +2334,7 @@ def request_roll_devices(self) -> None: self._ensure_roll_thread() self.roll_devices_requested.emit() - def start_roll_preview(self, req: RollPreviewRequest) -> None: + def start_coolscan_roll_preview(self, req: CoolscanRollPreviewRequest) -> None: self._ensure_roll_thread() self.roll_preview_requested.emit(req) @@ -2333,12 +2347,35 @@ def approve_roll_slot(self, slot: int) -> None: self.roll_approve_requested.emit(slot) def start_roll_scan(self, req: RollBatchScanRequest) -> None: + self.roll_worker.prepare_batch() self._ensure_roll_thread() self.roll_scan_requested.emit(req) def roll_safe_stop(self) -> None: self.roll_worker.safe_stop() + @property + def shutdown_block_reason(self) -> str | None: + return self._shutdown_block_reason + + def request_shutdown(self) -> bool: + """Prove the scanner reservation closed before the UI may exit.""" + + if self._roll_shutdown_complete: + return True + try: + self.roll_worker.shutdown() + except Exception as error: + logger.exception("application shutdown blocked by scanner teardown") + self._shutdown_block_reason = str(error) + self.roll_status.emit( + "Cannot exit: the Coolscan reservation is not safely closed." + ) + return False + self._roll_shutdown_complete = True + self._shutdown_block_reason = None + return True + def _on_roll_scan_finished(self, outputs: list) -> None: """Feed newly-written roll frames into NegPy's file list, like the plain Scan and Camera Scanning routes already do. Each output is already one complete per-slot RGB @@ -3262,6 +3299,11 @@ def cleanup(self) -> None: """ if self._cleaned_up: return + if not self.request_shutdown(): + raise RollScanningError( + self._shutdown_block_reason + or "Coolscan reservation is not safely closed" + ) self._cleaned_up = True self._render_debounce.stop() self._cursor_readout_timer.stop() @@ -3293,7 +3335,6 @@ def cleanup(self) -> None: if self.capture_thread.isRunning(): self.capture_thread.quit() self.capture_thread.wait() - self.roll_worker.shutdown() if self.roll_thread.isRunning(): self.roll_thread.quit() self.roll_thread.wait() diff --git a/negpy/desktop/frozen_entry.py b/negpy/desktop/frozen_entry.py new file mode 100644 index 00000000..aac6eccb --- /dev/null +++ b/negpy/desktop/frozen_entry.py @@ -0,0 +1,272 @@ +"""Minimal frozen auxiliary entry points for scanner and packaging checks. + +This module intentionally imports only the standard library at module load. +The real executable dispatches here before importing the Qt desktop, so a +desktop-only dependency or startup regression cannot block the isolated +LS-5000 worker or the offline post-build smoke. +""" + +from __future__ import annotations + +import sys + + +CAPTURE_HELPER_FLAG = "--ls5000-capture-helper" +LIVE_ACCEPTANCE_FLAG = "--ls5000-live-acceptance" +PACKAGING_SMOKE_FLAG = "--negpy-packaging-smoke" +PACKAGING_SMOKE_OK = "NegPy frozen scanner packaging smoke passed" + + +def _require_registered_repair_engine() -> None: + """Import the production bridge and reject a silently unavailable engine.""" + + from negpy.infrastructure.roll import fauxice_bridge + from negpy.infrastructure.roll import repair as roll_repair + + if not fauxice_bridge.engine_available() or not roll_repair.available(): + raise RuntimeError("the frozen Digital ICE repair bridge did not register an engine") + + +def _require_pinned_hybrid_runtime(*, loader=None) -> str: + """Load and independently rehash the configured hybrid model weights.""" + + import hashlib + import os + import re + import stat + from pathlib import Path + + if loader is None: + from negpy.services.repair.hybrid_runtime_manifest import ( + load_default_hybrid_runtime_manifest, + ) + + loader = load_default_hybrid_runtime_manifest + + runtime = loader() + if runtime is None: + raise RuntimeError("the pinned hybrid runtime is not installed") + try: + runtime.validate_files() + except ValueError as error: + raise RuntimeError(f"the pinned hybrid runtime is invalid: {error}") from error + + expected = runtime.model_weights_sha256 + if not isinstance(expected, str) or re.fullmatch(r"[0-9a-f]{64}", expected) is None: + raise RuntimeError("the hybrid model weights pin is not a lowercase SHA-256") + try: + weights = Path(runtime.model_weights) + linked = os.lstat(weights) + if stat.S_ISLNK(linked.st_mode) or not stat.S_ISREG(linked.st_mode): + raise RuntimeError("the hybrid model weights must be a regular non-symlink file") + flags = os.O_RDONLY | getattr(os, "O_CLOEXEC", 0) | getattr(os, "O_NOFOLLOW", 0) + descriptor = os.open(weights, flags) + try: + opened = os.fstat(descriptor) + identity = ( + opened.st_dev, + opened.st_ino, + opened.st_size, + opened.st_mtime_ns, + opened.st_ctime_ns, + ) + if not stat.S_ISREG(opened.st_mode) or identity != ( + linked.st_dev, + linked.st_ino, + linked.st_size, + linked.st_mtime_ns, + linked.st_ctime_ns, + ): + raise RuntimeError("the hybrid model weights changed while opening") + digest = hashlib.sha256() + while block := os.read(descriptor, 1024 * 1024): + digest.update(block) + after_read = os.fstat(descriptor) + finally: + os.close(descriptor) + after_path = os.lstat(weights) + except RuntimeError: + raise + except (OSError, TypeError, ValueError) as error: + raise RuntimeError(f"the hybrid model weights could not be verified: {error}") from error + + for metadata in (after_read, after_path): + if ( + metadata.st_dev, + metadata.st_ino, + metadata.st_size, + metadata.st_mtime_ns, + metadata.st_ctime_ns, + ) != identity: + raise RuntimeError("the hybrid model weights changed while hashing") + actual = digest.hexdigest() + if actual != expected: + raise RuntimeError(f"the hybrid model weights SHA-256 does not match the pin: expected {expected}, got {actual}") + return actual + + +def run_packaging_smoke_checks() -> None: + """Validate frozen scanner/color/DICE resources without opening a device.""" + + import ctypes + import hashlib + import importlib + from pathlib import Path + + from llvmlite.binding import check_jit_execution + + if not getattr(sys, "frozen", False): + raise RuntimeError("the packaging smoke is only valid in a frozen application") + meipass = getattr(sys, "_MEIPASS", None) + if not isinstance(meipass, str) or not meipass: + raise RuntimeError("the frozen application has no PyInstaller resource root") + resource_root = Path(meipass) + + # This is the exact hardened-runtime operation used by Numba's LLVM JIT: + # allocate an anonymous RW page and transition it to RX. Keep it explicit + # so the post-sign smoke catches missing executable-memory entitlements. + check_jit_execution() + + from coolscanpy import ( + DigitalIceAcquisition, + DigitalIceAcquisitionEvidence, + build_digital_ice_acquisition_evidence, + ) + from coolscanpy.protocol.ls5000_single_pass.bundle import verify_capture_bundle + from coolscanpy.protocol.ls5000_single_pass.density import ( + NikonDensityEvidence, + NikonExactBuilderEvidence, + build_nikon_density_evidence, + build_nikon_exact_builder_evidence, + ) + from coolscanpy.protocol.ls5000_single_pass.usb_backend import bundled_libusb_path + from negpy.services.repair.fauxice_hybrid_runner import ( + HybridRuntimeConfig, + run_hybrid_repair, + ) + from negpy.services.roll.nikon_icc import ( + NIKON_ADOBE_RGB_PROFILE_BYTES, + NIKON_ADOBE_RGB_PROFILE_SHA256, + nikon_adobe_rgb_profile, + ) + from negpy.services.roll.portable_builder import PortableStage1Builder + from negpy.services.roll.portable_cms import PortableCMSOnEvaluator + + required_callables = ( + DigitalIceAcquisition, + DigitalIceAcquisitionEvidence, + build_digital_ice_acquisition_evidence, + NikonDensityEvidence, + NikonExactBuilderEvidence, + build_nikon_density_evidence, + build_nikon_exact_builder_evidence, + HybridRuntimeConfig, + run_hybrid_repair, + ) + if not all(callable(value) for value in required_callables): + raise RuntimeError("a required Coolscan or DICE runtime API is unavailable") + for module_name in ( + "portable_digital_ice.backend", + "portable_digital_ice.fast_cpu.engine", + "portable_digital_ice.metal_backend.engine", + ): + importlib.import_module(module_name) + _require_registered_repair_engine() + _require_pinned_hybrid_runtime() + + bundle_sha256 = verify_capture_bundle(require_python_sources=False) + if len(bundle_sha256) != 64: + raise RuntimeError("the packaged Coolscan capture bundle identity is invalid") + + expected_libusb = resource_root / "coolscanpy" / "_native" / "libusb-1.0.dylib" + libusb_path = bundled_libusb_path() + if libusb_path != expected_libusb.resolve(strict=True): + raise RuntimeError(f"Coolscan resolved an unexpected frozen libusb: {libusb_path}") + libusb = ctypes.CDLL(str(libusb_path)) + if not all(hasattr(libusb, symbol) for symbol in ("libusb_init", "libusb_exit")): + raise RuntimeError("the packaged libusb is missing required entry points") + + # Constructors validate every exact LUT's size and pinned SHA-256, the + # portable evaluator source identity, and the strict 12-event receipt. + PortableStage1Builder() + PortableCMSOnEvaluator() + + profile = nikon_adobe_rgb_profile() + if len(profile) != NIKON_ADOBE_RGB_PROFILE_BYTES or hashlib.sha256(profile).hexdigest() != NIKON_ADOBE_RGB_PROFILE_SHA256: + raise RuntimeError("the exact Nikon output ICC profile failed validation") + + expected_icc = { + "AdobeCompat-v4.icc", + "DisplayP3-v4.icc", + "GrayGamma2.2.icc", + "ProPhoto-v4.icc", + "RGBScan.icc", + "Rec2020-v4.icc", + "sRGB-v4.icc", + } + icc_dir = resource_root / "icc" + packaged_icc = {path.name for path in icc_dir.glob("*.icc") if path.is_file()} + if packaged_icc != expected_icc: + raise RuntimeError("the frozen app's ICC asset set is incomplete or unexpected") + + +def dispatch_packaging_smoke(argv: list[str]) -> bool: + """Run the private post-build smoke before desktop initialization.""" + + if len(argv) < 2 or argv[1] != PACKAGING_SMOKE_FLAG: + return False + if len(argv) != 2: + raise RuntimeError("the packaging smoke does not accept arguments") + run_packaging_smoke_checks() + print(PACKAGING_SMOKE_OK, flush=True) + return True + + +def dispatch_capture_helper(argv: list[str]) -> bool: + """Run the frozen LS-5000 worker before desktop initialization.""" + + if len(argv) < 2 or argv[1] != CAPTURE_HELPER_FLAG: + return False + + from coolscanpy.protocol.ls5000_single_pass.capture_process import ( + CAPTURE_HELPER_FLAG as COOLSCAN_CAPTURE_HELPER_FLAG, + ) + from coolscanpy.protocol.ls5000_single_pass.worker import main as worker_main + + if COOLSCAN_CAPTURE_HELPER_FLAG != CAPTURE_HELPER_FLAG: + raise RuntimeError("NegPy and Coolscan helper flags do not match") + worker_main(argv[2:]) + return True + + +def dispatch_live_acceptance(argv: list[str]) -> bool: + """Run the one-shot LS-5000 acceptance before desktop initialization.""" + + if len(argv) < 2 or argv[1] != LIVE_ACCEPTANCE_FLAG: + return False + + from negpy.services.roll.live_acceptance import main as live_acceptance_main + + exit_code = live_acceptance_main(argv[2:]) + if exit_code: + raise SystemExit(exit_code) + return True + + +def dispatch_frozen_auxiliary(argv: list[str]) -> bool: + """Dispatch a helper/smoke command, or leave normal desktop startup alone.""" + + return dispatch_packaging_smoke(argv) or dispatch_live_acceptance(argv) or dispatch_capture_helper(argv) + + +__all__ = [ + "CAPTURE_HELPER_FLAG", + "LIVE_ACCEPTANCE_FLAG", + "PACKAGING_SMOKE_FLAG", + "PACKAGING_SMOKE_OK", + "dispatch_capture_helper", + "dispatch_frozen_auxiliary", + "dispatch_live_acceptance", + "dispatch_packaging_smoke", + "run_packaging_smoke_checks", +] diff --git a/negpy/desktop/main.py b/negpy/desktop/main.py index 578e15d4..e9db79c1 100644 --- a/negpy/desktop/main.py +++ b/negpy/desktop/main.py @@ -1,25 +1,39 @@ import os import sys +import tempfile +from typing import Callable -from PyQt6.QtCore import Qt, qInstallMessageHandler +from PyQt6.QtCore import QEvent, QObject, Qt, qInstallMessageHandler from PyQt6.QtGui import QIcon -from PyQt6.QtWidgets import QApplication, QProxyStyle, QStyle +from PyQt6.QtWidgets import QApplication, QMessageBox, QProgressDialog, QProxyStyle, QStyle -from negpy.desktop.controller import AppController -from negpy.desktop.session import DesktopSessionManager -from negpy.desktop.view.main_window import MainWindow -from negpy.infrastructure.storage.repository import StorageRepository -from negpy.services.assets.crosstalk import CrosstalkProfiles -from negpy.services.assets.gear import GearProfiles +from negpy.desktop.frozen_entry import ( + dispatch_capture_helper as _dispatch_capture_helper, + dispatch_packaging_smoke as _dispatch_packaging_smoke, +) from negpy.kernel.system.config import APP_CONFIG, BASE_USER_DIR from negpy.kernel.system.logging import get_logger, setup_logging from negpy.kernel.system.override import apply as apply_override from negpy.kernel.system.override import load_or_create as load_override from negpy.kernel.system.parallel import configure_cpu_parallel from negpy.kernel.system.paths import get_resource_path +from negpy.services.assets.crosstalk import CrosstalkProfiles +from negpy.services.assets.gear import GearProfiles +from negpy.services.repair.hybrid_runtime_manifest import ( + HybridRuntimeManifestError, + load_default_hybrid_runtime_manifest, +) logger = get_logger(__name__) +# A Finder-launched frozen macOS app can be stopped by the first access to +# ~/Documents before Qt has created a foreground window to host the TCC prompt. +# The handoff process below has no user-data side effects beyond a temporary, +# empty access probe; after consent it re-execs so the real process retains the +# existing pre-QApplication override/RHI/UI-scale ordering. +_MACOS_DOCUMENTS_READY_ENV = "NEGPY_MACOS_DOCUMENTS_READY" +_MACOS_ACCESS_PROBE_PREFIX = ".negpy-access-" + # qtawesome paints toolbar icons into a null pixmap when a button is asked to # render before its first layout has given it valid geometry (e.g. while the # startup "Restore Session" dialog spins a modal loop). The paint is harmless @@ -56,6 +70,100 @@ def styleHint(self, hint, option=None, widget=None, returnData=None): return super().styleHint(hint, option, widget, returnData) +class _ApplicationShutdownGate(QObject): + """Consume every Qt quit request until scanner teardown is proven.""" + + def __init__(self, allow_shutdown: Callable[[], bool]) -> None: + super().__init__() + self._allow_shutdown = allow_shutdown + + def eventFilter(self, watched, event) -> bool: + if event.type() == QEvent.Type.Quit and not self._allow_shutdown(): + return True + return super().eventFilter(watched, event) + + +class _MacOSDocumentsHandoff(QProgressDialog): + """A startup-only progress window that cannot dismiss the active TCC request.""" + + def closeEvent(self, event) -> None: + event.ignore() + + def keyPressEvent(self, event) -> None: + if event.key() == Qt.Key.Key_Escape: + event.accept() + return + super().keyPressEvent(event) + + +def _probe_user_data_access(user_dir: str) -> None: + """Prove that the configured user-data directory is writable without leaving a file behind.""" + + os.makedirs(user_dir, exist_ok=True) + fd: int | None = None + probe_path: str | None = None + try: + fd, probe_path = tempfile.mkstemp(prefix=_MACOS_ACCESS_PROBE_PREFIX, dir=user_dir) + finally: + if fd is not None: + try: + os.close(fd) + finally: + if probe_path is not None: + os.unlink(probe_path) + + +def _show_macos_documents_handoff() -> QProgressDialog: + """Show the foreground, non-cancellable UI that makes a TCC prompt visible.""" + + handoff = _MacOSDocumentsHandoff("Opening your existing NegPy workspace…", None, 0, 0) + handoff.setWindowTitle("NegPy") + handoff.setCancelButton(None) + handoff.setAutoClose(False) + handoff.setAutoReset(False) + handoff.setWindowFlag(Qt.WindowType.WindowCloseButtonHint, False) + handoff.setWindowFlag(Qt.WindowType.WindowContextHelpButtonHint, False) + handoff.setWindowModality(Qt.WindowModality.ApplicationModal) + handoff.show() + return handoff + + +def _macos_frozen_documents_handoff() -> bool: + """Handle the visible first-process permission check for a frozen macOS bundle. + + Returns True only when access was denied/unavailable and ``main`` must stop. + On success this function replaces the process and does not return. The child + consumes the one-process environment sentinel and follows normal startup. + """ + + if not (sys.platform == "darwin" and getattr(sys, "frozen", False)): + return False + if os.environ.pop(_MACOS_DOCUMENTS_READY_ENV, None) == "1": + return False + + app = QApplication.instance() or QApplication(sys.argv) + _handoff = _show_macos_documents_handoff() + app.processEvents() + + try: + _probe_user_data_access(BASE_USER_DIR) + except OSError: + QMessageBox.critical( + None, + "NegPy needs Documents access", + "NegPy could not open your existing Documents/NegPy workspace. " + "Your existing files were not changed. Grant NegPy access to the Documents folder " + "in macOS Privacy & Security, then reopen the app.", + ) + return True + + env = os.environ.copy() + env[_MACOS_DOCUMENTS_READY_ENV] = "1" + executable = os.path.abspath(sys.executable) + os.execve(executable, [executable, *sys.argv[1:]], env) + raise AssertionError("os.execve unexpectedly returned") + + def _install_exception_hook() -> None: """Log every unhandled exception — especially ones raised inside a Qt slot — to the file log and show a non-fatal notice, instead of letting PyQt call qFatal() and abort with a native crash @@ -101,10 +209,47 @@ def _bootstrap_environment() -> None: GearProfiles.ensure_user_dir() +def _load_desktop_hybrid_runtime(): + """Load the optional external companion without risking desktop startup.""" + + try: + runtime = load_default_hybrid_runtime_manifest() + except HybridRuntimeManifestError as error: + logger.error("Hybrid runtime disabled: %s", error) + return None + if runtime is None: + logger.info("Hybrid runtime is not installed; exact Digital ICE remains available") + else: + try: + runtime.validate_files() + except ValueError as error: + logger.error("Hybrid runtime disabled: %s", error) + return None + logger.info("Loaded pinned external hybrid runtime") + return runtime + + def main() -> None: """ Desktop entry point. """ + if _dispatch_packaging_smoke(sys.argv) or _dispatch_capture_helper(sys.argv): + return + + # A successful macOS handoff replaces this process. A denied handoff has + # already shown a visible explanation and must not initialize a silent, + # empty replacement workspace. + if _macos_frozen_documents_handoff(): + return + + # Keep desktop-only imports behind the helper dispatch. A UI regression + # must never prevent the frozen capture worker or packaging smoke from + # starting in their isolated process. + from negpy.desktop.controller import AppController + from negpy.desktop.session import DesktopSessionManager + from negpy.desktop.view.main_window import MainWindow + from negpy.infrastructure.storage.repository import StorageRepository + override_cfg = load_override(APP_CONFIG.override_toml_path) setup_logging(level=override_cfg.log_level_int) _install_exception_hook() # log unhandled slot exceptions to negpy.log instead of aborting @@ -153,9 +298,16 @@ def main() -> None: app.setStyleSheet(load_stylesheet()) session_manager = DesktopSessionManager(repo) - controller = AppController(session_manager) + controller = AppController( + session_manager, + hybrid_runtime=_load_desktop_hybrid_runtime(), + ) window = MainWindow(controller) + shutdown_gate = _ApplicationShutdownGate( + window.request_shutdown_for_exit, + ) + app.installEventFilter(shutdown_gate) window.show() exit_code = app.exec() diff --git a/negpy/desktop/view/main_window.py b/negpy/desktop/view/main_window.py index e921ad47..b64514bc 100644 --- a/negpy/desktop/view/main_window.py +++ b/negpy/desktop/view/main_window.py @@ -162,7 +162,32 @@ def _restore_window_geometry(self) -> None: self.resize(w, h) self.move(x, y) + def request_shutdown_for_exit(self) -> bool: + """Gate both window-close and application-level quit requests.""" + + if not self.controller.request_shutdown(): + reason = self.controller.shutdown_block_reason or ( + "The scanner reservation has not closed safely yet." + ) + self.statusBar().showMessage( + "Exit blocked until the Coolscan closes safely." + ) + QMessageBox.critical( + self, + "Scanner still reserved", + "NegPy cannot exit while the Coolscan reservation is unresolved.\n\n" + f"{reason}\n\nWait for the current operation to stop, then close again.", + ) + self.show() + self.raise_() + self.activateWindow() + return False + return True + def closeEvent(self, event) -> None: + if not self.request_shutdown_for_exit(): + event.ignore() + return try: self.controller.session.repo.save_global_setting("window_geometry", [self.x(), self.y(), self.width(), self.height()]) except Exception: diff --git a/negpy/desktop/view/sidebar/coolscan_roll.py b/negpy/desktop/view/sidebar/coolscan_roll.py index 7b6d5f24..b4b6fe37 100644 --- a/negpy/desktop/view/sidebar/coolscan_roll.py +++ b/negpy/desktop/view/sidebar/coolscan_roll.py @@ -42,11 +42,14 @@ from negpy.infrastructure.roll import coolscanpy_roll from negpy.infrastructure.roll.repair import RepairMode from negpy.infrastructure.roll.settings import RollScanSettings +from negpy.services.roll.exact_color import PositiveColorMode from negpy.services.roll.service import RollFrameOutput if TYPE_CHECKING: import coolscanpy + from negpy.desktop.workers.roll_worker import RollBatchScanRequest + _SLOT_ROLE = Qt.ItemDataRole.UserRole _WARN_COLOR = "#C8922E" # matches ScanlightSidebar's advisory tone _THUMBNAIL_SIZE = QSize(96, 96) @@ -86,6 +89,7 @@ def __init__(self, controller) -> None: self._thumbnails: dict[int, "coolscanpy.Thumbnail"] = {} self._scanning = False self._stopping = False # safe-stop acknowledged, waiting for the in-flight frame + self._active_scan_request: RollBatchScanRequest | None = None self._init_ui() self._connect_signals() @@ -114,7 +118,8 @@ def _update_settings_from_ui(self) -> None: write_unrepaired=self.write_unrepaired_check.isChecked(), write_repaired=self.write_repaired_check.isChecked(), write_positive=self.write_positive_check.isChecked(), - repair_mode=self.repair_mode_combo.currentData() or RepairMode.EXACT.value, + repair_mode=self.repair_mode_combo.currentData() or RollScanSettings.defaults().repair_mode, + positive_mode=self.positive_mode_combo.currentData() or PositiveColorMode.NIKON_EXACT.value, ) if updated == self._settings: return @@ -174,7 +179,9 @@ def _init_ui(self) -> None: folder_row.addWidget(self.folder_browse) out_form.addRow("Folder", folder_row) self.pattern_edit = QLineEdit(self._settings.filename_pattern) - self.pattern_edit.setToolTip('Jinja2 template. Variables: {{ date }}, {{ seq }} (the slot number).\nExample: {{ date }}_{{ "%03d" % seq }}') + self.pattern_edit.setToolTip( + 'Jinja2 template. Variables: {{ date }}, {{ seq }} (the slot number).\nExample: {{ date }}_{{ "%03d" % seq }}' + ) out_form.addRow("Filename", self.pattern_edit) # Three independent tiers, not a single three-way choice -- any combination is @@ -192,28 +199,41 @@ def _init_ui(self) -> None: self.write_repaired_check.setChecked(self._settings.write_repaired) self.write_repaired_check.setToolTip( "Tier 2: the unrepaired capture with infrared-guided dust/scratch repair applied, " - "still scanner-linear. Needs a repair engine to be registered; degrades with a " - "status in the receipt when none is available." + "still scanner-linear. It needs the frame-bound scanner prepass and validity evidence; " + "the Tier 1 TIFFs alone cannot recreate parity repair later." ) self.write_positive_check = QCheckBox("Positive") self.write_positive_check.setChecked(self._settings.write_positive) self.write_positive_check.setToolTip( - "Tier 3: the repaired capture inverted through NegPy's own rendering pipeline. " - "A regenerable preview, not an edit -- it will be superseded as the pipeline " - "improves, and needs Tier 2 to be producible first." + "Tier 3: the repaired capture converted through the Positive color path selected " + "below. Nikon exact is the parity default; NegPy approximate is an explicitly " + "labeled preview choice. Tier 2 must be producible first." ) tiers_row.addWidget(self.write_unrepaired_check) tiers_row.addWidget(self.write_repaired_check) tiers_row.addWidget(self.write_positive_check) out_form.addRow("Write", tiers_row) + self.positive_mode_combo = QComboBox() + self.positive_mode_combo.addItem("Nikon C-41 exact (parity)", PositiveColorMode.NIKON_EXACT.value) + self.positive_mode_combo.addItem("NegPy approximate (preview)", PositiveColorMode.NEGPY_APPROXIMATE.value) + self.positive_mode_combo.setToolTip( + "Color path for Tier 3. Nikon exact is the fail-closed parity path and requires " + "frame-bound builder evidence. NegPy approximate is a selectable preview path " + "and is never labeled Nikon-exact." + ) + idx = self.positive_mode_combo.findData(self._settings.positive_mode) + if idx >= 0: + self.positive_mode_combo.setCurrentIndex(idx) + out_form.addRow("Positive color", self.positive_mode_combo) + self.repair_mode_combo = QComboBox() - self.repair_mode_combo.addItem("Exact", RepairMode.EXACT.value) - self.repair_mode_combo.addItem("Hybrid (inpaint severe defects)", RepairMode.HYBRID.value) + self.repair_mode_combo.addItem("Exact (Nikon parity)", RepairMode.EXACT.value) + self.repair_mode_combo.addItem("Hybrid (generative severe-defect fill)", RepairMode.HYBRID.value) self.repair_mode_combo.setToolTip( - "Governs Tier 2, and Tier 3 through it, whenever a repair engine is registered. Hybrid " - "additionally routes severe zero-signal regions to an inpainting model and costs " - "substantially more time per frame than Exact." + "Governs Tier 2, and Tier 3 through it. Hybrid uses a generative inpainting model for " + "severe zero-signal regions and records every synthesized pixel in a disclosure mask. " + "Those fills are not recovered film data and are not bit-deterministic Nikon output." ) idx = self.repair_mode_combo.findData(self._settings.repair_mode) if idx >= 0: @@ -274,7 +294,9 @@ def _init_ui(self) -> None: layout.addWidget(self.scan_btn) self.safe_stop_btn = QPushButton(qta.icon("fa5s.stop", color=THEME.text_secondary), " Safe Stop") - self.safe_stop_btn.setToolTip("Finish the frame in flight, then stop before the next one -- the transport can't be aborted mid-pull.") + self.safe_stop_btn.setToolTip( + "Finish the frame in flight, then stop before the next one -- the transport can't be aborted mid-pull." + ) self.safe_stop_btn.setEnabled(False) layout.addWidget(self.safe_stop_btn) @@ -290,6 +312,7 @@ def _connect_signals(self) -> None: w.editingFinished.connect(self._update_settings_from_ui) for cb in (self.write_unrepaired_check, self.write_repaired_check, self.write_positive_check): cb.toggled.connect(self._update_settings_from_ui) + self.positive_mode_combo.currentIndexChanged.connect(self._update_settings_from_ui) self.repair_mode_combo.currentIndexChanged.connect(self._update_settings_from_ui) self.preview_btn.clicked.connect(self._on_preview_clicked) self.contact_sheet.itemSelectionChanged.connect(self._on_selection_changed) @@ -386,7 +409,9 @@ def _on_preview_clicked(self) -> None: self._set_status("Reading roll transport…") self.progress_bar.setVisible(True) self.progress_bar.setValue(0) - self.controller.start_roll_preview(RollPreviewRequest(device_id=device_id)) + self.controller.start_coolscan_roll_preview( + RollPreviewRequest(device_id=device_id) + ) @pyqtSlot(list) def _on_preview_ready(self, thumbnails: "list[coolscanpy.Thumbnail]") -> None: @@ -491,15 +516,17 @@ def _on_scan_clicked(self) -> None: write_unrepaired=self.write_unrepaired_check.isChecked(), write_repaired=self.write_repaired_check.isChecked(), write_positive=self.write_positive_check.isChecked(), - repair_mode=self.repair_mode_combo.currentData() or RepairMode.EXACT.value, + repair_mode=self.repair_mode_combo.currentData() or RollScanSettings.defaults().repair_mode, + positive_mode=self.positive_mode_combo.currentData() or PositiveColorMode.NIKON_EXACT.value, ) + self._active_scan_request = req self.set_scanning(True) self.controller.start_roll_scan(req) def _on_safe_stop_clicked(self) -> None: self._stopping = True self.safe_stop_btn.setEnabled(False) - self._set_status("Stopping after the current frame…") + self._set_status("Stopping the current frame safely…") self.controller.roll_safe_stop() @pyqtSlot(float, str) @@ -516,16 +543,23 @@ def _on_frame_written(self, output: RollFrameOutput) -> None: @pyqtSlot(list) def _on_finished(self, outputs: list[RollFrameOutput]) -> None: + issues = self._completion_issues(outputs) + self._active_scan_request = None self.set_scanning(False) - self._set_status(f"Scanned {len(outputs)} frame(s).") + if issues: + self._set_status("Completed with issues — " + "; ".join(issues)) + else: + self._set_status(f"Scanned {len(outputs)} frame(s).") @pyqtSlot() def _on_cancelled(self) -> None: + self._active_scan_request = None self.set_scanning(False) self._set_status("Stopped.") @pyqtSlot(str) def _on_error(self, msg: str) -> None: + self._active_scan_request = None self.set_scanning(False) self.progress_bar.setVisible(False) self._set_status(f"Error: {msg}") @@ -539,6 +573,52 @@ def _set_status(self, text: str) -> None: self.status_label.setText(text) self.status_label.setVisible(bool(text)) + def _completion_issues( + self, + outputs: list[RollFrameOutput], + ) -> list[str]: + """Describe requested outputs that the fail-closed service withheld.""" + + request = self._active_scan_request + if request is None: + return [] + issues: list[str] = [] + completed_slots = {output.slot for output in outputs} + missing_slots = [ + slot for slot in request.slots if slot not in completed_slots + ] + if missing_slots: + issues.append( + "slot(s) " + + ", ".join(str(slot) for slot in missing_slots) + + " did not complete" + ) + for output in outputs: + prefix = f"slot {output.slot}: " + if request.write_unrepaired and not output.rgb_path: + issues.append(prefix + "requested unrepaired output unavailable") + if request.write_repaired and not output.repaired_rgb_path: + issues.append(prefix + "requested repaired output unavailable") + repair_was_needed = request.write_repaired or request.write_positive + if ( + repair_was_needed + and request.repair_mode == RepairMode.HYBRID.value + and not ( + output.native_synthesis_mask_path + and output.hybrid_receipt_path + ) + ): + issues.append(prefix + "Hybrid repair degraded or unavailable") + if request.write_positive and not output.positive_path: + if ( + request.positive_mode + == PositiveColorMode.NIKON_EXACT.value + ): + issues.append(prefix + "Nikon exact positive unavailable") + else: + issues.append(prefix + "requested positive output unavailable") + return issues + # ── browse ──────────────────────────────────────────────────────── def _on_browse_folder(self) -> None: diff --git a/negpy/desktop/view/sidebar/export.py b/negpy/desktop/view/sidebar/export.py index 9a0340a6..de968942 100644 --- a/negpy/desktop/view/sidebar/export.py +++ b/negpy/desktop/view/sidebar/export.py @@ -275,9 +275,7 @@ def _browse_contact_sheet_output_path(self) -> None: self.cs_output_path_edit.setText(path) def _update_cs_colors_btn_tooltip(self) -> None: - self.cs_colors_btn.setToolTip( - f"Background {self._cs_background_color}, labels {self._cs_label_color}" - ) + self.cs_colors_btn.setToolTip(f"Background {self._cs_background_color}, labels {self._cs_label_color}") def _on_cs_colors_clicked(self) -> None: try: diff --git a/negpy/desktop/workers/roll_worker.py b/negpy/desktop/workers/roll_worker.py index 00548c87..adfd7c5c 100644 --- a/negpy/desktop/workers/roll_worker.py +++ b/negpy/desktop/workers/roll_worker.py @@ -9,19 +9,25 @@ from __future__ import annotations +import threading from dataclasses import dataclass from PyQt6.QtCore import QObject, pyqtSignal, pyqtSlot from negpy.infrastructure.roll import coolscanpy_roll -from negpy.infrastructure.roll.repair import RepairMode +from negpy.infrastructure.roll.repair import RepairCancelled from negpy.infrastructure.roll.settings import RollScanSettings from negpy.kernel.system.logging import get_logger +from negpy.services.repair.fauxice_hybrid_runner import HybridRuntimeConfig from negpy.services.roll.service import RollFrameOutput, RollScanningService logger = get_logger(__name__) +class RollShutdownBlocked(RuntimeError): + """The scanner worker has not reached a state that is safe to close.""" + + @dataclass(frozen=True) class RollPreviewRequest: device_id: str @@ -35,13 +41,14 @@ class RollBatchScanRequest: output_folder: str filename_pattern: str # Which of the three output tiers to write -- see - # `RollScanningService.write_frame`. Defaults mirror `RollScanSettings`, - # so a request built without naming them writes Tier 1 only, matching - # this integration's behavior before Tier 2/3 existed. + # `RollScanningService.write_frame`. Defaults mirror `RollScanSettings`: + # first-run requests retain Tier 1 and produce Hybrid repair plus the + # Nikon-exact positive while their frame-bound evidence is available. write_unrepaired: bool = RollScanSettings.defaults().write_unrepaired write_repaired: bool = RollScanSettings.defaults().write_repaired write_positive: bool = RollScanSettings.defaults().write_positive - repair_mode: str = RepairMode.EXACT.value + repair_mode: str = RollScanSettings.defaults().repair_mode + positive_mode: str = RollScanSettings.defaults().positive_mode class RollWorker(QObject): @@ -60,17 +67,31 @@ class RollWorker(QObject): error = pyqtSignal(str) status = pyqtSignal(str) - def __init__(self) -> None: + def __init__( + self, + *, + hybrid_runtime: HybridRuntimeConfig | None = None, + ) -> None: super().__init__() - self._service = RollScanningService() + self._service = RollScanningService(hybrid_runtime=hybrid_runtime) self._open_device_id: str | None = None + self._operation_lock = threading.RLock() + self._shutdown_requested = threading.Event() + + def _reject_if_shutting_down(self) -> None: + if self._shutdown_requested.is_set(): + raise RollShutdownBlocked( + "scanner teardown has started; no new operation may begin" + ) # ----- device / roll lifecycle ----- @pyqtSlot() def list_devices(self) -> None: try: - devices = self._service.list_devices() + with self._operation_lock: + self._reject_if_shutting_down() + devices = self._service.list_devices() self.devices_ready.emit(devices) except Exception as e: logger.exception("roll device listing failed") @@ -85,6 +106,7 @@ def _ensure_open(self, device_id: str) -> None: stale reservation on the old device would otherwise sit open with nothing left able to use it. """ + self._reject_if_shutting_down() if self._open_device_id == device_id: return if self._open_device_id is not None: @@ -96,26 +118,38 @@ def _ensure_open(self, device_id: str) -> None: self.opened.emit(device_id) @pyqtSlot() - def close_roll(self) -> None: + def close_roll(self) -> bool: try: - self._service.close() - except Exception: + with self._operation_lock: + self._service.close() + except Exception as error: logger.exception("error closing roll") - finally: + self.status.emit( + "Scanner close is unresolved; the reservation remains open." + ) + self.error.emit(f"Close roll: {error}") + return False + else: self._open_device_id = None self.closed.emit() + return True # ----- preview / approval ----- @pyqtSlot(RollPreviewRequest) def run_preview(self, req: RollPreviewRequest) -> None: try: - self._ensure_open(req.device_id) - self.status.emit("Reading roll transport…") - thumbnails = self._service.preview( - req.slots or None, - on_progress=lambda p: self.progress.emit(p.fraction, p.message), - ) + with self._operation_lock: + self._reject_if_shutting_down() + self._ensure_open(req.device_id) + self.status.emit("Reading roll transport…") + thumbnails = self._service.preview( + req.slots or None, + on_progress=lambda p: self.progress.emit( + p.fraction, + p.message, + ), + ) self.preview_ready.emit(thumbnails) self.status.emit(f"Previewed {len(thumbnails)} slot(s).") except Exception as e: @@ -125,7 +159,9 @@ def run_preview(self, req: RollPreviewRequest) -> None: @pyqtSlot(int, int) def set_spacing_offset(self, slot: int, offset_rows: int) -> None: try: - self._service.set_spacing_offset(slot, offset_rows) + with self._operation_lock: + self._reject_if_shutting_down() + self._service.set_spacing_offset(slot, offset_rows) self.spacing_offset_set.emit(slot, offset_rows) except Exception as e: logger.exception("set_spacing_offset failed") @@ -134,7 +170,9 @@ def set_spacing_offset(self, slot: int, offset_rows: int) -> None: @pyqtSlot(int) def approve(self, slot: int) -> None: try: - self._service.approve(slot) + with self._operation_lock: + self._reject_if_shutting_down() + self._service.approve(slot) self.approved.emit(slot) except Exception as e: logger.exception("approve failed") @@ -142,30 +180,49 @@ def approve(self, slot: int) -> None: # ----- scanning ----- + def prepare_batch(self) -> None: + """Reset cancellation before the controller queues this batch.""" + + self._reject_if_shutting_down() + self._service.prepare_batch() + @pyqtSlot(RollBatchScanRequest) def run_batch_scan(self, req: RollBatchScanRequest) -> None: written: list[RollFrameOutput] = [] try: - self._ensure_open(req.device_id) - self.status.emit("Scanning…") - for frame in self._service.scan_many( - req.slots, - on_progress=lambda p: self.progress.emit(p.fraction, p.message), - ): - output = self._service.write_frame( - frame, - req.output_folder, - req.filename_pattern, - write_unrepaired=req.write_unrepaired, - write_repaired=req.write_repaired, - write_positive=req.write_positive, - repair_mode=req.repair_mode, - ) - written.append(output) - self.frame_written.emit(output) + with self._operation_lock: + self._reject_if_shutting_down() + self._ensure_open(req.device_id) + self.status.emit("Scanning…") + for frame in self._service.scan_many( + req.slots, + on_progress=lambda p: self.progress.emit( + p.fraction, + p.message, + ), + ): + output = self._service.write_frame( + frame, + req.output_folder, + req.filename_pattern, + write_unrepaired=req.write_unrepaired, + write_repaired=req.write_repaired, + write_positive=req.write_positive, + repair_mode=req.repair_mode, + positive_mode=req.positive_mode, + on_repair_progress=lambda fraction: self.progress.emit( + fraction, + "Applying Digital ICE repair…", + ), + ) + written.append(output) + self.frame_written.emit(output) self.finished.emit(written) except Exception as e: - if coolscanpy_roll.is_safe_stop(e): + if coolscanpy_roll.is_safe_stop(e) or isinstance( + e, + RepairCancelled, + ): # safe_stop() was requested deliberately (see `safe_stop` below): the frame # already in flight always finishes -- and is already written, already on # disk, already in `written` -- so this is a clean stop, not a failure. @@ -190,10 +247,38 @@ def safe_stop(self) -> None: """ self._service.safe_stop() - def shutdown(self) -> None: - """Stop any scan and release the roll (called on app teardown).""" + def shutdown(self, *, timeout_seconds: float = 5.0) -> None: + """Stop any scan and release the roll, or block application exit. + + The operation lock prevents teardown from racing the scanner-owning + worker path. A timed-out or failed close leaves the service and + device identity intact so the user can retry instead of abandoning + an ownership-uncertain helper/reservation. + """ + + self._shutdown_requested.set() self.safe_stop() + acquired = self._operation_lock.acquire(timeout=timeout_seconds) + if not acquired: + message = ( + "the current scanner operation is still stopping; " + "application exit is blocked" + ) + self.status.emit(message) + raise RollShutdownBlocked(message) try: self._service.close() - except Exception: + except Exception as error: logger.exception("error closing roll on shutdown") + self.status.emit( + "Scanner close is unresolved; application exit is blocked." + ) + self.error.emit(f"Shutdown: {error}") + raise + else: + was_open = self._open_device_id is not None + self._open_device_id = None + if was_open: + self.closed.emit() + finally: + self._operation_lock.release() diff --git a/negpy/infrastructure/roll/coolscanpy_roll.py b/negpy/infrastructure/roll/coolscanpy_roll.py index a011f0f0..82cb4bb4 100644 --- a/negpy/infrastructure/roll/coolscanpy_roll.py +++ b/negpy/infrastructure/roll/coolscanpy_roll.py @@ -39,6 +39,10 @@ if TYPE_CHECKING: import coolscanpy + from coolscanpy.protocol.ls5000_single_pass.capture_process import ( + ManualFrameApproval as CoolscanManualFrameApproval, + ) + from coolscanpy.types import ProgressCallback as CoolscanProgressCallback def available() -> bool: @@ -72,7 +76,7 @@ def __init__(self, device: "coolscanpy.Device", roll: "coolscanpy.Roll") -> None self._roll = roll def preview( - self, slots: Iterable[int] | None = None, *, on_progress: "coolscanpy.ProgressCallback | None" = None + self, slots: Iterable[int] | None = None, *, on_progress: "CoolscanProgressCallback | None" = None ) -> "list[coolscanpy.Thumbnail]": """One whole-roll transport read. See `coolscanpy.Roll.preview`.""" try: @@ -80,15 +84,29 @@ def preview( except Exception as error: raise _translate(error) from error + def restore_preview_session( + self, + payload: str, + slots: Iterable[int] | None = None, + ) -> "list[coolscanpy.Thumbnail]": + """Restore one content-verified preview without scanner I/O.""" + + try: + return self._roll.restore_preview_session(payload, slots) + except Exception as error: + raise _translate(error) from error + def set_spacing_offset(self, slot: int, offset_rows: int) -> None: try: self._roll.set_spacing_offset(slot, offset_rows) except Exception as error: raise _translate(error) from error - def approve(self, slot: int) -> None: + def approve(self, slot: int) -> "CoolscanManualFrameApproval": + """Approve one reviewed slot and return Coolscanpy's bound receipt.""" + try: - self._roll.approve(slot) + return self._roll.approve(slot) except Exception as error: raise _translate(error) from error @@ -98,9 +116,7 @@ def needs_approval(self, slot: int) -> bool: except Exception as error: raise _translate(error) from error - def scan_many( - self, slots: Iterable[int], *, on_progress: "coolscanpy.ProgressCallback | None" = None - ) -> Iterator["coolscanpy.Frame"]: + def scan_many(self, slots: Iterable[int], *, on_progress: "CoolscanProgressCallback | None" = None) -> Iterator["coolscanpy.Frame"]: """Batch fine-scan `slots` in one transport reservation. A `RuntimeError` raised here whose `__cause__` is a @@ -118,11 +134,10 @@ def safe_stop(self) -> None: self._roll.safe_stop() def close(self) -> None: - """Idempotent. Releases the roll extension, then the device.""" - try: - self._roll.close() - finally: - self._device.close() + """Release the roll, then its device after ownership is confirmed.""" + + self._roll.close() + self._device.close() def __enter__(self) -> "RollHandle": return self @@ -144,10 +159,10 @@ def open_roll(device_id: str | None = None, *, material: "coolscanpy.Material | `device_id` follows `coolscanpy.open()`: omitted (`None`) picks "the one attached unit", an explicit id (from `list_devices()`) disambiguates when more than one is attached. `material` defaults to - `coolscanpy.Material.COLOR_NEGATIVE`, the only material coolscanpy's - roll engine fine-scans end to end today; black-and-white preview works - but its batch scan raises `NotImplementedError` (unchanged, surfaced as - a plain `RuntimeError` here like everything else). + `coolscanpy.Material.COLOR_NEGATIVE`, NegPy's live-accepted workflow. + Coolscanpy's host-native B&W fine-scan route is implemented and covered + without hardware, but still awaits its first live macOS acceptance with + conventional silver B&W film. """ import coolscanpy diff --git a/negpy/infrastructure/roll/fauxice_bridge.py b/negpy/infrastructure/roll/fauxice_bridge.py index b4081b41..1bcc2849 100644 --- a/negpy/infrastructure/roll/fauxice_bridge.py +++ b/negpy/infrastructure/roll/fauxice_bridge.py @@ -1,49 +1,171 @@ -"""Wire digital-fauxice into the roll repair seam. +"""Wire scanner-native portable Digital ICE into the roll repair seam.""" -Import this module and ``roll_repair.available()`` flips True when -``portable-digital-ice`` is installed -- same guarded-import trick -``coolscanpy_roll`` uses. Without the engine, import does nothing. +from __future__ import annotations -The 285 dpi RGBI meter pass (``Frame.meter_rgbi``, 425x281x4 uint16) -rides along as ``prepass_rgbi``; fauxice needs both captures of the -same physical frame and will not run without the prepass. -""" +import hashlib +import io +import threading +from typing import Callable -from __future__ import annotations +import numpy as np +from PIL import Image from negpy.infrastructure.roll import repair as roll_repair -from negpy.infrastructure.roll.repair import RepairMode, RepairResult +from negpy.infrastructure.roll.repair import ( + RepairAcquisition, + RepairMode, + RepairResult, +) + +from negpy.services.repair.fauxice_ir_repair import ( + FauxiceRepairCancelled, + FauxiceRepairConfig, + engine_available, + repair_ir_dust, +) + -try: - from negpy.services.repair.fauxice_ir_repair import ( - FauxiceRepairConfig, - engine_available, - repair_ir_dust, +def _raw_rgb_sha256(rgb: np.ndarray) -> str: + canonical = np.array(rgb, dtype=" bytes: + stream = io.BytesIO() + Image.fromarray(mask.astype(np.uint8) * 255, mode="L").save( + stream, + format="PNG", + optimize=False, + compress_level=9, ) -except ImportError: - pass -else: - if engine_available(): - - class _FauxiceEngine: - def repair(self, rgb, ir, mode: RepairMode, *, prepass_rgbi=None) -> RepairResult: - from negpy.services.repair.fauxice_ir_repair import RepairMode as FauxiceMode, RepairStatus - - fauxice_mode = FauxiceMode.HYBRID if mode is RepairMode.HYBRID else FauxiceMode.EXACT - config = FauxiceRepairConfig(enabled=True, mode=fauxice_mode) - result = repair_ir_dust( - rgb, ir, same_frame_id="roll-frame", config=config, prepass_rgbi=prepass_rgbi - ) - - if result.status is RepairStatus.APPLIED and result.repaired_rgb16 is not None: - from negpy.services.repair.fauxice_ir_repair import _engine_version - - return RepairResult( - rgb=result.repaired_rgb16, - engine="digital-fauxice", - engine_version=_engine_version() or "unknown", - mode=mode, - ) - raise RuntimeError(f"fauxice repair did not produce output: {result.reason}") - - roll_repair.register_engine(_FauxiceEngine()) + return stream.getvalue() + + +class _FauxiceEngine: + def repair( + self, + acquisition: RepairAcquisition, + mode: RepairMode, + *, + hybrid_runtime=None, + progress: Callable[[float], None] | None = None, + cancel: threading.Event | None = None, + ) -> RepairResult: + from negpy.services.repair.fauxice_ir_repair import ( + RepairMode as FauxiceMode, + RepairStatus, + _engine_version, + ) + + fauxice_mode = FauxiceMode.HYBRID if mode is RepairMode.HYBRID else FauxiceMode.EXACT + config = FauxiceRepairConfig(enabled=True, mode=fauxice_mode) + main = acquisition.main_rgbi + try: + result = repair_ir_dust( + main[..., :3], + main[..., 3], + same_frame_id=acquisition.acquisition_id, + config=config, + prepass_rgbi=acquisition.prepass_rgbi, + validity_mask=acquisition.ir_validity, + hybrid_runtime=hybrid_runtime, + progress=progress, + cancel=cancel, + ) + except FauxiceRepairCancelled as error: + raise roll_repair.RepairCancelled(str(error)) from error + + if result.status is not RepairStatus.APPLIED or result.repaired_rgb16 is None: + raise RuntimeError(f"fauxice repair did not produce output: {result.reason}") + if result.mode_resolved is None: + raise RuntimeError("fauxice repair omitted its resolved mode") + resolved_mode = RepairMode(result.mode_resolved.value) + native_repaired = np.asarray(result.repaired_rgb16) + if native_repaired.dtype != np.uint16 or native_repaired.shape != (*main.shape[:2], 3) or not native_repaired.flags.c_contiguous: + raise RuntimeError("fauxice repair returned invalid scanner-native RGB geometry") + native_output_sha256 = _raw_rgb_sha256(native_repaired) + if result.native_output_rgb_sha256 is not None and result.native_output_rgb_sha256 != native_output_sha256: + raise RuntimeError("fauxice native output SHA-256 changed") + storage_rgb = acquisition.storage_rgb(native_repaired) + + mask_fields: dict[str, object] = {} + if resolved_mode is RepairMode.HYBRID: + routed_native_mask = result.hybrid_mask + routed_native_mask_png = result.hybrid_mask_png + if ( + routed_native_mask is None + or routed_native_mask_png is None + or result.hybrid_mask_sha256 is None + or result.hybrid_receipt is None + or result.hybrid_receipt_sha256 is None + ): + raise RuntimeError("hybrid repair omitted its verified disclosure evidence") + routed_native_mask = np.asarray(routed_native_mask) + if ( + routed_native_mask.dtype != np.bool_ + or routed_native_mask.shape != main.shape[:2] + or not routed_native_mask.flags.c_contiguous + ): + raise RuntimeError("hybrid disclosure mask geometry is invalid") + if ( + hashlib.sha256(routed_native_mask_png).hexdigest() + != result.hybrid_mask_sha256 + ): + raise RuntimeError("hybrid native mask SHA-256 changed") + if hashlib.sha256(result.hybrid_receipt).hexdigest() != result.hybrid_receipt_sha256: + raise RuntimeError("hybrid receipt SHA-256 changed") + native_mask = np.ascontiguousarray( + routed_native_mask & acquisition.ir_validity + ) + native_mask_png = _encode_mask_png(native_mask) + storage_mask = acquisition.storage_mask(native_mask) + storage_mask_png = _encode_mask_png(storage_mask) + mask_fields = { + "native_synthesis_mask_png": native_mask_png, + "native_synthesis_mask_sha256": hashlib.sha256( + native_mask_png + ).hexdigest(), + "native_synthesis_mask_shape": tuple(native_mask.shape), + "routed_native_synthesis_mask_png": routed_native_mask_png, + "routed_native_synthesis_mask_sha256": result.hybrid_mask_sha256, + "routed_native_synthesis_mask_shape": tuple( + routed_native_mask.shape + ), + "storage_synthesis_mask_png": storage_mask_png, + "storage_synthesis_mask_sha256": hashlib.sha256(storage_mask_png).hexdigest(), + "storage_synthesis_mask_shape": tuple(storage_mask.shape), + "synthesis_mask_transform": acquisition.storage_transform, + "synthesis_fraction": float(np.count_nonzero(native_mask)) + / float(native_mask.size), + "routing_counts": result.hybrid_routing_counts, + "hybrid_receipt": result.hybrid_receipt, + "hybrid_receipt_sha256": result.hybrid_receipt_sha256, + "hybrid_provenance_class": result.hybrid_provenance_class, + "hybrid_receipt_output_rgb_sha256": (result.hybrid_receipt_output_rgb_sha256), + } + + return RepairResult( + rgb=storage_rgb, + engine="digital-fauxice", + engine_version=result.engine_version or _engine_version() or "unknown", + mode_requested=mode, + mode_resolved=resolved_mode, + reason=result.reason, + acquisition_id=acquisition.acquisition_id, + slot=acquisition.slot, + reservation_id=acquisition.reservation_id, + evidence_sha256=acquisition.evidence_sha256, + backend_requested=result.backend_requested, + backend_used=result.backend_used, + backend_selection_reason=result.backend_selection_reason, + native_output_rgb_sha256=native_output_sha256, + storage_output_rgb_sha256=_raw_rgb_sha256(storage_rgb), + **mask_fields, + ) + + +if engine_available(): + roll_repair.register_engine(_FauxiceEngine()) + + +__all__ = ["_FauxiceEngine"] diff --git a/negpy/infrastructure/roll/repair.py b/negpy/infrastructure/roll/repair.py index 9c203b94..af66a691 100644 --- a/negpy/infrastructure/roll/repair.py +++ b/negpy/infrastructure/roll/repair.py @@ -6,23 +6,42 @@ rendered from the repaired frame. This module is the plug point for the middle tier -- it does not repair anything itself. -No repair engine ships in this package. `available()` returns False and -`repair()` raises until something calls `register_engine()` with a real -implementation. That keeps Tier 2 honest: a frame this module cannot -actually repair is reported as unavailable rather than written to disk as -"repaired" when it is really just a copy of Tier 1. See +This seam has no intrinsic implementation: `available()` stays false and +`repair()` raises until a bridge calls `register_engine()`. NegPy's +`fauxice_bridge` registers the installed portable Digital ICE runtime during +roll-service import. The explicit registration boundary keeps Tier 2 honest: +a build that cannot load the engine reports repair as unavailable rather than +writing a copy of Tier 1 under a repaired label. See `docs/COOLSCANPY_ROLL_SCANNING.md` for the intended capture -> repair -> invert ordering and which project is expected to fill this slot. """ from __future__ import annotations +import hashlib +import re +import threading from dataclasses import dataclass from enum import StrEnum -from typing import Protocol +from typing import TYPE_CHECKING, Callable, Protocol import numpy as np +if TYPE_CHECKING: + from negpy.services.repair.fauxice_hybrid_runner import HybridRuntimeConfig + + +DIGITAL_ICE_STORAGE_TRANSFORM = "rot90-k1-scanner-native-to-upright-v1" + + +class RepairCancelled(RuntimeError): + """A caller-requested repair stop; never a degradable repair failure.""" + + +def _raw_sha256(array: np.ndarray, *, dtype: np.dtype) -> str: + canonical = np.array(array, dtype=dtype, order="C", copy=True) + return hashlib.sha256(memoryview(canonical).cast("B")).hexdigest() + class RepairMode(StrEnum): """A Tier-2 repair variant. @@ -30,15 +49,124 @@ class RepairMode(StrEnum): EXACT heals only the pixels the infrared channel confidently flags as a defect. HYBRID additionally routes severe zero-signal regions (where the infrared channel carries no usable signal at all, e.g. a dense scratch) - to an inpainting model. Both are deterministic -- the same frame and mode - repair to the same result every time -- which is what makes Tier 2 worth - caching: unlike Tier 3, it never needs to be redone once it has run. + to a separately pinned inpainting runtime. Receipts bind the backend, + resolved mode, output hashes, and hybrid disclosure evidence rather than + making a blanket reproducibility claim across different runtimes. """ EXACT = "exact" HYBRID = "hybrid" +@dataclass(frozen=True) +class RepairAcquisition: + """One hash-bound scanner-native prepass/main pair ready for repair.""" + + acquisition_id: str + slot: int + reservation_id: str + capture_attempt_id: str + storage_transform: str + evidence_sha256: str + main_rgbi_sha256: str + prepass_rgbi_sha256: str + ir_validity_sha256: str + main_rgbi: np.ndarray + prepass_rgbi: np.ndarray + ir_validity: np.ndarray + + def __post_init__(self) -> None: + if re.fullmatch(r"dice-[0-9a-f]{64}", self.acquisition_id) is None: + raise ValueError("repair acquisition identity is malformed") + if type(self.slot) is not int or not 1 <= self.slot <= 40: + raise ValueError("repair acquisition slot must be in 1..40") + for label, value in ( + ("reservation identity", self.reservation_id), + ("capture-attempt identity", self.capture_attempt_id), + ): + if type(value) is not str or not value.strip(): + raise ValueError(f"repair {label} must be non-empty") + if self.storage_transform != DIGITAL_ICE_STORAGE_TRANSFORM: + raise ValueError("repair acquisition storage transform is unsupported") + for label, digest in ( + ("evidence", self.evidence_sha256), + ("main RGBI", self.main_rgbi_sha256), + ("prepass RGBI", self.prepass_rgbi_sha256), + ("IR validity", self.ir_validity_sha256), + ): + if re.fullmatch(r"[0-9a-f]{64}", digest) is None: + raise ValueError(f"repair acquisition {label} SHA-256 is malformed") + + main = np.array(self.main_rgbi, dtype=" "RepairAcquisition": + """Test/helper constructor; production passes producer-bound hashes.""" + + return cls( + acquisition_id=acquisition_id, + slot=slot, + reservation_id=reservation_id, + capture_attempt_id=capture_attempt_id, + storage_transform=storage_transform, + evidence_sha256=evidence_sha256, + main_rgbi_sha256=_raw_sha256(main_rgbi, dtype=np.dtype(" np.ndarray: + native = np.asarray(scanner_native_rgb) + if native.dtype != np.uint16 or native.shape != (*self.main_rgbi.shape[:2], 3): + raise ValueError("repair output must match scanner-native main RGB geometry") + return np.ascontiguousarray(np.rot90(native, k=1, axes=(0, 1))) + + def storage_mask(self, scanner_native_mask: np.ndarray) -> np.ndarray: + native = np.asarray(scanner_native_mask) + if native.dtype != np.bool_ or native.shape != self.main_rgbi.shape[:2]: + raise ValueError("repair mask must match scanner-native main geometry") + return np.ascontiguousarray(np.rot90(native, k=1, axes=(0, 1))) + + @dataclass(frozen=True) class RepairResult: """One frame's Tier-2 RGB, plus what the receipt needs to audit or @@ -50,14 +178,51 @@ class RepairResult: rgb: np.ndarray engine: str engine_version: str - mode: RepairMode + mode_requested: RepairMode + mode_resolved: RepairMode + reason: str + acquisition_id: str | None = None + slot: int | None = None + reservation_id: str | None = None + evidence_sha256: str | None = None + backend_requested: str | None = None + backend_used: str | None = None + backend_selection_reason: str | None = None + native_output_rgb_sha256: str | None = None + storage_output_rgb_sha256: str | None = None + native_synthesis_mask_png: bytes | None = None + native_synthesis_mask_sha256: str | None = None + native_synthesis_mask_shape: tuple[int, int] | None = None + routed_native_synthesis_mask_png: bytes | None = None + routed_native_synthesis_mask_sha256: str | None = None + routed_native_synthesis_mask_shape: tuple[int, int] | None = None + storage_synthesis_mask_png: bytes | None = None + storage_synthesis_mask_sha256: str | None = None + storage_synthesis_mask_shape: tuple[int, int] | None = None + synthesis_mask_transform: str | None = None + synthesis_fraction: float | None = None + routing_counts: dict[str, int] | None = None + hybrid_receipt: bytes | None = None + hybrid_receipt_sha256: str | None = None + hybrid_provenance_class: str | None = None + hybrid_receipt_output_rgb_sha256: str | None = None + + @property + def degraded(self) -> bool: + return self.mode_requested is not self.mode_resolved class RepairEngine(Protocol): """What a repair implementation must provide to `register_engine()`.""" def repair( - self, rgb: np.ndarray, ir: np.ndarray, mode: RepairMode, *, prepass_rgbi: np.ndarray | None = None + self, + acquisition: RepairAcquisition, + mode: RepairMode, + *, + hybrid_runtime: "HybridRuntimeConfig | None" = None, + progress: Callable[[float], None] | None = None, + cancel: threading.Event | None = None, ) -> RepairResult: ... @@ -92,7 +257,12 @@ def unregister_engine() -> None: def repair( - rgb: np.ndarray, ir: np.ndarray, mode: RepairMode, *, prepass_rgbi: np.ndarray | None = None + acquisition: RepairAcquisition, + mode: RepairMode, + *, + hybrid_runtime: "HybridRuntimeConfig | None" = None, + progress: Callable[[float], None] | None = None, + cancel: threading.Event | None = None, ) -> RepairResult: """Repair one frame's RGB using its infrared plane. @@ -102,4 +272,10 @@ def repair( """ if _engine is None: raise RuntimeError("no dust-repair engine is registered; Tier 2 and Tier 3 are unavailable") - return _engine.repair(rgb, ir, mode, prepass_rgbi=prepass_rgbi) + return _engine.repair( + acquisition, + mode, + hybrid_runtime=hybrid_runtime, + progress=progress, + cancel=cancel, + ) diff --git a/negpy/infrastructure/roll/settings.py b/negpy/infrastructure/roll/settings.py index 9b88ad3f..1a148d76 100644 --- a/negpy/infrastructure/roll/settings.py +++ b/negpy/infrastructure/roll/settings.py @@ -5,6 +5,7 @@ from dataclasses import dataclass from negpy.infrastructure.roll.repair import RepairMode +from negpy.services.roll.exact_color import PositiveColorMode @dataclass(frozen=True) @@ -23,20 +24,25 @@ class RollScanSettings: `negpy.services.roll.service.write_frame`); any combination is valid, and they are independent, not a single three-way choice. `write_unrepaired` defaults on because it is the archival master and the only tier the - scanner itself can reproduce -- the other two default off since they are - derived, regenerable, and (for `write_positive`) subject to a rendering - pipeline that is still being tuned. `repair_mode` governs Tier 2 (and, - through it, Tier 3) whenever a repair engine is registered; see - `negpy.infrastructure.roll.repair`. + scanner itself can reproduce. For a new user, repaired and positive also + default on in Hybrid + Nikon-exact mode so the complete parity workflow + runs while its frame-bound prepass, infrared-validity data, acquisition + provenance, and native color-builder evidence are still available. Saved + user choices override these first-run defaults. `repair_mode` governs Tier + 2 (and, through it, Tier 3) whenever a repair engine is registered; see + `negpy.infrastructure.roll.repair`. `positive_mode` chooses the Tier-3 + color path. The roll-scanning parity workflow defaults to fail-closed Nikon + exact color; NegPy's approximate renderer remains an explicit choice. """ last_device_id: str = "" output_folder: str = "" filename_pattern: str = '{{ date }}_{{ "%03d" % seq }}' write_unrepaired: bool = True - write_repaired: bool = False - write_positive: bool = False - repair_mode: str = RepairMode.EXACT.value + write_repaired: bool = True + write_positive: bool = True + repair_mode: str = RepairMode.HYBRID.value + positive_mode: str = PositiveColorMode.NIKON_EXACT.value @classmethod def defaults(cls) -> "RollScanSettings": diff --git a/negpy/kernel/system/paths.py b/negpy/kernel/system/paths.py index 3e68923d..31fdcebf 100644 --- a/negpy/kernel/system/paths.py +++ b/negpy/kernel/system/paths.py @@ -46,6 +46,14 @@ def get_default_user_dir() -> str: if env_path: return os.path.abspath(env_path) + # A frozen macOS app must create a foreground Qt window before its first + # Documents access so macOS can present the Files & Folders prompt. Keep + # resolution pure here; the desktop startup handoff performs the first + # deliberate write/access probe once that window is visible. + if sys.platform == "darwin": + home = Path(os.path.expanduser("~")) + return str((home / "Documents" / "NegPy").absolute()) + docs_dir: Optional[Path] = None if sys.platform == "win32": @@ -94,9 +102,6 @@ def get_default_user_dir() -> str: except Exception: pass - elif sys.platform == "darwin": - docs_dir = Path.home() / "Documents" - home = Path(os.path.expanduser("~")) if not docs_dir: docs_dir = home / "Documents" diff --git a/negpy/services/assets/crosstalk.py b/negpy/services/assets/crosstalk.py index 0fc091b5..a2f88647 100644 --- a/negpy/services/assets/crosstalk.py +++ b/negpy/services/assets/crosstalk.py @@ -1,5 +1,6 @@ import os import re +import stat import tomllib from typing import List, Optional @@ -7,6 +8,7 @@ from negpy.kernel.system.paths import get_resource_path DEFAULT_NAME = "Default" +_MAX_PROFILE_BYTES = 64 * 1024 # ponytail: 2-line helpers duplicated from contact_sheet_templates; extract to a @@ -69,9 +71,31 @@ def _scan() -> dict: @staticmethod def _parse_file(path: str) -> Optional[tuple]: """Parses a .toml file to (name, flat 9-float list), or None if invalid.""" + descriptor: Optional[int] = None try: - with open(path, "rb") as f: - data = tomllib.load(f) + linked = os.lstat(path) + if stat.S_ISLNK(linked.st_mode) or not stat.S_ISREG(linked.st_mode): + return None + if linked.st_size < 0 or linked.st_size > _MAX_PROFILE_BYTES: + return None + + flags = os.O_RDONLY | getattr(os, "O_CLOEXEC", 0) | getattr(os, "O_NOFOLLOW", 0) | getattr(os, "O_NONBLOCK", 0) + descriptor = os.open(path, flags) + with os.fdopen(descriptor, "rb") as f: + descriptor = None + before = os.fstat(f.fileno()) + if not stat.S_ISREG(before.st_mode) or not os.path.samestat(linked, before) or before.st_size != linked.st_size: + return None + payload = f.read(_MAX_PROFILE_BYTES + 1) + after = os.fstat(f.fileno()) + if ( + len(payload) != before.st_size + or len(payload) > _MAX_PROFILE_BYTES + or (before.st_dev, before.st_ino, before.st_size, before.st_mtime_ns) + != (after.st_dev, after.st_ino, after.st_size, after.st_mtime_ns) + ): + return None + data = tomllib.loads(payload.decode("utf-8")) rows = data.get("matrix") if not isinstance(rows, list) or len(rows) != 3: return None @@ -88,6 +112,9 @@ def _parse_file(path: str) -> Optional[tuple]: return name, flat except Exception: return None + finally: + if descriptor is not None: + os.close(descriptor) @staticmethod def list_profiles() -> List[str]: diff --git a/negpy/services/export/contact_sheet.py b/negpy/services/export/contact_sheet.py index c8d4d058..3f495b2b 100644 --- a/negpy/services/export/contact_sheet.py +++ b/negpy/services/export/contact_sheet.py @@ -179,9 +179,7 @@ def _draw_captions( draw.text((x, y), display, font=font, fill=fg, anchor="lm") @staticmethod - def _truncate( - draw: ImageDraw.ImageDraw, text: str, font: ImageFont.FreeTypeFont | ImageFont.ImageFont, max_w: int - ) -> str: + def _truncate(draw: ImageDraw.ImageDraw, text: str, font: ImageFont.FreeTypeFont | ImageFont.ImageFont, max_w: int) -> str: if draw.textlength(text, font=font) <= max_w: return text ellipsis = "..." diff --git a/negpy/services/repair/fauxice_hybrid_runner.py b/negpy/services/repair/fauxice_hybrid_runner.py index abb9d4a5..d6d0812e 100644 --- a/negpy/services/repair/fauxice_hybrid_runner.py +++ b/negpy/services/repair/fauxice_hybrid_runner.py @@ -20,20 +20,91 @@ from __future__ import annotations import hashlib +import io import json +import os +import re +import signal +import stat +import subprocess +import tempfile +import threading +import time + import numpy as np import numpy.typing as npt from dataclasses import dataclass from pathlib import Path from typing import Any, Callable, Sequence -import subprocess +from PIL import Image class HybridRunError(RuntimeError): """The fauxce-hybrid subprocess did not produce a usable result.""" +class HybridRunCancelled(HybridRunError): + """The caller cancelled the external hybrid process and its children.""" + + +def _stable_regular_sha256(path: Path, *, label: str) -> str: + before = os.lstat(path) + if stat.S_ISLNK(before.st_mode) or not stat.S_ISREG(before.st_mode): + raise ValueError(f"{label} must be a regular non-symlink file") + flags = os.O_RDONLY + if hasattr(os, "O_CLOEXEC"): + flags |= os.O_CLOEXEC + if hasattr(os, "O_NOFOLLOW"): + flags |= os.O_NOFOLLOW + if hasattr(os, "O_NONBLOCK"): + flags |= os.O_NONBLOCK + descriptor = os.open(path, flags) + try: + opened = os.fstat(descriptor) + identity = ( + opened.st_dev, + opened.st_ino, + opened.st_size, + opened.st_mtime_ns, + opened.st_ctime_ns, + ) + if not stat.S_ISREG(opened.st_mode) or identity != ( + before.st_dev, + before.st_ino, + before.st_size, + before.st_mtime_ns, + before.st_ctime_ns, + ): + raise ValueError(f"{label} changed while it was opened") + digest = hashlib.sha256() + remaining = opened.st_size + while remaining: + block = os.read(descriptor, min(1024 * 1024, remaining)) + if not block: + raise ValueError(f"{label} changed while it was hashed") + digest.update(block) + remaining -= len(block) + # Bound the read by the size captured at open. One extra byte detects + # growth without following an attacker-controlled, never-ending file. + if os.read(descriptor, 1): + raise ValueError(f"{label} changed while it was hashed") + after_read = os.fstat(descriptor) + finally: + os.close(descriptor) + after_path = os.lstat(path) + for metadata in (after_read, after_path): + if ( + metadata.st_dev, + metadata.st_ino, + metadata.st_size, + metadata.st_mtime_ns, + metadata.st_ctime_ns, + ) != identity: + raise ValueError(f"{label} changed while it was hashed") + return digest.hexdigest() + + @dataclass(frozen=True) class HybridRuntimeConfig: """Where the pinned IOPaint interpreter and hash-verified weights live. @@ -45,6 +116,10 @@ class HybridRuntimeConfig: filename; this config just carries those caller-verified values through. """ + hybrid_python: Path + executable: Path + core_source_manifest_sha256: str + hybrid_source_manifest_sha256: str iopaint_python: Path iopaint_executable: Path iopaint_source_manifest_sha256: str @@ -54,22 +129,96 @@ class HybridRuntimeConfig: inpaint_device: str = "cpu" inpaint_threads: int = 1 inpaint_seed: int = 0 - executable: str = "fauxce-hybrid" + + def __post_init__(self) -> None: + for field_name in ( + "hybrid_python", + "executable", + "iopaint_python", + "iopaint_executable", + "model_dir", + "model_weights", + ): + value = Path(getattr(self, field_name)) + if not value.is_absolute(): + raise ValueError(f"{field_name} must be an absolute explicit path") + object.__setattr__(self, field_name, value) + for field_name in ( + "core_source_manifest_sha256", + "hybrid_source_manifest_sha256", + "iopaint_source_manifest_sha256", + "model_weights_sha256", + ): + if re.fullmatch(r"[0-9a-f]{64}", getattr(self, field_name)) is None: + raise ValueError(f"{field_name} must be a lowercase SHA-256 digest") + if self.inpaint_device not in {"cpu", "cuda", "mps"}: + raise ValueError("inpaint_device must be cpu, cuda, or mps") + if type(self.inpaint_threads) is not int or self.inpaint_threads < 1: + raise ValueError("inpaint_threads must be a positive integer") + if type(self.inpaint_seed) is not int or self.inpaint_seed < 0: + raise ValueError("inpaint_seed must be a non-negative integer") + + def validate_files(self) -> None: + """Fail closed unless every configured runtime artifact is present.""" + + executables = ( + ("hybrid_python", self.hybrid_python), + ("executable", self.executable), + ("iopaint_python", self.iopaint_python), + ("iopaint_executable", self.iopaint_executable), + ) + for label, path in executables: + try: + resolved = path.resolve(strict=True) + mode = resolved.stat().st_mode + except OSError as error: + raise ValueError(f"{label} is unavailable: {error}") from error + if not stat.S_ISREG(mode) or not os.access(path, os.X_OK): + raise ValueError(f"{label} must resolve to an executable regular file") + try: + model_dir = self.model_dir.resolve(strict=True) + except OSError as error: + raise ValueError(f"model_dir is unavailable: {error}") from error + if not model_dir.is_dir(): + raise ValueError("model_dir must resolve to a directory") + try: + weights = self.model_weights.resolve(strict=True) + weights_mode = weights.stat().st_mode + except OSError as error: + raise ValueError(f"model_weights is unavailable: {error}") from error + if not stat.S_ISREG(weights_mode): + raise ValueError("model_weights must resolve to a regular file") + try: + measured_weights_sha256 = _stable_regular_sha256( + self.model_weights, + label="model_weights", + ) + except OSError as error: + raise ValueError(f"model_weights is unavailable: {error}") from error + if measured_weights_sha256 != self.model_weights_sha256: + raise ValueError("model_weights SHA-256 does not match the pinned runtime") @dataclass(frozen=True) class HybridRunResult: """What one fauxce-hybrid subprocess call produced, read back into memory. - The mask is carried as bytes rather than a path: the CLI's ``--out`` - directory must not already exist and is treated as scratch space by this - module, so nothing durable survives the call except what the caller - persists from this result. + The mask is carried as bytes rather than a path. The CLI files remain in + the caller-owned per-run scratch child until the caller removes it; only + values copied into this result are independent of that directory. """ hybrid_rgb16: npt.NDArray[np.uint16] synth_mask_png: bytes synth_mask_sha256: str + synth_mask: npt.NDArray[np.bool_] + receipt: bytes + receipt_sha256: str + acquisition_manifest_sha256: str + main_rgbi_sha256: str + prepass_rgbi_sha256: str + output_rgb16_sha256: str + provenance_class: str synthesis_fraction: float | None engine_version: str | None backend_requested: str | None @@ -78,6 +227,415 @@ class HybridRunResult: routing_counts: dict[str, int] | None +ReceiptVerifier = Callable[[Path, HybridRuntimeConfig], Any] +VerificationRunner = Callable[..., "subprocess.CompletedProcess[str]"] +_MAX_SUBPROCESS_LOG_BYTES = 16 * 1024 * 1024 + + +@dataclass(frozen=True) +class _ReceiptVerification: + receipt_sha256: str + model_weights_rehashed: bool + + +_EXTERNAL_RECEIPT_VERIFIER = r""" +import json +import sys +from pathlib import Path + +from fauxce_hybrid.receipts import verify_receipt + +receipt_path = Path(sys.argv[1]) +weights_path = Path(sys.argv[2]) +weights_sha256 = sys.argv[3] + +def resolve_weights(attestation): + if getattr(attestation, "sha256", None) != weights_sha256: + raise ValueError("receipt model weights do not match the pinned runtime") + return weights_path + +verified = verify_receipt( + receipt_path, + model_weights_resolver=resolve_weights, + require_model_weights=True, +) +document = { + "model_weights_rehashed": bool(verified.model_weights_rehashed), + "receipt_sha256": verified.receipt_sha256, + "schema": "negpy.external-fauxce-receipt-verification-v1", +} +sys.stdout.write(json.dumps(document, sort_keys=True, separators=(",", ":"))) +""".strip() + + +def _process_group_exists(process_group: int) -> bool: + try: + os.killpg(process_group, 0) + except ProcessLookupError: + return False + except (AttributeError, PermissionError): + return True + return True + + +def _terminate_process_group(process: subprocess.Popen) -> None: + process_group = process.pid + try: + os.killpg(process_group, signal.SIGTERM) + except (AttributeError, OSError): + try: + process.terminate() + except OSError: + pass + deadline = time.monotonic() + 3.0 + while _process_group_exists(process_group) and time.monotonic() < deadline: + time.sleep(0.05) + # The group leader may have exited on TERM while an IOPaint/model child + # ignored it. Kill the group after grace regardless of leader state. + try: + os.killpg(process_group, signal.SIGKILL) + except (AttributeError, OSError): + try: + process.kill() + except OSError: + pass + try: + process.wait(timeout=3.0) + except (OSError, subprocess.TimeoutExpired): + pass + + +def _sanitized_subprocess_environment() -> dict[str, str]: + environment = dict(os.environ) + for name in ( + "PYTHONHOME", + "PYTHONINSPECT", + "PYTHONPATH", + "PYTHONSTARTUP", + "PYTHONUSERBASE", + ): + environment.pop(name, None) + for name in tuple(environment): + if name.startswith(("DYLD_", "LD_")): + environment.pop(name, None) + environment.update( + { + "LANG": "C", + "LC_ALL": "C", + "MKL_NUM_THREADS": "1", + "NUMEXPR_NUM_THREADS": "1", + "OMP_NUM_THREADS": "1", + "OPENBLAS_NUM_THREADS": "1", + "PYTHONHASHSEED": "0", + "PYTHONNOUSERSITE": "1", + } + ) + return environment + + +def _raise_if_cancelled( + cancel: threading.Event | None, + *, + phase: str, +) -> None: + if cancel is not None and cancel.is_set(): + raise HybridRunCancelled(f"fauxce-hybrid cancelled {phase}") + + +def _run_command( + argv: Sequence[str], + *, + timeout_seconds: float, + cancel: threading.Event | None, + runner: VerificationRunner | None, + label: str, +) -> subprocess.CompletedProcess[str]: + if cancel is not None and cancel.is_set(): + raise HybridRunCancelled(f"{label} cancelled before launch") + if runner is not None: + try: + completed = runner( + list(argv), + capture_output=True, + env=_sanitized_subprocess_environment(), + text=True, + timeout=timeout_seconds, + ) + except (OSError, subprocess.SubprocessError) as error: + raise HybridRunError(f"could not launch {label}: {error}") from error + if cancel is not None and cancel.is_set(): + raise HybridRunCancelled(f"{label} cancelled") + return completed + + process: subprocess.Popen | None = None + completed_normally = False + try: + with ( + tempfile.TemporaryFile(mode="w+t", encoding="utf-8") as stdout_file, + tempfile.TemporaryFile( + mode="w+t", + encoding="utf-8", + ) as stderr_file, + ): + process = subprocess.Popen( + list(argv), + stdin=subprocess.DEVNULL, + stdout=stdout_file, + stderr=stderr_file, + env=_sanitized_subprocess_environment(), + text=True, + start_new_session=True, + ) + deadline = time.monotonic() + timeout_seconds + while process.poll() is None: + if cancel is not None and cancel.is_set(): + _terminate_process_group(process) + raise HybridRunCancelled(f"{label} cancelled") + if time.monotonic() >= deadline: + _terminate_process_group(process) + raise HybridRunError(f"{label} exceeded its {timeout_seconds:g}s timeout") + if ( + os.fstat(stdout_file.fileno()).st_size > _MAX_SUBPROCESS_LOG_BYTES + or os.fstat(stderr_file.fileno()).st_size > _MAX_SUBPROCESS_LOG_BYTES + ): + _terminate_process_group(process) + raise HybridRunError(f"{label} output exceeded its safe size limit") + time.sleep(0.05) + if ( + os.fstat(stdout_file.fileno()).st_size > _MAX_SUBPROCESS_LOG_BYTES + or os.fstat(stderr_file.fileno()).st_size > _MAX_SUBPROCESS_LOG_BYTES + ): + raise HybridRunError(f"{label} output exceeded its safe size limit") + stdout_file.seek(0) + stderr_file.seek(0) + completed = subprocess.CompletedProcess( + list(argv), + process.returncode, + stdout_file.read(), + stderr_file.read(), + ) + if _process_group_exists(process.pid): + _terminate_process_group(process) + raise HybridRunError(f"{label} left child processes running after exit") + completed_normally = True + return completed + except (HybridRunError, HybridRunCancelled): + raise + except (OSError, subprocess.SubprocessError) as error: + raise HybridRunError(f"could not launch {label}: {error}") from error + finally: + if process is not None and not completed_normally: + _terminate_process_group(process) + + +def _raw_sha256(array: np.ndarray, *, dtype: np.dtype) -> str: + canonical = np.array(array, dtype=dtype, order="C", copy=True) + return hashlib.sha256(memoryview(canonical).cast("B")).hexdigest() + + +def _canonical_rgbi(array: np.ndarray, *, label: str) -> np.ndarray: + value = np.asarray(array) + if value.ndim != 3 or value.shape[2] != 4 or value.dtype != np.uint16: + raise HybridRunError(f"{label} must be an HxWx4 uint16 array") + canonical = np.array(value, dtype=" bytes: + try: + return json.dumps( + document, + sort_keys=True, + separators=(",", ":"), + ensure_ascii=False, + allow_nan=False, + ).encode("utf-8") + except (TypeError, ValueError, RecursionError) as error: + raise HybridRunError(f"acquisition manifest is not canonical JSON: {error}") from error + + +def _canonical_receipt_bytes(document: object) -> bytes: + try: + return ( + json.dumps( + document, + sort_keys=True, + separators=(",", ":"), + ensure_ascii=True, + allow_nan=False, + ).encode("utf-8") + + b"\n" + ) + except (TypeError, ValueError, RecursionError) as error: + raise HybridRunError(f"hybrid receipt is not canonical JSON: {error}") from error + + +def _stable_regular_bytes(path: Path, *, maximum_bytes: int, label: str) -> bytes: + descriptor: int | None = None + try: + linked = os.lstat(path) + if stat.S_ISLNK(linked.st_mode) or not stat.S_ISREG(linked.st_mode): + raise HybridRunError(f"{label} must be a regular non-symlink file") + if linked.st_size < 0 or linked.st_size > maximum_bytes: + raise HybridRunError(f"{label} exceeds its safe size limit") + flags = os.O_RDONLY | getattr(os, "O_CLOEXEC", 0) | getattr(os, "O_NOFOLLOW", 0) | getattr(os, "O_NONBLOCK", 0) + descriptor = os.open(path, flags) + with os.fdopen(descriptor, "rb", closefd=True) as handle: + descriptor = None + before = os.fstat(handle.fileno()) + if not stat.S_ISREG(before.st_mode) or ( + before.st_dev, + before.st_ino, + before.st_size, + before.st_mtime_ns, + before.st_ctime_ns, + ) != ( + linked.st_dev, + linked.st_ino, + linked.st_size, + linked.st_mtime_ns, + linked.st_ctime_ns, + ): + raise HybridRunError(f"{label} changed while being opened") + payload = handle.read(maximum_bytes + 1) + after = os.fstat(handle.fileno()) + if ( + len(payload) != before.st_size + or len(payload) > maximum_bytes + or ( + before.st_dev, + before.st_ino, + before.st_size, + before.st_mtime_ns, + before.st_ctime_ns, + ) + != ( + after.st_dev, + after.st_ino, + after.st_size, + after.st_mtime_ns, + after.st_ctime_ns, + ) + ): + raise HybridRunError(f"{label} changed while being read") + return payload + except HybridRunError: + raise + except OSError as error: + raise HybridRunError(f"could not securely read {label}: {error}") from error + finally: + if descriptor is not None: + os.close(descriptor) + + +def _decode_mask_png(payload: bytes, *, expected_shape: tuple[int, int]) -> np.ndarray: + try: + with Image.open(io.BytesIO(payload)) as image: + if image.format != "PNG": + raise HybridRunError("synthesis disclosure mask is not a PNG") + if image.size != (expected_shape[1], expected_shape[0]): + raise HybridRunError("synthesis disclosure mask geometry is invalid") + decoded = np.asarray(image.convert("L")) + except HybridRunError: + raise + except Exception as error: + raise HybridRunError(f"synthesis disclosure mask PNG is invalid: {error}") from error + if decoded.shape != expected_shape or decoded.dtype != np.uint8: + raise HybridRunError("synthesis disclosure mask geometry is invalid") + unique = np.unique(decoded) + if not np.all(np.isin(unique, np.array([0, 255], dtype=np.uint8))): + raise HybridRunError("synthesis disclosure mask must be binary") + mask = np.ascontiguousarray(decoded != 0) + mask.setflags(write=False) + return mask + + +def _default_receipt_verifier( + path: Path, + runtime: HybridRuntimeConfig, + *, + runner: VerificationRunner | None = None, + cancel: threading.Event | None = None, +) -> _ReceiptVerification: + """Verify with the explicitly configured Python-3.12 companion. + + NegPy itself supports Python 3.13 while fauxce-hybrid intentionally uses + a separate Python 3.12 environment. Importing the verifier in-process + would therefore make a correctly installed companion appear unavailable. + The external verifier returns only a small attestation; NegPy then reads + and checks every result artifact itself before publishing it. + """ + + completed = _run_command( + [ + str(runtime.hybrid_python), + "-I", + "-c", + _EXTERNAL_RECEIPT_VERIFIER, + str(path), + str(runtime.model_weights), + runtime.model_weights_sha256, + ], + timeout_seconds=300.0, + cancel=cancel, + runner=runner, + label="external hybrid receipt verifier", + ) + if completed.returncode != 0: + detail = (completed.stderr or completed.stdout or "").strip() + raise HybridRunError("external hybrid receipt verification failed: " + detail[-2000:]) + try: + document = json.loads(completed.stdout) + except (TypeError, json.JSONDecodeError) as error: + raise HybridRunError("external hybrid receipt verifier returned invalid JSON") from error + if ( + not isinstance(document, dict) + or document.get("schema") != "negpy.external-fauxce-receipt-verification-v1" + or re.fullmatch(r"[0-9a-f]{64}", document.get("receipt_sha256", "")) is None + or type(document.get("model_weights_rehashed")) is not bool + ): + raise HybridRunError("external hybrid receipt verifier returned an invalid attestation") + return _ReceiptVerification( + receipt_sha256=document["receipt_sha256"], + model_weights_rehashed=document["model_weights_rehashed"], + ) + + +def _decode_npy_rgb16( + payload: bytes, + *, + expected_shape: tuple[int, int, int], +) -> np.ndarray: + stream = io.BytesIO(payload) + try: + version = np.lib.format.read_magic(stream) + if version == (1, 0): + shape, fortran_order, dtype = np.lib.format.read_array_header_1_0(stream) + elif version == (2, 0): + shape, fortran_order, dtype = np.lib.format.read_array_header_2_0(stream) + else: + raise HybridRunError(f"hybrid output NPY version {version!r} is unsupported") + if tuple(shape) != expected_shape or dtype != np.dtype(" HybridRunResult: """Run one frame through the fauxce-hybrid CLI and read its outputs back. - ``scratch_dir`` must exist and be empty; this function creates the CLI's - ``--out`` subdirectory itself (the CLI refuses a pre-existing one). The - caller owns ``scratch_dir``'s lifetime; nothing here deletes it, so a - caller that wants ephemeral scratch space should wrap the call in its own + ``scratch_dir`` must resolve to a directory, but it may already contain + other entries. This function creates one exclusive + ``negpy-hybrid-run-*`` child and then creates the CLI's ``--out`` path + inside it (the CLI refuses a pre-existing output path). The caller owns + both the parent and per-run child lifetime; nothing here deletes them, so + an ephemeral caller should wrap the parent in ``tempfile.TemporaryDirectory()``. """ - prepass_path = scratch_dir / "prepass.rgbi16.npy" - main_path = scratch_dir / "main.rgbi16.npy" - out_dir = scratch_dir / "out" - np.save(prepass_path, prepass_rgbi, allow_pickle=False) - np.save(main_path, main_rgbi, allow_pickle=False) + main = _canonical_rgbi(main_rgbi, label="main_rgbi") + prepass = _canonical_rgbi(prepass_rgbi, label="prepass_rgbi") + try: + runtime.validate_files() + except ValueError as error: + raise HybridRunError(f"hybrid runtime is unavailable: {error}") from error + try: + scratch_root = Path(scratch_dir).resolve(strict=True) + except OSError as error: + raise HybridRunError(f"hybrid scratch directory is unavailable: {error}") from error + if not scratch_root.is_dir(): + raise HybridRunError("hybrid scratch path must resolve to a directory") + if type(same_frame_id) is not str or not same_frame_id.strip(): + raise HybridRunError("same_frame_id must be non-empty") + if cancel is not None and cancel.is_set(): + raise HybridRunCancelled("fauxce-hybrid cancelled before launch") + if progress is not None: + progress(0.0) + main_sha256 = _raw_sha256(main, dtype=np.dtype(" dict[str, Any]: + artifacts = receipt.get("artifacts") + if not isinstance(artifacts, list): + raise HybridRunError("hybrid receipt artifacts are malformed") + matches = [artifact for artifact in artifacts if isinstance(artifact, dict) and artifact.get("role") == role] + if len(matches) != 1: + raise HybridRunError(f"hybrid receipt must contain exactly one {role!r} artifact") + return matches[0] + + +def _validate_receipt_bindings( + receipt: dict[str, Any], + *, + runtime: HybridRuntimeConfig, + backend: str, + acquisition_manifest_sha256: str, + main: np.ndarray, + prepass: np.ndarray, + hybrid_rgb16: np.ndarray, + synthesis_mask: np.ndarray, + model_weights_rehashed: bool, +) -> None: + if receipt.get("schema") != "fauxce-hybrid-receipt-v2": + raise HybridRunError("hybrid receipt schema is unsupported") + inputs = receipt.get("inputs") + if not isinstance(inputs, dict): + raise HybridRunError("hybrid receipt inputs are malformed") + for role, expected in (("main", main), ("prepass", prepass)): + row = inputs.get(role) + if not isinstance(row, dict): + raise HybridRunError(f"hybrid receipt {role} input is missing") + if row.get("canonical_encoding") != "uint16_little_endian_c_order": + raise HybridRunError(f"hybrid receipt {role} encoding is unsupported") + if row.get("shape") != list(expected.shape): + raise HybridRunError(f"hybrid receipt {role} geometry changed") + if row.get("raw_sha256") != _raw_sha256(expected, dtype=np.dtype(" frame_pixels + or counts["synthesis_pixels"] > counts["at_floor_pixels"] + or counts["final_regions"] > counts["synthesis_pixels"] + or (counts["final_regions"] == 0) != (pixel_count == 0) + ): + raise HybridRunError("hybrid receipt routing counts changed") + + output_hash = _raw_sha256(hybrid_rgb16, dtype=np.dtype(" dict[str, Any]: @@ -202,6 +1052,7 @@ def _receipt_fields(receipt: dict[str, Any]) -> dict[str, Any]: __all__ = [ "HybridRunError", + "HybridRunCancelled", "HybridRunResult", "HybridRuntimeConfig", "run_hybrid_repair", diff --git a/negpy/services/repair/fauxice_ir_repair.py b/negpy/services/repair/fauxice_ir_repair.py index 3b8f3070..659745a2 100644 --- a/negpy/services/repair/fauxice_ir_repair.py +++ b/negpy/services/repair/fauxice_ir_repair.py @@ -14,21 +14,15 @@ calibration the main pass depends on. Its own docs are explicit that this is not a resampling convenience and cannot be reconstructed after the fact: "Reconstructing or guessing it from the main scan would move outside the -byte-exact claim." NegPy's current scanning pipeline -(``negpy/services/scanning/writer.py``) captures one pass per frame and -never retains a prepass, so on essentially every frame NegPy has imported to -date, ``prepass_rgbi`` is unavailable and this module reports ``SKIPPED`` -rather than fabricate one. See ``docs/FAUXICE_IR_REPAIR.md`` for the -consequence of that in practice, and for the ``exact``/``hybrid`` mode -split (``fauxice_hybrid_runner.py`` covers the hybrid path). - -This is a one-shot repair, not a per-render pipeline stage: the exact CPU -reference measures roughly an hour per frame, and its compiled and GPU -backends measure roughly six to ten seconds, still far too slow to sit in -``DarkroomEngine.process()`` and re-run on every slider drag the way -``RetouchConfig.ir_dust_remove`` does. Hybrid mode is slower still (see -``docs/FAUXICE_IR_REPAIR.md``), so this module also accepts an optional -progress callback and cancellation event, mirroring the shape +byte-exact claim." NegPy's generic import/scanning writer still lacks that +paired capture, but the Coolscan roll path now supplies its scanner-bound +285 dpi prepass, validity mask, and main RGBI acquisition directly. Calls +without that real prepass report ``SKIPPED`` rather than fabricate one. See +``docs/FAUXICE_IR_REPAIR.md`` for the exact/hybrid split +(``fauxice_hybrid_runner.py`` covers the external hybrid path). + +This is a one-shot repair, not a per-render pipeline stage. It accepts an +optional progress callback and cancellation event, mirroring the shape ``ScannerService.run_scan`` already uses for its own long operation (``negpy/services/scanning/service.py``), so a future background worker can wrap a repair call the same way ``ScanWorker`` wraps a scan. @@ -45,6 +39,7 @@ from __future__ import annotations import importlib.util +import hashlib import json import os import subprocess @@ -61,6 +56,7 @@ from negpy.kernel.system.logging import get_logger from negpy.services.repair.fauxice_hybrid_runner import ( + HybridRunCancelled, HybridRunError, HybridRuntimeConfig, run_hybrid_repair, @@ -96,6 +92,10 @@ class RepairMode(str, Enum): HYBRID = "hybrid" +class FauxiceRepairCancelled(RuntimeError): + """A cooperative stop requested by the caller.""" + + def engine_available() -> bool: """True if the core digital-fauxice engine is importable. @@ -106,15 +106,22 @@ def engine_available() -> bool: return importlib.util.find_spec(ENGINE_IMPORT_NAME) is not None -def hybrid_available() -> bool: - """True if the optional fauxce-hybrid companion is importable. +def hybrid_available(runtime: HybridRuntimeConfig | None = None) -> bool: + """True only when an explicit external hybrid runtime was supplied. - Independent of ``engine_available()``: a NegPy install can have the core - engine without the hybrid companion (the expected common case, since - hybrid also needs a separately configured, pinned IOPaint runtime that - this module never installs or manages). + The companion deliberately runs in a separate Python environment, so an + in-process import probe is both irrelevant and wrong on NegPy's supported + Python 3.13 runtime. ``HybridRuntimeConfig`` is validated when built and + carries every executable/path/hash needed at the process boundary. """ - return importlib.util.find_spec(HYBRID_IMPORT_NAME) is not None + + if runtime is None: + return False + try: + runtime.validate_files() + except ValueError: + return False + return True def _engine_version() -> str | None: @@ -165,8 +172,14 @@ class FauxiceRepairResult: backend_selection_reason: str | None = None hybrid_mask_png: bytes | None = None hybrid_mask_sha256: str | None = None + hybrid_mask: npt.NDArray[np.bool_] | None = None hybrid_synthesis_fraction: float | None = None hybrid_routing_counts: dict[str, int] | None = None + hybrid_receipt: bytes | None = None + hybrid_receipt_sha256: str | None = None + hybrid_provenance_class: str | None = None + hybrid_receipt_output_rgb_sha256: str | None = None + native_output_rgb_sha256: str | None = None def provenance(self) -> dict: """JSON-serializable provenance record; the sidecar's payload.""" @@ -254,12 +267,9 @@ def repair_ir_dust( ``progress`` and ``cancel`` follow ``ScannerService.run_scan``'s own shape (a ``0.0``-``1.0`` callback plus a ``threading.Event``) so a background worker can wire them the same way ``ScanWorker`` wires a - scan. Both are optional and both only cover the exact path: the engine - natively supports cooperative progress and cancellation there, but the - hybrid path is one blocking call to an external CLI, so ``progress`` is - never invoked during a hybrid run and ``cancel`` is only checked before - that call starts, not while it is running (see - ``docs/FAUXICE_IR_REPAIR.md``). + scan. The exact engine polls its native cooperative callback; the hybrid + runner polls the event while its isolated process group runs, terminates + that group on cancellation, and reports coarse phase progress. """ mode_requested = config.mode @@ -294,10 +304,8 @@ def repair_ir_dust( ) if cancel is not None and cancel.is_set(): - return FauxiceRepairResult( - status=RepairStatus.SKIPPED, - reason="cancelled by caller before repair started", - mode_requested=mode_requested, + raise FauxiceRepairCancelled( + "cancelled by caller before repair started" ) main_rgbi = np.dstack([rgb, ir]) @@ -307,12 +315,20 @@ def repair_ir_dust( hybrid_outcome = None if mode_requested is RepairMode.HYBRID: - if not hybrid_available(): - hybrid_note = f"hybrid mode requested but {HYBRID_IMPORT_NAME} is not installed; degraded to exact repair. " - elif hybrid_runtime is None: + if hybrid_runtime is None: hybrid_note = "hybrid mode requested but no hybrid runtime is configured; degraded to exact repair. " + elif not hybrid_available(hybrid_runtime): + try: + hybrid_runtime.validate_files() + except ValueError as error: + detail = str(error) + else: # pragma: no cover - defensive against a racing runtime + detail = "runtime artifacts changed during validation" + hybrid_note = f"hybrid mode requested but the configured hybrid runtime is unavailable ({detail}); degraded to exact repair. " elif cancel is not None and cancel.is_set(): - hybrid_note = "hybrid mode requested but cancelled before the hybrid run started; degraded to exact repair. " + raise FauxiceRepairCancelled( + "cancelled before the hybrid run started" + ) else: try: with tempfile.TemporaryDirectory(prefix="negpy-fauxice-hybrid-") as scratch: @@ -323,9 +339,15 @@ def repair_ir_dust( backend=config.backend, runtime=hybrid_runtime, scratch_dir=Path(scratch), - runner=hybrid_subprocess_runner or subprocess.run, + runner=hybrid_subprocess_runner, + progress=progress, + cancel=cancel, ) mode_resolved = RepairMode.HYBRID + except HybridRunCancelled as error: + raise FauxiceRepairCancelled( + f"hybrid repair cancelled: {error}" + ) from error except HybridRunError as error: hybrid_note = f"hybrid mode requested but the fauxce-hybrid run failed ({error}); degraded to exact repair. " @@ -339,8 +361,14 @@ def repair_ir_dust( "backend_selection_reason": hybrid_outcome.backend_selection_reason, "hybrid_mask_png": hybrid_outcome.synth_mask_png, "hybrid_mask_sha256": hybrid_outcome.synth_mask_sha256, + "hybrid_mask": hybrid_outcome.synth_mask, "hybrid_synthesis_fraction": hybrid_outcome.synthesis_fraction, "hybrid_routing_counts": hybrid_outcome.routing_counts, + "hybrid_receipt": hybrid_outcome.receipt, + "hybrid_receipt_sha256": hybrid_outcome.receipt_sha256, + "hybrid_provenance_class": hybrid_outcome.provenance_class, + "hybrid_receipt_output_rgb_sha256": (hybrid_outcome.output_rgb16_sha256), + "native_output_rgb_sha256": hybrid_outcome.output_rgb16_sha256, } region_note = "" if result_kwargs["hybrid_routing_counts"] is not None: @@ -363,12 +391,9 @@ def repair_ir_dust( cancel=cancel, ) except ProcessingCancelled as error: - return FauxiceRepairResult( - status=RepairStatus.SKIPPED, - reason=f"{hybrid_note}cancelled before completion: {error}", - mode_requested=mode_requested, - mode_resolved=None, - ) + raise FauxiceRepairCancelled( + f"{hybrid_note}cancelled before completion: {error}" + ) from error except (ValueError, TypeError, RuntimeError) as error: return FauxiceRepairResult( status=RepairStatus.SKIPPED, @@ -376,13 +401,21 @@ def repair_ir_dust( mode_requested=mode_requested, mode_resolved=None, ) - result_kwargs = {"engine_version": _engine_version(), **backend_info} + result_kwargs = { + "engine_version": _engine_version(), + "native_output_rgb_sha256": hashlib.sha256( + np.array(repaired, dtype=" Path: + return Path(BASE_USER_DIR) / RUNTIME_MANIFEST_FILENAME + + +def runtime_manifest_pin_path(manifest_path: Path) -> Path: + return manifest_path.with_name(manifest_path.name + ".sha256") + + +def _stable_regular_bytes(path: Path, *, maximum_bytes: int, label: str) -> bytes: + descriptor: int | None = None + try: + linked = os.lstat(path) + if stat.S_ISLNK(linked.st_mode) or not stat.S_ISREG(linked.st_mode): + raise HybridRuntimeManifestError(f"{label} must be a regular non-symlink file") + if linked.st_size < 0 or linked.st_size > maximum_bytes: + raise HybridRuntimeManifestError(f"{label} exceeds its safe size limit") + flags = ( + os.O_RDONLY + | getattr(os, "O_CLOEXEC", 0) + | getattr(os, "O_NOFOLLOW", 0) + | getattr(os, "O_NONBLOCK", 0) + ) + descriptor = os.open(path, flags) + with os.fdopen(descriptor, "rb", closefd=True) as handle: + descriptor = None + before = os.fstat(handle.fileno()) + if ( + not stat.S_ISREG(before.st_mode) + or (before.st_dev, before.st_ino) != (linked.st_dev, linked.st_ino) + or before.st_size != linked.st_size + ): + raise HybridRuntimeManifestError(f"{label} changed while opening") + payload = handle.read(maximum_bytes + 1) + after = os.fstat(handle.fileno()) + if ( + len(payload) != before.st_size + or len(payload) > maximum_bytes + or (before.st_dev, before.st_ino, before.st_size, before.st_mtime_ns) + != (after.st_dev, after.st_ino, after.st_size, after.st_mtime_ns) + ): + raise HybridRuntimeManifestError(f"{label} changed while reading") + return payload + except HybridRuntimeManifestError: + raise + except OSError as error: + raise HybridRuntimeManifestError(f"could not read {label}: {error}") from error + finally: + if descriptor is not None: + os.close(descriptor) + + +def canonical_runtime_manifest_bytes(document: object) -> bytes: + try: + return ( + json.dumps( + document, + sort_keys=True, + separators=(",", ":"), + ensure_ascii=True, + allow_nan=False, + ).encode("utf-8") + + b"\n" + ) + except (TypeError, ValueError, RecursionError) as error: + raise HybridRuntimeManifestError(f"hybrid runtime manifest cannot be canonicalized: {error}") from error + + +def load_hybrid_runtime_manifest( + manifest_path: str | Path, + *, + expected_sha256: str, +) -> HybridRuntimeConfig: + """Load one canonical manifest whose digest came from a separate boundary.""" + + path = Path(manifest_path) + if _SHA256_RE.fullmatch(expected_sha256) is None: + raise HybridRuntimeManifestError("hybrid runtime manifest pin must be a lowercase SHA-256 digest") + payload = _stable_regular_bytes( + path, + maximum_bytes=RUNTIME_MANIFEST_MAX_BYTES, + label="hybrid runtime manifest", + ) + actual_sha256 = hashlib.sha256(payload).hexdigest() + if actual_sha256 != expected_sha256: + raise HybridRuntimeManifestError(f"hybrid runtime manifest SHA-256 mismatch: expected {expected_sha256}, got {actual_sha256}") + try: + document = json.loads(payload) + except (UnicodeDecodeError, json.JSONDecodeError) as error: + raise HybridRuntimeManifestError(f"hybrid runtime manifest is not valid JSON: {error}") from error + if not isinstance(document, dict): + raise HybridRuntimeManifestError("hybrid runtime manifest must be an object") + if canonical_runtime_manifest_bytes(document) != payload: + raise HybridRuntimeManifestError("hybrid runtime manifest is not canonical JSON") + fields = frozenset(document) + if fields != _REQUIRED_FIELDS: + missing = sorted(_REQUIRED_FIELDS - fields) + extra = sorted(fields - _REQUIRED_FIELDS) + raise HybridRuntimeManifestError(f"hybrid runtime manifest fields changed (missing={missing}, extra={extra})") + if document["schema"] != RUNTIME_MANIFEST_SCHEMA: + raise HybridRuntimeManifestError("hybrid runtime manifest schema is unsupported") + for field in _PATH_FIELDS: + if not isinstance(document[field], str) or not document[field]: + raise HybridRuntimeManifestError(f"{field} must be a non-empty path string") + for field in _HASH_FIELDS: + if not isinstance(document[field], str) or _SHA256_RE.fullmatch(document[field]) is None: + raise HybridRuntimeManifestError(f"{field} must be a lowercase SHA-256 digest") + if document["inpaint_device"] not in {"cpu", "cuda", "mps"}: + raise HybridRuntimeManifestError("inpaint_device must be cpu, cuda, or mps") + for field, minimum in (("inpaint_threads", 1), ("inpaint_seed", 0)): + value = document[field] + if type(value) is not int or value < minimum: + raise HybridRuntimeManifestError(f"{field} must be an integer greater than or equal to {minimum}") + try: + return HybridRuntimeConfig( + hybrid_python=Path(document["hybrid_python"]), + executable=Path(document["executable"]), + core_source_manifest_sha256=document["core_source_manifest_sha256"], + hybrid_source_manifest_sha256=document["hybrid_source_manifest_sha256"], + iopaint_python=Path(document["iopaint_python"]), + iopaint_executable=Path(document["iopaint_executable"]), + iopaint_source_manifest_sha256=document["iopaint_source_manifest_sha256"], + model_dir=Path(document["model_dir"]), + model_weights=Path(document["model_weights"]), + model_weights_sha256=document["model_weights_sha256"], + inpaint_device=document["inpaint_device"], + inpaint_threads=document["inpaint_threads"], + inpaint_seed=document["inpaint_seed"], + ) + except (TypeError, ValueError) as error: + raise HybridRuntimeManifestError(f"hybrid runtime manifest values are invalid: {error}") from error + + +def load_default_hybrid_runtime_manifest( + manifest_path: str | Path | None = None, +) -> HybridRuntimeConfig | None: + """Load the desktop runtime, or return ``None`` when it was never installed.""" + + path = default_hybrid_runtime_manifest_path() if manifest_path is None else Path(manifest_path) + pin_path = runtime_manifest_pin_path(path) + if not path.exists() and not pin_path.exists(): + return None + if not path.exists() or not pin_path.exists(): + raise HybridRuntimeManifestError("hybrid runtime manifest and its .sha256 pin must both exist") + pin_bytes = _stable_regular_bytes( + pin_path, + maximum_bytes=65, + label="hybrid runtime manifest pin", + ) + try: + pin = pin_bytes.decode("ascii").rstrip("\n") + except UnicodeDecodeError as error: + raise HybridRuntimeManifestError("hybrid runtime manifest pin is not ASCII") from error + if pin_bytes != (pin + "\n").encode("ascii") or _SHA256_RE.fullmatch(pin) is None: + raise HybridRuntimeManifestError("hybrid runtime manifest pin must be one lowercase SHA-256 plus newline") + return load_hybrid_runtime_manifest(path, expected_sha256=pin) + + +__all__ = [ + "HybridRuntimeManifestError", + "RUNTIME_MANIFEST_FILENAME", + "RUNTIME_MANIFEST_SCHEMA", + "canonical_runtime_manifest_bytes", + "default_hybrid_runtime_manifest_path", + "load_default_hybrid_runtime_manifest", + "load_hybrid_runtime_manifest", + "runtime_manifest_pin_path", +] diff --git a/negpy/services/roll/__init__.py b/negpy/services/roll/__init__.py index 810cf5b8..6cdf3b68 100644 --- a/negpy/services/roll/__init__.py +++ b/negpy/services/roll/__init__.py @@ -5,11 +5,52 @@ `negpy.services.scanning`). """ +from negpy.services.roll.exact_color import ( + BuilderReceipt, + ExactColorIntegrityError, + ExactColorResult, + ExactColorUnavailable, + FIXED_COMPOSITION_SHA256, + NATIVE_BUILDER_SCOPE, + NativeValidatedBuilderReceipt, + PositiveColorMode, + STAGE3_REPLAY_SCOPE, + Stage1BuilderResult, + ValidatedBuilderReceipt, + VerifiedBuilderApplicationReceipt, + VerifiedCMSReceipt, + VerifiedPortableCMSEvaluator, + VerifiedStage1Builder, + load_stage3_replay_builder_receipt, +) +from negpy.services.roll.native_builder import NativeBuilderEvidence, build_native_builder_receipt +from negpy.services.roll.portable_builder import PortableStage1Builder +from negpy.services.roll.portable_cms import PortableCMSOnEvaluator from negpy.services.roll.service import RollFrameOutput, RollScanningError, RollScanningService, available __all__ = [ + "ExactColorIntegrityError", + "ExactColorResult", + "ExactColorUnavailable", + "FIXED_COMPOSITION_SHA256", + "BuilderReceipt", + "NATIVE_BUILDER_SCOPE", + "NativeBuilderEvidence", + "NativeValidatedBuilderReceipt", + "PositiveColorMode", + "PortableCMSOnEvaluator", + "PortableStage1Builder", "RollFrameOutput", "RollScanningError", "RollScanningService", + "STAGE3_REPLAY_SCOPE", + "Stage1BuilderResult", + "ValidatedBuilderReceipt", + "VerifiedBuilderApplicationReceipt", + "VerifiedCMSReceipt", + "VerifiedPortableCMSEvaluator", + "VerifiedStage1Builder", "available", + "build_native_builder_receipt", + "load_stage3_replay_builder_receipt", ] diff --git a/negpy/services/roll/deep_acceptance.py b/negpy/services/roll/deep_acceptance.py new file mode 100644 index 00000000..39a69642 --- /dev/null +++ b/negpy/services/roll/deep_acceptance.py @@ -0,0 +1,1993 @@ +"""Offline, fail-closed audit of completed LS-5000 acceptance outputs. + +The live acceptance runner deliberately performs only a cheap publication +check while the scanner is reserved. This module composes the stronger +loaders used by the production write path after the scanner has been closed: +Digital ICE replay, retained native-builder re-derivation, portable CML4 +re-evaluation, Hybrid disclosure binding, and exact TIFF/ICC verification. + +Nothing in this module opens a scanner or imports a hardware backend. +""" + +from __future__ import annotations + +import hashlib +import hmac +import importlib +import json +import os +import re +import stat +from contextlib import contextmanager +from dataclasses import dataclass +from pathlib import Path +from typing import Any, Iterator, NoReturn, Protocol, Sequence, cast + +import numpy as np + +from negpy.infrastructure.roll.repair import RepairAcquisition, RepairMode, RepairResult +from negpy.services.repair.hybrid_runtime_manifest import ( + HybridRuntimeManifestError, + load_default_hybrid_runtime_manifest, +) +from negpy.services.roll import exact_color, nikon_icc +from negpy.services.roll import service as roll_service +from negpy.services.roll.portable_builder import PortableStage1Builder +from negpy.services.roll.portable_cms import PortableCMSOnEvaluator +from negpy.services.roll.service import RollFrameOutput + + +class _FcntlAPI(Protocol): + LOCK_EX: int + LOCK_NB: int + LOCK_UN: int + + def flock(self, descriptor: int, operation: int) -> None: ... + + +try: + _fcntl_module = importlib.import_module("fcntl") +except ImportError: # pragma: no cover - this audit targets the macOS workflow + _fcntl: _FcntlAPI | None = None +else: + _fcntl = cast(_FcntlAPI, _fcntl_module) + + +SCHEMA = "negpy.ls5000-deep-acceptance.v1" +SLOTS = (1, 2, 3, 4, 5, 6) +APPROVED_SLOTS = frozenset((1, 6)) +_OUTPUT_FIELDS = ( + "rgb_path", + "ir_path", + "repaired_rgb_path", + "repaired_ir_path", + "positive_path", + "receipt_path", + "synthesis_mask_path", + "native_synthesis_mask_path", + "hybrid_receipt_path", +) +_SHA256_RE = re.compile(r"[0-9a-f]{64}") +_MAX_EVIDENCE_BYTES = 64 * 1024 * 1024 +_MAX_RECEIPT_BYTES = 16 * 1024 * 1024 + + +class _HybridRuntime(Protocol): + core_source_manifest_sha256: str + hybrid_source_manifest_sha256: str + iopaint_source_manifest_sha256: str + model_weights_sha256: str + inpaint_device: str + inpaint_threads: int + inpaint_seed: int + + def validate_files(self) -> None: ... + + +class DeepAcceptanceError(ValueError): + """A completed output failed an offline acceptance invariant.""" + + +@dataclass(frozen=True) +class _FrameAudit: + summary: dict[str, Any] + referenced_files: frozenset[Path] + receipt_path: Path + receipt: dict[str, Any] + ownership: object + builder_receipt: exact_color.NativeValidatedBuilderReceipt + output_artifacts: dict[str, str] + + +def _fail(label: str, message: str) -> NoReturn: + raise DeepAcceptanceError(f"{label}: {message}") + + +def _public_output_slot( + output: object, + *, + expected_slot: int | None, +) -> tuple[RollFrameOutput, int]: + if type(output) is not RollFrameOutput: + raise DeepAcceptanceError("output must be one RollFrameOutput") + slot = output.slot if expected_slot is None else expected_slot + if type(slot) is not int: + raise DeepAcceptanceError("expected slot must be an integer") + return output, slot + + +def _validated_runtime(runtime: _HybridRuntime | None) -> _HybridRuntime: + try: + active = runtime if runtime is not None else load_default_hybrid_runtime_manifest() + except (HybridRuntimeManifestError, OSError, TypeError, ValueError) as error: + raise DeepAcceptanceError(f"pinned Hybrid runtime manifest is invalid: {error}") from error + if active is None: + raise DeepAcceptanceError("the pinned Hybrid runtime is not installed") + try: + active.validate_files() + except (AttributeError, OSError, TypeError, ValueError) as error: + raise DeepAcceptanceError(f"pinned Hybrid runtime is invalid: {error}") from error + return active + + +def _color_dependencies( + builder: PortableStage1Builder | None, + evaluator: PortableCMSOnEvaluator | None, +) -> tuple[PortableStage1Builder, PortableCMSOnEvaluator]: + try: + active_builder = builder if builder is not None else PortableStage1Builder() + active_evaluator = evaluator if evaluator is not None else PortableCMSOnEvaluator() + except (exact_color.ExactColorUnavailable, OSError, TypeError, ValueError) as error: + raise DeepAcceptanceError(f"portable exact-color dependencies are unavailable: {error}") from error + return active_builder, active_evaluator + + +def _canonical_json( + value: object, + *, + newline: bool = False, + ensure_ascii: bool = False, +) -> bytes: + payload = json.dumps( + value, + sort_keys=True, + separators=(",", ":"), + ensure_ascii=ensure_ascii, + allow_nan=False, + ).encode("utf-8") + return payload + (b"\n" if newline else b"") + + +def _sha256(payload: bytes) -> str: + return hashlib.sha256(payload).hexdigest() + + +def _require_dict(value: object, *, label: str) -> dict[str, Any]: + if type(value) is not dict: + _fail(label, "must be an object") + return cast(dict[str, Any], value) + + +def _require_str(value: object, *, label: str) -> str: + if type(value) is not str or not value: + _fail(label, "must be a non-empty string") + return value + + +def _require_sha256(value: object, *, label: str) -> str: + if type(value) is not str or _SHA256_RE.fullmatch(value) is None: + _fail(label, "must be one lowercase SHA-256 digest") + return value + + +def _output_root(path: str | os.PathLike[str]) -> Path: + try: + candidate = Path(path) + linked = candidate.lstat() + resolved = candidate.resolve(strict=True) + except (OSError, TypeError, ValueError) as error: + raise DeepAcceptanceError(f"output directory is unavailable: {error}") from error + if stat.S_ISLNK(linked.st_mode) or not stat.S_ISDIR(linked.st_mode): + raise DeepAcceptanceError("output directory must be a real directory") + return resolved + + +def _regular_file( + path_value: str | os.PathLike[str], + *, + root: Path, + label: str, +) -> Path: + path = Path(path_value) + if not path.is_absolute(): + _fail(label, "path must be absolute") + lexical = Path(os.path.abspath(os.fspath(path))) + if not lexical.is_relative_to(root): + _fail(label, "escaped the output directory") + try: + current = root + for component in lexical.relative_to(root).parts[:-1]: + current /= component + parent_metadata = current.lstat() + if stat.S_ISLNK(parent_metadata.st_mode) or not stat.S_ISDIR(parent_metadata.st_mode): + _fail(label, "has a symlink or non-directory parent") + linked = lexical.lstat() + resolved = lexical.resolve(strict=True) + except OSError as error: + raise DeepAcceptanceError(f"{label}: unavailable: {error}") from error + if stat.S_ISLNK(linked.st_mode) or not stat.S_ISREG(linked.st_mode): + _fail(label, "must be a regular non-symlink file") + if resolved != lexical: + _fail(label, "resolved through a symbolic link") + return resolved + + +def _stable_bytes(path: Path, *, maximum_bytes: int, label: str) -> bytes: + try: + return roll_service._stable_regular_bytes( # noqa: SLF001 - production integrity boundary + str(path), + maximum_bytes=maximum_bytes, + label=label, + ) + except (OSError, ValueError) as error: + raise DeepAcceptanceError(f"{label}: {error}") from error + + +def _load_frame_receipt( + receipt_path: Path, + *, + label: str, +) -> tuple[dict[str, Any], bytes]: + payload = _stable_bytes( + receipt_path, + maximum_bytes=_MAX_RECEIPT_BYTES, + label=label, + ) + try: + document = roll_service._strict_json_loads(payload) # noqa: SLF001 + except (UnicodeDecodeError, ValueError, TypeError) as error: + raise DeepAcceptanceError(f"{label}: invalid JSON: {error}") from error + document = _require_dict(document, label=label) + try: + canonical = json.dumps( + document, + indent=2, + ensure_ascii=True, + allow_nan=False, + ).encode("utf-8") + except (TypeError, ValueError, RecursionError) as error: + raise DeepAcceptanceError(f"{label}: cannot canonicalize JSON: {error}") from error + if payload != canonical: + _fail(label, "is not in the production canonical frame-receipt encoding") + return document, payload + + +def _nested_receipt_value( + document: object, + *keys: str, + label: str, +) -> object: + current = document + for key in keys: + current = _require_dict(current, label=label).get(key) + return current + + +def _receipt_artifact_path( + document: object, + *keys: str, + root: Path, + label: str, +) -> Path: + value = _nested_receipt_value(document, *keys, label=label) + if type(value) is not str or not Path(value).is_absolute(): + _fail(label, "must be an absolute artifact path") + return _regular_file(value, root=root, label=label) + + +def _absolute_receipt_paths(value: object, *, key: str | None = None) -> set[str]: + found: set[str] = set() + if type(value) is dict: + for child_key, child in cast(dict[str, object], value).items(): + found.update(_absolute_receipt_paths(child, key=child_key)) + elif type(value) is list: + for child in value: + found.update(_absolute_receipt_paths(child, key=key)) + elif ( + type(value) is str + and key != "relative_path" + and (key == "path" or (key is not None and key.endswith("_path"))) + and Path(value).is_absolute() + ): + found.add(value) + return found + + +def _collect_completed_frame_files_locked( + output: RollFrameOutput, + *, + root: Path, + expected_slot: int, +) -> tuple[dict[str, Any], bytes, frozenset[Path]]: + """Resolve one exact completed receipt while its frame lock is held.""" + + label = f"slot {expected_slot}" + if type(output) is not RollFrameOutput or output.slot != expected_slot: + _fail(label, "RollFrameOutput identity is wrong") + receipt_path = _regular_file( + output.receipt_path, + root=root, + label=f"{label} frame receipt", + ) + receipt, receipt_bytes = _load_frame_receipt( + receipt_path, + label=f"{label} frame receipt", + ) + if ( + receipt.get("version") != 1 + or receipt.get("slot") != expected_slot + or receipt.get("dpi") != 4_000 + or receipt.get("depth") != 16 + or type(receipt.get("device_id")) is not str + or not receipt.get("device_id") + or receipt.get("device_model") != "Nikon LS-5000 ED 1.03" + ): + _fail(label, "frame or LS-5000 capture identity is wrong") + outputs = _require_dict(receipt.get("outputs"), label=f"{label} outputs") + unrepaired = _require_dict(outputs.get("unrepaired"), label=f"{label} unrepaired tier") + repaired = _require_dict(outputs.get("repaired"), label=f"{label} repaired tier") + positive = _require_dict(outputs.get("positive"), label=f"{label} positive tier") + dice = _require_dict( + outputs.get("repair_acquisition_evidence"), + label=f"{label} DICE evidence", + ) + native = _require_dict( + outputs.get("native_color_evidence"), + label=f"{label} native color evidence", + ) + if any(row.get("written") is not True for row in (unrepaired, repaired, positive)): + _fail(label, "all three output tiers must be complete") + if ( + repaired.get("mode_requested") != "hybrid" + or repaired.get("mode_resolved") != "hybrid" + or repaired.get("degraded") is not False + or positive.get("color_mode") != "nikon-exact" + or positive.get("exact_nikon_color") is not True + or positive.get("native_per_acquisition_builder") is not True + or positive.get("builder_validated") is not True + or positive.get("cms_verified") is not True + or dice.get("retained") is not True + or dice.get("replayable") is not True + or native.get("retained") is not True + or native.get("native_per_acquisition_builder") is not True + ): + _fail(label, "completed Hybrid/Nikon-exact evidence flags changed") + artifacts = _require_dict(receipt.get("artifacts"), label=f"{label} artifacts") + if set(artifacts) != {"rgb", "ir"}: + _fail(label, "frame artifact inventory is not exactly RGB and IR") + + required: list[Path] = [ + _receipt_artifact_path( + outputs, + "unrepaired", + "rgb_path", + root=root, + label=f"{label} RGB", + ), + _receipt_artifact_path( + outputs, + "unrepaired", + "ir_path", + root=root, + label=f"{label} IR", + ), + _receipt_artifact_path( + outputs, + "repaired", + "rgb_path", + root=root, + label=f"{label} repaired RGB", + ), + _receipt_artifact_path( + outputs, + "repaired", + "ir_path", + root=root, + label=f"{label} repaired IR", + ), + _receipt_artifact_path( + outputs, + "positive", + "rgb_path", + root=root, + label=f"{label} positive", + ), + _receipt_artifact_path( + outputs, + "repair_acquisition_evidence", + "binding", + "path", + root=root, + label=f"{label} DICE binding", + ), + ] + for key in ("prepass_rgbi", "ir_validity"): + required.append( + _receipt_artifact_path( + outputs, + "repair_acquisition_evidence", + "artifacts", + key, + "path", + root=root, + label=f"{label} DICE {key}", + ) + ) + for key in ("storage_rgb_tiff", "storage_ir_tiff"): + required.append( + _receipt_artifact_path( + outputs, + "repair_acquisition_evidence", + "sources", + key, + "path", + root=root, + label=f"{label} DICE {key}", + ) + ) + for branch, name in ( + (("applied_final", "storage"), "storage mask"), + (("applied_final", "native"), "native mask"), + (("routed_raw", "native"), "routed native mask"), + ): + required.append( + _receipt_artifact_path( + outputs, + "repaired", + "disclosure_mask", + *branch, + "path", + root=root, + label=f"{label} {name}", + ) + ) + for key, name in ( + ("hybrid_receipt", "Hybrid receipt"), + ("hybrid_evidence_binding", "Hybrid binding"), + ): + required.append( + _receipt_artifact_path( + outputs, + "repaired", + key, + "path", + root=root, + label=f"{label} {name}", + ) + ) + + retained = _require_dict( + native.get("retained_builder_evidence"), + label=f"{label} retained native builder", + ) + if positive.get("retained_builder_evidence") != retained: + _fail(label, "positive and native tiers reference different builder evidence") + for key in ( + "builder_receipt", + "analyzer_rgb", + "evidence_receipt", + "frame_ownership_receipt", + "density_evidence_receipt", + ): + required.append( + _receipt_artifact_path( + retained, + key, + "path", + root=root, + label=f"{label} native {key}", + ) + ) + luts = retained.get("pre_f_luts") + if type(luts) is not list or len(luts) != 3: + _fail(label, "retained native builder must contain exactly three LUTs") + for index, row in enumerate(luts): + required.append( + _receipt_artifact_path( + row, + "path", + root=root, + label=f"{label} native LUT {index}", + ) + ) + + required_set = set(required) + if len(required_set) != 21: + _fail(label, "receipt-bound artifact paths alias or are incomplete") + recorded_absolute = _absolute_receipt_paths(outputs) + if recorded_absolute != {str(path) for path in required_set}: + missing = sorted(str(path) for path in required_set - {Path(item) for item in recorded_absolute}) + extra = sorted(recorded_absolute - {str(path) for path in required_set}) + raise DeepAcceptanceError(f"{label}: receipt path inventory changed (missing={missing}, extra={extra})") + + expected_output_paths = { + "rgb_path": required[0], + "ir_path": required[1], + "repaired_rgb_path": required[2], + "repaired_ir_path": required[3], + "positive_path": required[4], + "receipt_path": receipt_path, + "synthesis_mask_path": required[10], + "native_synthesis_mask_path": required[11], + "hybrid_receipt_path": required[13], + } + if set(expected_output_paths) != set(_OUTPUT_FIELDS): + raise AssertionError("internal RollFrameOutput field inventory changed") + for field, path in expected_output_paths.items(): + if getattr(output, field) != str(path): + _fail(label, f"RollFrameOutput {field} differs from its receipt") + + receipt_lock = _regular_file( + _receipt_lock_path(receipt_path), + root=root, + label=f"{label} frame receipt lock", + ) + return receipt, receipt_bytes, frozenset((*required_set, receipt_path, receipt_lock)) + + +def collect_completed_frame_files( + output: RollFrameOutput, + *, + output_dir: str | os.PathLike[str], + expected_slot: int | None = None, +) -> list[str]: + """Cheaply collect one completed frame's exact owned-file inventory. + + This hardware-inert checkpoint validates only the bounded frame receipt, + path bindings, regular files, and persistent frame lock. It deliberately + does not decode TIFF/PNG/NPY data, hash model or raster bytes, load the + Hybrid runtime, or recompute color. + """ + + checked_output, slot = _public_output_slot( + output, + expected_slot=expected_slot, + ) + root = _output_root(output_dir) + with _hold_frame_locks((checked_output,), root=root): + _, _, referenced = _collect_completed_frame_files_locked( + checked_output, + root=root, + expected_slot=slot, + ) + return sorted(str(path) for path in referenced) + + +def _artifact_row( + row_value: object, + *, + root: Path, + label: str, + expected_path: Path | None = None, + maximum_bytes: int = _MAX_EVIDENCE_BYTES, + digest_key: str = "sha256", +) -> tuple[Path, bytes]: + row = _require_dict(row_value, label=label) + path_value = row.get("path") + if type(path_value) is not str: + _fail(label, "has no path") + path = _regular_file(path_value, root=root, label=label) + if expected_path is not None and path != expected_path: + _fail(label, "path disagrees with the published RollFrameOutput") + payload = _stable_bytes(path, maximum_bytes=maximum_bytes, label=label) + if row.get("bytes") != len(payload): + _fail(label, "byte count changed") + digest = _require_sha256(row.get(digest_key), label=f"{label} SHA-256") + if not hmac.compare_digest(digest, _sha256(payload)): + _fail(label, "SHA-256 changed") + return path, payload + + +def _published_path( + output: RollFrameOutput, + field: str, + recorded: object, + *, + root: Path, + label: str, +) -> Path: + value = getattr(output, field) + if type(value) is not str or type(recorded) is not str or value != recorded: + _fail(label, "receipt and RollFrameOutput paths disagree") + return _regular_file(value, root=root, label=label) + + +def _array_artifact_matches(row_value: object, array: np.ndarray, *, label: str) -> None: + row = _require_dict(row_value, label=label) + contiguous = np.ascontiguousarray(array) + expected = { + "byte_length": contiguous.nbytes, + "dtype": str(contiguous.dtype), + "sha256": hashlib.sha256(memoryview(contiguous).cast("B")).hexdigest(), + "shape": list(contiguous.shape), + } + if row != expected: + _fail(label, "does not bind the decoded array") + + +def _decode_tiff(path: Path, shape: tuple[int, ...], *, label: str) -> np.ndarray: + try: + return roll_service._stable_tiff_array( # noqa: SLF001 - production integrity boundary + str(path), + expected_shape=shape, + label=label, + ) + except (OSError, ValueError) as error: + raise DeepAcceptanceError(f"{label}: {error}") from error + + +def _bound_archive_path( + row_value: object, + *, + binding_path: Path, + root: Path, + label: str, +) -> Path: + row = _require_dict(row_value, label=label) + relative = row.get("relative_path") + if type(relative) is not str or not relative or Path(relative).is_absolute(): + _fail(label, "has no safe relative path") + return _regular_file( + binding_path.parent / relative, + root=root, + label=label, + ) + + +def _validate_dice_archive_paths( + dice: dict[str, Any], + *, + binding_path: Path, + binding_bytes: bytes, + root: Path, + rgb_path: Path, + ir_path: Path, + referenced: set[Path], + label: str, +) -> None: + """Bind sidecar paths to the exact relative members consumed by replay.""" + + try: + binding = roll_service._strict_json_loads(binding_bytes) # noqa: SLF001 + except (UnicodeDecodeError, ValueError, TypeError) as error: + raise DeepAcceptanceError(f"{label}: DICE binding JSON is invalid: {error}") from error + binding = _require_dict(binding, label=f"{label} DICE binding") + if binding_path.name != "acquisition-binding.json" or binding_path.parent.parent != root / ".negpy-dice-acquisition": + _fail(label, "DICE evidence escaped its production archive directory") + bound_acquisition = _require_dict(binding.get("acquisition"), label=f"{label} bound DICE acquisition") + if ( + dice.get("acquisition_id") != bound_acquisition.get("acquisition_id") + or dice.get("replay") != binding.get("replay") + or dice.get("schema") != binding.get("schema") + ): + _fail(label, "DICE sidecar identity or replay contract changed") + outer_artifacts = _require_dict(dice.get("artifacts"), label=f"{label} DICE artifacts") + bound_artifacts = _require_dict(binding.get("artifacts"), label=f"{label} bound DICE artifacts") + outer_sources = _require_dict(dice.get("sources"), label=f"{label} DICE sources") + bound_sources = _require_dict(binding.get("sources"), label=f"{label} bound DICE sources") + if set(outer_artifacts) != {"prepass_rgbi", "ir_validity"} or set(bound_artifacts) != {"prepass_rgbi", "ir_validity"}: + _fail(label, "DICE artifact inventory changed") + if set(outer_sources) != {"storage_rgb_tiff", "storage_ir_tiff"} or set(bound_sources) != {"storage_rgb_tiff", "storage_ir_tiff"}: + _fail(label, "DICE source inventory changed") + + expected_artifact_names = { + "prepass_rgbi": "prepass.rgbi16.npy", + "ir_validity": "ir-validity.npy", + } + for key, expected_name in expected_artifact_names.items(): + outer = _require_dict(outer_artifacts.get(key), label=f"{label} DICE {key}") + bound = _require_dict(bound_artifacts.get(key), label=f"{label} bound DICE {key}") + if {name: value for name, value in outer.items() if name != "path"} != bound: + _fail(label, f"DICE {key} sidecar differs from the replay binding") + outer_path, _ = _artifact_row( + outer, + root=root, + label=f"{label} DICE {key}", + digest_key="file_sha256", + ) + bound_path = _bound_archive_path( + bound, + binding_path=binding_path, + root=root, + label=f"{label} bound DICE {key}", + ) + if outer_path != bound_path: + _fail(label, f"DICE {key} path differs from the replay binding") + if bound_path.parent != binding_path.parent or bound_path.name != expected_name: + _fail(label, f"DICE {key} escaped its production archive directory") + referenced.add(bound_path) + + for key, expected_path in ( + ("storage_rgb_tiff", rgb_path), + ("storage_ir_tiff", ir_path), + ): + outer = _require_dict(outer_sources.get(key), label=f"{label} DICE {key}") + bound = _require_dict(bound_sources.get(key), label=f"{label} bound DICE {key}") + if {name: value for name, value in outer.items() if name != "path"} != bound: + _fail(label, f"DICE {key} sidecar differs from the replay binding") + recorded = outer.get("path") + if type(recorded) is not str: + _fail(label, f"DICE {key} has no path") + outer_path = _regular_file( + recorded, + root=root, + label=f"{label} DICE {key}", + ) + bound_path = _bound_archive_path( + bound, + binding_path=binding_path, + root=root, + label=f"{label} bound DICE {key}", + ) + if outer_path != expected_path or bound_path != expected_path: + _fail(label, f"DICE {key} does not bind the published Tier-1 TIFF") + + +def _stored_receipt_hash(document: object) -> str: + return _sha256(_canonical_json(document)) + + +def _receipt_lock_path(receipt_path: Path) -> Path: + """Return the persistent transaction-lock path for one frame receipt.""" + + lock_name = hashlib.sha256(str(receipt_path).encode("utf-8")).hexdigest() + return receipt_path.parent / ".negpy-locks" / f"{lock_name}.lock" + + +@contextmanager +def _hold_frame_locks( + outputs: Sequence[RollFrameOutput], + *, + root: Path, +) -> Iterator[tuple[Path, ...]]: + """Hold every production frame lock for the entire audit snapshot.""" + + if _fcntl is None: + raise DeepAcceptanceError("safe frame locking is unavailable") + lock_paths: set[Path] = set() + for index, output in enumerate(outputs, start=1): + if type(output) is not RollFrameOutput or type(output.receipt_path) is not str: + _fail(f"frame {index}", "RollFrameOutput identity is wrong") + receipt_path = _regular_file( + output.receipt_path, + root=root, + label=f"frame {index} receipt", + ) + lock_paths.add( + _regular_file( + _receipt_lock_path(receipt_path), + root=root, + label=f"frame {index} receipt lock", + ) + ) + + held: list[tuple[Path, int, tuple[int, int]]] = [] + try: + for path in sorted(lock_paths): + before = path.lstat() + flags = os.O_RDWR | getattr(os, "O_CLOEXEC", 0) | getattr(os, "O_NOFOLLOW", 0) + descriptor = os.open(path, flags) + try: + opened = os.fstat(descriptor) + identity = (opened.st_dev, opened.st_ino) + if not stat.S_ISREG(opened.st_mode) or identity != (before.st_dev, before.st_ino): + raise DeepAcceptanceError(f"frame lock changed while opening: {path}") + try: + _fcntl.flock(descriptor, _fcntl.LOCK_EX | _fcntl.LOCK_NB) + except BlockingIOError as error: + raise DeepAcceptanceError(f"frame output is busy in another process: {path}") from error + visible = path.lstat() + if identity != (visible.st_dev, visible.st_ino): + raise DeepAcceptanceError(f"frame lock changed while acquiring ownership: {path}") + except BaseException: + os.close(descriptor) + raise + held.append((path, descriptor, identity)) + yield tuple(path for path, _, _ in held) + for path, _, identity in held: + visible = path.lstat() + if not stat.S_ISREG(visible.st_mode) or identity != (visible.st_dev, visible.st_ino): + raise DeepAcceptanceError(f"frame lock changed during deep acceptance: {path}") + except OSError as error: + raise DeepAcceptanceError(f"cannot hold frame output locks: {error}") from error + finally: + for _, descriptor, _ in reversed(held): + try: + _fcntl.flock(descriptor, _fcntl.LOCK_UN) + finally: + os.close(descriptor) + + +def _require_receipt_copy( + document: object, + digest: object, + expected_document: dict[str, Any], + expected_digest: str, + *, + label: str, +) -> None: + if document != expected_document: + _fail(label, "payload differs from a fresh production evaluation") + if digest != expected_digest or _stored_receipt_hash(document) != expected_digest: + _fail(label, "SHA-256 differs from a fresh production evaluation") + + +def _runtime_receipt_binding( + receipt: dict[str, Any], + *, + acquisition: RepairAcquisition, + repaired: dict[str, Any], + runtime: _HybridRuntime, + label: str, +) -> None: + inputs = _require_dict(receipt.get("inputs"), label=f"{label} inputs") + main = _require_dict(inputs.get("main"), label=f"{label} main input") + prepass = _require_dict(inputs.get("prepass"), label=f"{label} prepass input") + geometry = _require_dict(inputs.get("geometry"), label=f"{label} geometry") + provenance = _require_dict(inputs.get("provenance"), label=f"{label} provenance") + main_rgbi = np.asarray(getattr(acquisition, "main_rgbi")) + prepass_rgbi = np.asarray(getattr(acquisition, "prepass_rgbi")) + if main != { + "canonical_encoding": "uint16_little_endian_c_order", + "raw_sha256": getattr(acquisition, "main_rgbi_sha256"), + "shape": list(main_rgbi.shape), + }: + _fail(label, "main input does not bind the retained acquisition") + if prepass != { + "canonical_encoding": "uint16_little_endian_c_order", + "raw_sha256": getattr(acquisition, "prepass_rgbi_sha256"), + "shape": list(prepass_rgbi.shape), + }: + _fail(label, "prepass input does not bind the retained acquisition") + if geometry.get("mask_shape") != list(main_rgbi.shape[:2]) or geometry.get("output_shape") != [*main_rgbi.shape[:2], 3]: + _fail(label, "geometry does not bind the retained acquisition") + assertion = { + "assertions": { + "focus_exposure_locked": True, + "same_frame_id": getattr(acquisition, "acquisition_id"), + }, + "inputs": { + "main": {"raw_sha256": getattr(acquisition, "main_rgbi_sha256")}, + "prepass": {"raw_sha256": getattr(acquisition, "prepass_rgbi_sha256")}, + }, + "provenance_class": "caller_asserted_bare_npy", + "schema": "negpy.fauxce-hybrid-acquisition-assertion-v1", + } + if provenance.get("basis") != "caller_asserted" or provenance.get("source_manifest_sha256") != _sha256(_canonical_json(assertion)): + _fail(label, "acquisition assertion binding changed") + + core = _require_dict(receipt.get("core"), label=f"{label} core") + backend = _require_dict(core.get("backend"), label=f"{label} backend") + generation = _require_dict(receipt.get("generation"), label=f"{label} generation") + if core.get("source_manifest_sha256") != getattr(runtime, "core_source_manifest_sha256") or generation.get( + "hybrid_source_manifest_sha256" + ) != getattr(runtime, "hybrid_source_manifest_sha256"): + _fail(label, "source manifests differ from the pinned runtime") + if core.get("version") != repaired.get("engine_version"): + _fail(label, "Hybrid core version differs from the repaired receipt") + if ( + backend.get("requested") != repaired.get("backend_requested") + or backend.get("used") != repaired.get("backend_used") + or backend.get("reason") != repaired.get("backend_selection_reason") + ): + _fail(label, "backend disclosure differs from the repaired receipt") + + inpainting = _require_dict(receipt.get("inpainting"), label=f"{label} inpainting") + if type(inpainting.get("invoked")) is not bool: + _fail(label, "inpainting invocation disclosure is malformed") + if inpainting["invoked"]: + model = _require_dict(inpainting.get("model"), label=f"{label} model") + tool = _require_dict(inpainting.get("tool"), label=f"{label} tool") + run = _require_dict(inpainting.get("runtime"), label=f"{label} runtime") + if ( + model.get("weights_sha256") != getattr(runtime, "model_weights_sha256") + or tool.get("iopaint_source_manifest_sha256") != getattr(runtime, "iopaint_source_manifest_sha256") + or run.get("device") != getattr(runtime, "inpaint_device") + or run.get("threads") != getattr(runtime, "inpaint_threads") + or run.get("seed") != getattr(runtime, "inpaint_seed") + ): + _fail(label, "inpainting runtime differs from the pinned runtime") + + +def _hybrid_result( + repaired: dict[str, Any], + *, + acquisition: RepairAcquisition, + repaired_rgb: np.ndarray, + output: RollFrameOutput, + root: Path, + runtime: _HybridRuntime, + referenced: set[Path], + label: str, +) -> RepairResult: + disclosure = _require_dict(repaired.get("disclosure_mask"), label=f"{label} disclosure mask") + applied = _require_dict(disclosure.get("applied_final"), label=f"{label} applied mask") + routed = _require_dict(disclosure.get("routed_raw"), label=f"{label} routed mask") + storage_row = _require_dict(applied.get("storage"), label=f"{label} storage mask") + native_row = _require_dict(applied.get("native"), label=f"{label} native mask") + routed_row = _require_dict(routed.get("native"), label=f"{label} routed native mask") + hybrid_row = _require_dict(repaired.get("hybrid_receipt"), label=f"{label} Hybrid receipt") + binding_row = _require_dict( + repaired.get("hybrid_evidence_binding"), + label=f"{label} Hybrid binding", + ) + + storage_path = _published_path( + output, + "synthesis_mask_path", + storage_row.get("path"), + root=root, + label=f"{label} storage mask", + ) + native_path = _published_path( + output, + "native_synthesis_mask_path", + native_row.get("path"), + root=root, + label=f"{label} native mask", + ) + receipt_path = _published_path( + output, + "hybrid_receipt_path", + hybrid_row.get("path"), + root=root, + label=f"{label} Hybrid receipt", + ) + storage_path, storage_png = _artifact_row( + storage_row, + root=root, + label=f"{label} storage mask", + expected_path=storage_path, + ) + native_path, native_png = _artifact_row( + native_row, + root=root, + label=f"{label} native mask", + expected_path=native_path, + ) + routed_path, routed_png = _artifact_row( + routed_row, + root=root, + label=f"{label} routed native mask", + ) + receipt_path, receipt_bytes = _artifact_row( + hybrid_row, + root=root, + label=f"{label} Hybrid receipt", + expected_path=receipt_path, + maximum_bytes=_MAX_RECEIPT_BYTES, + ) + binding_path, binding_bytes = _artifact_row( + binding_row, + root=root, + label=f"{label} Hybrid binding", + maximum_bytes=_MAX_RECEIPT_BYTES, + ) + evidence_directory = receipt_path.parent + hybrid_digest = _require_sha256(hybrid_row.get("sha256"), label=f"{label} Hybrid receipt SHA-256") + expected_hidden_paths = { + receipt_path: "hybrid-receipt.json", + native_path: "synth-mask-applied-scanner-native.png", + routed_path: "synth-mask-routed-scanner-native.png", + binding_path: "negpy-binding.json", + } + if evidence_directory.name != hybrid_digest or any( + path.parent != evidence_directory or path.name != expected_name for path, expected_name in expected_hidden_paths.items() + ): + _fail(label, "Hybrid evidence escaped its content-addressed directory") + referenced.update((storage_path, native_path, routed_path, receipt_path, binding_path)) + + try: + receipt_document = roll_service._strict_json_loads( # noqa: SLF001 + receipt_bytes + ) + binding_document = roll_service._strict_json_loads( # noqa: SLF001 + binding_bytes + ) + except (UnicodeDecodeError, ValueError, TypeError) as error: + raise DeepAcceptanceError(f"{label}: retained Hybrid JSON is invalid: {error}") from error + receipt_document = _require_dict(receipt_document, label=f"{label} Hybrid receipt") + binding_document = _require_dict(binding_document, label=f"{label} Hybrid binding") + if _canonical_json(receipt_document, newline=True, ensure_ascii=True) != receipt_bytes: + _fail(label, "Hybrid receipt is not canonical") + if _canonical_json(binding_document) != binding_bytes: + _fail(label, "Hybrid retained binding is not canonical") + + engine = _require_str(repaired.get("engine"), label=f"{label} repair engine") + engine_version = _require_str(repaired.get("engine_version"), label=f"{label} repair engine version") + reason = _require_str(repaired.get("reason"), label=f"{label} repair reason") + try: + result = RepairResult( + rgb=repaired_rgb, + engine=engine, + engine_version=engine_version, + mode_requested=RepairMode(repaired.get("mode_requested")), + mode_resolved=RepairMode(repaired.get("mode_resolved")), + reason=reason, + acquisition_id=repaired.get("acquisition_id"), + slot=repaired.get("slot"), + reservation_id=repaired.get("reservation_id"), + evidence_sha256=repaired.get("evidence_sha256"), + backend_requested=repaired.get("backend_requested"), + backend_used=repaired.get("backend_used"), + backend_selection_reason=repaired.get("backend_selection_reason"), + native_output_rgb_sha256=repaired.get("native_output_rgb_sha256"), + storage_output_rgb_sha256=repaired.get("storage_output_rgb_sha256"), + native_synthesis_mask_png=native_png, + native_synthesis_mask_sha256=native_row.get("sha256"), + native_synthesis_mask_shape=tuple(native_row.get("shape", ())), + routed_native_synthesis_mask_png=routed_png, + routed_native_synthesis_mask_sha256=routed_row.get("sha256"), + routed_native_synthesis_mask_shape=tuple(routed_row.get("shape", ())), + storage_synthesis_mask_png=storage_png, + storage_synthesis_mask_sha256=storage_row.get("sha256"), + storage_synthesis_mask_shape=tuple(storage_row.get("shape", ())), + synthesis_mask_transform=applied.get("transform"), + synthesis_fraction=applied.get("fraction"), + routing_counts=routed.get("routing_counts"), + hybrid_receipt=receipt_bytes, + hybrid_receipt_sha256=hybrid_row.get("sha256"), + hybrid_provenance_class=hybrid_row.get("provenance_class"), + hybrid_receipt_output_rgb_sha256=hybrid_row.get("verified_output_rgb_sha256"), + ) + except (TypeError, ValueError) as error: + raise DeepAcceptanceError(f"{label}: Hybrid result receipt is malformed: {error}") from error + try: + roll_service._validate_repair_result_binding( # noqa: SLF001 + acquisition, + result, + requested_mode=RepairMode.HYBRID, + ) + except (TypeError, ValueError) as error: + raise DeepAcceptanceError(f"{label}: Hybrid result binding failed: {error}") from error + if result.engine != "digital-fauxice" or result.degraded: + _fail(label, "repair was not non-degraded digital-fauxice Hybrid") + try: + applied_mask = roll_service._decode_binary_mask( # noqa: SLF001 + native_png, + expected_shape=acquisition.main_rgbi.shape[:2], + label=f"{label} final scanner-native disclosure mask", + ) + except (OSError, TypeError, ValueError) as error: + raise DeepAcceptanceError(f"{label}: final disclosure mask could not be decoded: {error}") from error + applied_pixel_count = int(np.count_nonzero(applied_mask)) + del applied_mask + if applied.get("pixel_count") != applied_pixel_count: + _fail(label, "outer synthesis pixel count differs from the decoded mask") + + expected_binding = { + "acquisition": { + "acquisition_id": getattr(acquisition, "acquisition_id"), + "capture_attempt_id": getattr(acquisition, "capture_attempt_id"), + "evidence_sha256": getattr(acquisition, "evidence_sha256"), + "ir_validity_sha256": getattr(acquisition, "ir_validity_sha256"), + "main_rgbi_sha256": getattr(acquisition, "main_rgbi_sha256"), + "prepass_rgbi_sha256": getattr(acquisition, "prepass_rgbi_sha256"), + "reservation_id": getattr(acquisition, "reservation_id"), + "slot": getattr(acquisition, "slot"), + "storage_transform": getattr(acquisition, "storage_transform"), + }, + "hybrid_receipt_sha256": result.hybrid_receipt_sha256, + "hybrid_receipt_output_rgb_sha256": result.hybrid_receipt_output_rgb_sha256, + "native_output_rgb_sha256": result.native_output_rgb_sha256, + "native_synthesis_mask_sha256": result.native_synthesis_mask_sha256, + "routed_native_synthesis_mask_sha256": result.routed_native_synthesis_mask_sha256, + "provenance_class": result.hybrid_provenance_class, + "schema": "negpy.dice-hybrid-retained-evidence-v2", + "storage_output_rgb_sha256": result.storage_output_rgb_sha256, + "storage_synthesis_mask_sha256": result.storage_synthesis_mask_sha256, + "synthesis_mask_transform": result.synthesis_mask_transform, + } + if binding_document != expected_binding: + _fail(label, "Hybrid retained binding changed") + _runtime_receipt_binding( + receipt_document, + acquisition=acquisition, + repaired=repaired, + runtime=runtime, + label=label, + ) + return result + + +def _retained_native_paths( + retained_value: object, + *, + root: Path, + label: str, + referenced: set[Path], +) -> Path: + retained = _require_dict(retained_value, label=label) + if retained.get("scope") != exact_color.NATIVE_BUILDER_SCOPE or retained.get("native_per_acquisition_builder") is not True: + _fail(label, "is not native per-acquisition builder evidence") + receipt_path, _ = _artifact_row(retained.get("builder_receipt"), root=root, label=f"{label} receipt") + if receipt_path.name != "native-builder-receipt.json": + _fail(label, "native builder receipt filename changed") + if receipt_path.parent.parent != root / ".negpy-native-builder": + _fail(label, "native builder evidence escaped its production directory") + referenced.add(receipt_path) + directory = receipt_path.parent + expected_rows = { + "analyzer_rgb": directory / "analyzer-rgb-u16le.bin", + "evidence_receipt": directory / "native-builder-evidence.json", + "frame_ownership_receipt": (directory / "nikon-density-frame-ownership.json"), + "density_evidence_receipt": directory / "nikon-density-evidence.json", + } + for key, expected_path in expected_rows.items(): + path, _ = _artifact_row( + retained.get(key), + root=root, + label=f"{label} {key}", + expected_path=expected_path, + ) + referenced.add(path) + luts = retained.get("pre_f_luts") + if type(luts) is not list or len(luts) != 3: + _fail(label, "must retain exactly three pre-F LUTs") + for index, (channel, row) in enumerate(zip(("r", "g", "b"), luts, strict=True)): + row_document = _require_dict(row, label=f"{label} pre-F LUT {index}") + if row_document.get("channel") != channel: + _fail(label, "pre-F LUT channel order changed") + path, _ = _artifact_row( + row_document, + root=root, + label=f"{label} pre-F LUT {index}", + expected_path=directory / f"builder-preF-{channel}.bin", + ) + referenced.add(path) + return receipt_path + + +def _validate_frame( + output: RollFrameOutput, + *, + root: Path, + expected_slot: int, + builder: PortableStage1Builder, + evaluator: PortableCMSOnEvaluator, + runtime: _HybridRuntime, +) -> _FrameAudit: + label = f"slot {expected_slot}" + receipt, receipt_bytes, completed_files = _collect_completed_frame_files_locked( + output, + root=root, + expected_slot=expected_slot, + ) + receipt_path = _regular_file(output.receipt_path, root=root, label=f"{label} frame receipt") + receipt_lock_path = _regular_file( + _receipt_lock_path(receipt_path), + root=root, + label=f"{label} frame receipt lock", + ) + smear = receipt.get("transport_smear") + if not isinstance(smear, dict): + # Compatibility with an early compact receipt spelling. + if receipt.get("transport_smear_verdict") != "clean": + _fail(label, "transport-smear verdict is not clean") + elif smear.get("verdict") != "clean": + _fail(label, "transport-smear verdict is not clean") + + outputs = _require_dict(receipt.get("outputs"), label=f"{label} outputs") + unrepaired = _require_dict(outputs.get("unrepaired"), label=f"{label} unrepaired tier") + repaired = _require_dict(outputs.get("repaired"), label=f"{label} repaired tier") + positive = _require_dict(outputs.get("positive"), label=f"{label} positive tier") + if any(entry.get("written") is not True for entry in (unrepaired, repaired, positive)): + _fail(label, "all three tiers were not written") + if repaired.get("mode_requested") != "hybrid" or repaired.get("mode_resolved") != "hybrid" or repaired.get("degraded") is not False: + _fail(label, "repair was not non-degraded Hybrid") + if ( + positive.get("color_mode") != "nikon-exact" + or positive.get("exact_nikon_color") is not True + or positive.get("inversion_path") != "native-per-acquisition-builder-and-verified-portable-cms" + or positive.get("native_per_acquisition_builder") is not True + or positive.get("native_builder_scope") != exact_color.NATIVE_BUILDER_SCOPE + or positive.get("builder_validated") is not True + or positive.get("cms_verified") is not True + ): + _fail(label, "positive is not validated native Nikon exact color") + + rgb_path = _published_path( + output, + "rgb_path", + unrepaired.get("rgb_path"), + root=root, + label=f"{label} RGB", + ) + ir_path = _published_path( + output, + "ir_path", + unrepaired.get("ir_path"), + root=root, + label=f"{label} IR", + ) + repaired_rgb_path = _published_path( + output, + "repaired_rgb_path", + repaired.get("rgb_path"), + root=root, + label=f"{label} repaired RGB", + ) + repaired_ir_path = _published_path( + output, + "repaired_ir_path", + repaired.get("ir_path"), + root=root, + label=f"{label} repaired IR", + ) + positive_path = _published_path( + output, + "positive_path", + positive.get("rgb_path"), + root=root, + label=f"{label} positive", + ) + referenced = { + receipt_path, + receipt_lock_path, + rgb_path, + ir_path, + repaired_rgb_path, + repaired_ir_path, + positive_path, + } + + dice = _require_dict( + outputs.get("repair_acquisition_evidence"), + label=f"{label} DICE acquisition evidence", + ) + if dice.get("retained") is not True or dice.get("replayable") is not True or dice.get("schema") != "negpy.dice-acquisition-replay-v1": + _fail(label, "DICE acquisition evidence is not retained and replayable") + binding_path, binding_bytes = _artifact_row( + dice.get("binding"), + root=root, + label=f"{label} DICE acquisition binding", + maximum_bytes=_MAX_RECEIPT_BYTES, + ) + referenced.add(binding_path) + _validate_dice_archive_paths( + dice, + binding_path=binding_path, + binding_bytes=binding_bytes, + root=root, + rgb_path=rgb_path, + ir_path=ir_path, + referenced=referenced, + label=label, + ) + try: + acquisition = roll_service.load_repair_acquisition_evidence(binding_path) + except (OSError, TypeError, ValueError) as error: + raise DeepAcceptanceError(f"{label}: DICE replay failed: {error}") from error + expected_rgb_name = f"acceptance_slot{expected_slot:02d}.tif" + expected_dice_token = _sha256(f"{acquisition.acquisition_id}\0acceptance_slot{expected_slot:02d}".encode("utf-8")) + if rgb_path.name != expected_rgb_name or binding_path.parent.name != expected_dice_token: + _fail(label, "DICE evidence directory is not content-addressed") + acquisition_entry = _require_dict(repaired.get("acquisition"), label=f"{label} repaired acquisition") + expected_acquisition = { + "acquisition_id": acquisition.acquisition_id, + "slot": acquisition.slot, + "reservation_id": acquisition.reservation_id, + "capture_attempt_id": acquisition.capture_attempt_id, + "evidence_sha256": acquisition.evidence_sha256, + "storage_transform": acquisition.storage_transform, + "main_rgbi_sha256": acquisition.main_rgbi_sha256, + "prepass_rgbi_sha256": acquisition.prepass_rgbi_sha256, + "ir_validity_sha256": acquisition.ir_validity_sha256, + } + if acquisition_entry != expected_acquisition: + _fail(label, "repaired acquisition differs from retained DICE evidence") + + storage_shape = ( + acquisition.main_rgbi.shape[1], + acquisition.main_rgbi.shape[0], + ) + rgb = _decode_tiff(rgb_path, (*storage_shape, 3), label=f"{label} RGB") + infrared = _decode_tiff(ir_path, storage_shape, label=f"{label} IR") + expected_storage = np.rot90(acquisition.main_rgbi, k=1, axes=(0, 1)) + if not np.array_equal(rgb, expected_storage[..., :3]) or not np.array_equal(infrared, expected_storage[..., 3]): + _fail(label, "Tier-1 TIFFs differ from the replayed DICE acquisition") + artifacts = _require_dict(receipt.get("artifacts"), label=f"{label} artifacts") + if set(artifacts) != {"rgb", "ir"}: + _fail(label, "frame artifact inventory is not exactly RGB and IR") + _array_artifact_matches(artifacts.get("rgb"), rgb, label=f"{label} RGB artifact") + _array_artifact_matches(artifacts.get("ir"), infrared, label=f"{label} IR artifact") + del rgb, expected_storage + + repaired_rgb = _decode_tiff(repaired_rgb_path, (*storage_shape, 3), label=f"{label} repaired RGB") + repaired_ir = _decode_tiff(repaired_ir_path, storage_shape, label=f"{label} repaired IR") + if not np.array_equal(repaired_ir, infrared): + _fail(label, "repaired IR is not the unchanged Tier-1 IR") + del repaired_ir, infrared + + repair_result = _hybrid_result( + repaired, + acquisition=acquisition, + repaired_rgb=repaired_rgb, + output=output, + root=root, + runtime=runtime, + referenced=referenced, + label=label, + ) + if ( + positive.get("repair_engine") != repair_result.engine + or positive.get("repair_engine_version") != repair_result.engine_version + or positive.get("repair_mode") != str(repair_result.mode_resolved) + ): + _fail(label, "positive repair provenance differs from the repaired tier") + acquisition_id = acquisition.acquisition_id + acquisition_reservation_id = acquisition.reservation_id + acquisition_capture_attempt_id = acquisition.capture_attempt_id + repair_storage_output_rgb_sha256 = repair_result.storage_output_rgb_sha256 + hybrid_receipt_sha256 = repair_result.hybrid_receipt_sha256 + del repair_result, acquisition + + native_evidence = _require_dict( + outputs.get("native_color_evidence"), + label=f"{label} native color evidence", + ) + if ( + native_evidence.get("retained") is not True + or native_evidence.get("native_per_acquisition_builder") is not True + or native_evidence.get("scope") != exact_color.NATIVE_BUILDER_SCOPE + ): + _fail(label, "native color evidence was not retained") + retained = native_evidence.get("retained_builder_evidence") + receipt_file = _retained_native_paths( + retained, + root=root, + label=f"{label} native builder evidence", + referenced=referenced, + ) + if positive.get("retained_builder_evidence") != retained: + _fail(label, "positive and native evidence reference different builders") + try: + builder_receipt = exact_color.load_native_builder_receipt(receipt_file) + except exact_color.ExactColorUnavailable as error: + raise DeepAcceptanceError(f"{label}: native builder reload failed: {error}") from error + if ( + builder_receipt.slot != expected_slot + or builder_receipt.reservation_id != acquisition_reservation_id + or builder_receipt.capture_attempt_id != acquisition_capture_attempt_id + ): + _fail(label, "native builder belongs to another acquisition") + expected_builder_document = exact_color.builder_receipt_payload(builder_receipt) + if ( + native_evidence.get("builder_receipt") != expected_builder_document + or native_evidence.get("builder_receipt_sha256") != builder_receipt.sha256 + ): + _fail(label, "native color evidence does not bind the reloaded builder") + + try: + color = exact_color.evaluate_exact_color( + repaired_rgb, + builder_receipt=builder_receipt, + builder=builder, + evaluator=evaluator, + ) + except exact_color.ExactColorUnavailable as error: + raise DeepAcceptanceError(f"{label}: exact color replay failed: {error}") from error + application = color.builder_application_receipt + if application is None: + _fail(label, "exact color replay omitted its builder application receipt") + _require_receipt_copy( + positive.get("builder_receipt"), + positive.get("builder_receipt_sha256"), + exact_color.receipt_payload(color.builder_receipt), + color.builder_receipt.sha256, + label=f"{label} builder receipt", + ) + _require_receipt_copy( + positive.get("builder_application_receipt"), + positive.get("builder_application_receipt_sha256"), + exact_color.receipt_payload(application), + application.sha256, + label=f"{label} builder application receipt", + ) + _require_receipt_copy( + positive.get("cms_receipt"), + positive.get("cms_receipt_sha256"), + exact_color.receipt_payload(color.cms_receipt), + color.cms_receipt.sha256, + label=f"{label} CMS receipt", + ) + if ( + positive.get("repaired_input_rgb_sha256") != color.source_rgb_sha256 + or positive.get("input_rgb_sha256") != color.input_rgb_sha256 + or positive.get("stage1_input_rgb_sha256") != color.input_rgb_sha256 + or positive.get("output_rgb_sha256") != color.output_rgb_sha256 + or repaired.get("storage_output_rgb_sha256") != repair_storage_output_rgb_sha256 + ): + _fail(label, "color or repair content hashes changed") + try: + profile = nikon_icc.nikon_adobe_rgb_profile() + except nikon_icc.NikonICCProfileError as error: + raise DeepAcceptanceError(f"{label}: pinned Nikon ICC profile is unavailable: {error}") from error + try: + tiff_binding = roll_service._verify_exact_positive_tiff( # noqa: SLF001 + str(positive_path), + expected_rgb=color.rgb, + expected_icc=profile, + ) + except exact_color.ExactColorIntegrityError as error: + raise DeepAcceptanceError(f"{label}: exact positive TIFF failed: {error}") from error + if tiff_binding != positive.get("tiff_artifact"): + _fail(label, "positive TIFF binding differs from its sidecar") + if positive.get("icc_profile") != nikon_icc.profile_receipt_binding(): + _fail(label, "positive ICC receipt differs from the pinned Nikon profile") + + try: + from coolscanpy.protocol.ls5000_single_pass.density import ( + NikonDensityFrameOwnershipReceipt, + ) + + ownership_document = roll_service._strict_json_loads( # noqa: SLF001 + builder_receipt.frame_ownership_receipt + ) + ownership = NikonDensityFrameOwnershipReceipt.from_dict(ownership_document) + except (ImportError, TypeError, ValueError) as error: + raise DeepAcceptanceError(f"{label}: density ownership is invalid: {error}") from error + outer_ownership = receipt.get("nikon_density_ownership") + compact_ownership = { + "reservation_id": ownership.reservation_id, + "batch_session_id": ownership.batch_session_id, + "preview_sha256": ownership.preview_sha256, + "preview_identity_sha256": ownership.preview_identity_sha256, + "transport_table_sha256": ownership.transport_table_sha256, + "reviewed_fingerprint_sha256": ownership.reviewed_fingerprint_sha256, + "fresh_fingerprint_sha256": ownership.fresh_fingerprint_sha256, + "frame_capture_attempt_id": ownership.frame_capture_attempt_id, + "frame_index": ownership.frame_index, + "frame_total": ownership.frame_total, + "selected_slots": list(ownership.selected_slots), + "selected_slot": ownership.selected_slot, + } + if outer_ownership not in (compact_ownership, ownership.to_dict()): + _fail(label, "public frame receipt and retained density ownership disagree") + if ( + ownership.selected_slot != expected_slot + or ownership.reservation_id != acquisition_reservation_id + or ownership.frame_capture_attempt_id != acquisition_capture_attempt_id + or receipt.get("reviewed_fingerprint_sha256") != ownership.reviewed_fingerprint_sha256 + or receipt.get("fresh_fingerprint_sha256") != ownership.fresh_fingerprint_sha256 + ): + _fail(label, "frame, acquisition, and density ownership disagree") + if ( + builder_receipt.preview_sha256 != ownership.preview_sha256 + or builder_receipt.preview_identity_sha256 != ownership.preview_identity_sha256 + or builder_receipt.batch_session_id != ownership.batch_session_id + ): + _fail(label, "native builder and density ownership disagree") + + if referenced != set(completed_files): + missing = sorted(str(path) for path in set(completed_files) - referenced) + extra = sorted(str(path) for path in referenced - set(completed_files)) + raise DeepAcceptanceError(f"{label}: deep artifact inventory changed (missing={missing}, extra={extra})") + output_artifacts: dict[str, str] = {} + for field in _OUTPUT_FIELDS: + path = getattr(output, field) + if type(path) is not str: + _fail(label, "RollFrameOutput omitted a completed artifact") + output_artifacts[field] = path + + return _FrameAudit( + summary={ + "slot": expected_slot, + "frame_receipt": { + "path": str(receipt_path), + "bytes": len(receipt_bytes), + "sha256": _sha256(receipt_bytes), + }, + "acquisition_id": acquisition_id, + "reservation_id": acquisition_reservation_id, + "capture_attempt_id": acquisition_capture_attempt_id, + "transport_identity_sha256": ownership.transport_identity_sha256, + "preview_identity_sha256": ownership.preview_identity_sha256, + "builder_receipt_sha256": builder_receipt.sha256, + "cms_receipt_sha256": color.cms_receipt.sha256, + "positive_file_sha256": tiff_binding["file_sha256"], + "hybrid_receipt_sha256": hybrid_receipt_sha256, + "referenced_file_count": len(referenced), + "referenced_files": sorted(str(path) for path in referenced), + }, + referenced_files=frozenset(referenced), + receipt_path=receipt_path, + receipt=receipt, + ownership=ownership, + builder_receipt=builder_receipt, + output_artifacts=output_artifacts, + ) + + +def validate_completed_frame( + output: RollFrameOutput, + *, + output_dir: str | os.PathLike[str], + expected_slot: int | None = None, + builder: PortableStage1Builder | None = None, + evaluator: PortableCMSOnEvaluator | None = None, + hybrid_runtime: _HybridRuntime | None = None, +) -> dict[str, Any]: + """Deeply validate one already-published frame without touching hardware.""" + + checked_output, slot = _public_output_slot( + output, + expected_slot=expected_slot, + ) + root = _output_root(output_dir) + runtime = _validated_runtime(hybrid_runtime) + active_builder, active_evaluator = _color_dependencies(builder, evaluator) + with _hold_frame_locks((checked_output,), root=root): + audit = _validate_frame( + checked_output, + root=root, + expected_slot=slot, + builder=active_builder, + evaluator=active_evaluator, + runtime=runtime, + ) + return { + "schema": SCHEMA, + "status": "passed", + "scope": "frame", + "output_dir": str(root), + **audit.summary, + } + + +def _validate_manual_approval( + frame: _FrameAudit, + *, + expected: bool, +) -> dict[str, Any] | None: + slot = frame.summary["slot"] + approval_value = frame.receipt.get("manual_approval") + if (approval_value is not None) is not expected: + _fail(f"slot {slot}", "manual approval presence is wrong") + if approval_value is None: + return None + approval = _require_dict(approval_value, label=f"slot {slot} manual approval") + if set(approval) != { + "reviewed_fingerprint_sha256", + "slot", + "spacing_offset", + "thumbnail_sha256", + "reviewed_lookup_row", + "reviewed_native_origin", + "review_reasons", + }: + _fail(f"slot {slot}", "manual approval fields changed") + review_reasons = approval.get("review_reasons") + if type(review_reasons) is not list or not review_reasons or any(type(reason) is not str or not reason for reason in review_reasons): + _fail(f"slot {slot}", "manual approval reasons are malformed") + try: + from coolscanpy.types import ApprovalReceipt + + parsed = ApprovalReceipt( + reviewed_fingerprint_sha256=approval["reviewed_fingerprint_sha256"], + slot=approval["slot"], + spacing_offset=approval["spacing_offset"], + thumbnail_sha256=approval["thumbnail_sha256"], + reviewed_lookup_row=approval["reviewed_lookup_row"], + reviewed_native_origin=approval["reviewed_native_origin"], + review_reasons=tuple(review_reasons), + ) + except (ImportError, KeyError, TypeError, ValueError) as error: + raise DeepAcceptanceError(f"slot {slot}: manual approval is invalid: {error}") from error + ownership = frame.ownership + if ( + parsed.slot != slot + or parsed.spacing_offset != frame.receipt.get("spacing_offset") + or parsed.reviewed_fingerprint_sha256 != getattr(ownership, "reviewed_fingerprint_sha256") + ): + _fail(f"slot {slot}", "manual approval does not bind the reviewed frame") + return { + "binding_sha256": parsed.binding_sha256, + "boundary_offset_rows": parsed.spacing_offset, + "review_reasons": list(parsed.review_reasons), + "reviewed_fingerprint_sha256": parsed.reviewed_fingerprint_sha256, + "reviewed_lookup_row": parsed.reviewed_lookup_row, + "reviewed_native_origin": parsed.reviewed_native_origin, + "schema_version": 1, + "slot": parsed.slot, + "thumbnail_sha256": parsed.thumbnail_sha256, + } + + +def _inventory( + root: Path, + frames: Sequence[_FrameAudit], + *, + allowed_output_lock_name: str | None, +) -> dict[str, Any]: + expected_files = set().union(*(frame.referenced_files for frame in frames)) + if allowed_output_lock_name is not None: + if ( + type(allowed_output_lock_name) is not str + or not allowed_output_lock_name + or Path(allowed_output_lock_name).name != allowed_output_lock_name + or allowed_output_lock_name in {".", ".."} + ): + raise DeepAcceptanceError("allowed output lock name must be one safe basename") + expected_files.add(root / allowed_output_lock_name) + + found_files: set[Path] = set() + found_directories = {root} + try: + for directory, names, files in os.walk(root, topdown=True, followlinks=False): + parent = Path(directory) + for name in names: + child = parent / name + metadata = child.lstat() + if stat.S_ISLNK(metadata.st_mode) or not stat.S_ISDIR(metadata.st_mode): + _fail("inventory", f"directory entry is unsafe: {child}") + found_directories.add(child.resolve(strict=True)) + for name in files: + child = parent / name + metadata = child.lstat() + if stat.S_ISLNK(metadata.st_mode) or not stat.S_ISREG(metadata.st_mode): + _fail("inventory", f"file entry is unsafe: {child}") + found_files.add(child.resolve(strict=True)) + except OSError as error: + raise DeepAcceptanceError(f"inventory walk failed: {error}") from error + + try: + expected_files = {path.resolve(strict=True) for path in expected_files} + except OSError as error: + raise DeepAcceptanceError(f"inventory expected file is unavailable: {error}") from error + expected_directories = {root} + for path in expected_files: + parent = path.parent + while parent != root: + expected_directories.add(parent) + parent = parent.parent + expected_directories.add(root) + if found_files != expected_files: + missing = sorted(str(path.relative_to(root)) for path in expected_files - found_files) + extra = sorted(str(path.relative_to(root)) for path in found_files - expected_files) + raise DeepAcceptanceError(f"inventory file set changed (missing={missing}, extra={extra})") + if found_directories != expected_directories: + missing = sorted(str(path.relative_to(root)) for path in expected_directories - found_directories) + extra = sorted(str(path.relative_to(root)) for path in found_directories - expected_directories) + raise DeepAcceptanceError(f"inventory directory set changed (missing={missing}, extra={extra})") + allowed_lock_path = (root / allowed_output_lock_name).resolve(strict=True) if allowed_output_lock_name is not None else None + visible = [ + path for path in found_files if path != allowed_lock_path and not any(part.startswith(".") for part in path.relative_to(root).parts) + ] + if len(visible) != 42: + raise DeepAcceptanceError(f"visible inventory contains {len(visible)} files; expected 42") + return { + "regular_file_count": len(found_files), + "directory_count": len(found_directories), + "visible_file_count": len(visible), + "allowed_output_lock_path": (str(allowed_lock_path) if allowed_lock_path is not None else None), + "exact": True, + } + + +def _run_receipt_binding( + run_receipt_path: str | os.PathLike[str] | None, + *, + root: Path, + frames: Sequence[_FrameAudit], + approvals: dict[int, dict[str, Any]], +) -> dict[str, Any] | None: + if run_receipt_path is None: + return None + path = Path(run_receipt_path).absolute() + if path.is_relative_to(root): + raise DeepAcceptanceError("run receipt must be outside the output directory") + payload = _stable_bytes( + path, + maximum_bytes=_MAX_RECEIPT_BYTES, + label="live run receipt", + ) + try: + document = roll_service._strict_json_loads(payload) # noqa: SLF001 + except (UnicodeDecodeError, ValueError, TypeError) as error: + raise DeepAcceptanceError(f"live run receipt is invalid JSON: {error}") from error + document = _require_dict(document, label="live run receipt") + if _canonical_json(document, newline=True, ensure_ascii=True) != payload: + raise DeepAcceptanceError("live run receipt is not canonical JSON") + rows = document.get("frames") + settings = document.get("settings") + close = document.get("close") + output_lease = document.get("output_lease") + operation_state = document.get("operation_state") + deep_result = document.get("deep_acceptance") + expected_device_id = frames[0].receipt.get("device_id") if frames else None + if ( + document.get("schema") != "negpy.ls5000-live-acceptance.v2" + or document.get("status") != "succeeded" + or document.get("phase") != "succeeded" + or document.get("device_id") != expected_device_id + or document.get("output_dir") != str(root) + or document.get("slots") != list(SLOTS) + or document.get("approved_slots") != sorted(APPROVED_SLOTS) + or settings + != { + "write_unrepaired": True, + "write_repaired": True, + "write_positive": True, + "repair_mode": "hybrid", + "positive_mode": "nikon-exact", + "filename_pattern": 'acceptance_slot{{ "%02d" % seq }}', + } + or close + != { + "iterator": {"attempted": True, "succeeded": True}, + "roll": {"attempted": True, "succeeded": True}, + } + or type(output_lease) is not dict + or output_lease.get("acquired") is not True + or output_lease.get("release_attempted") is not True + or output_lease.get("released") is not True + or type(operation_state) is not dict + or operation_state.get("batch_exhausted") is not True + or operation_state.get("verified_slots") != list(SLOTS) + or type(deep_result) is not dict + or deep_result.get("status") != "passed" + or deep_result.get("slots") != list(SLOTS) + or document.get("retry_count") != 0 + or document.get("eject_requested") is not False + or type(rows) is not list + or len(rows) != 6 + ): + raise DeepAcceptanceError("live run receipt did not record one successful six-frame run") + for frame, row_value in zip(frames, rows, strict=True): + row = _require_dict(row_value, label="live run frame") + artifacts = _require_dict(row.get("artifacts"), label="live run frame artifacts") + if ( + row.get("slot") != frame.summary["slot"] + or row.get("expected_slot") != frame.summary["slot"] + or row.get("frame_receipt_sha256") != frame.summary["frame_receipt"]["sha256"] + or artifacts != frame.output_artifacts + ): + _fail("live run receipt", "frame sidecar binding changed") + + reviewed_row = _require_dict( + document.get("reviewed_approval"), + label="live run reviewed approval", + ) + if set(reviewed_row) != { + "path", + "expected_sha256", + "verified_sha256", + "bytes", + "reviewed_fingerprint_sha256", + "contact_sheet", + }: + _fail("live run receipt", "reviewed-approval binding fields changed") + reviewed_path_value = reviewed_row.get("path") + if type(reviewed_path_value) is not str or not Path(reviewed_path_value).is_absolute(): + _fail("live run receipt", "reviewed-approval path is not absolute") + reviewed_path = Path(reviewed_path_value).absolute() + if reviewed_path.is_relative_to(root): + _fail("live run receipt", "reviewed approval must be outside output") + reviewed_bytes = _stable_bytes( + reviewed_path, + maximum_bytes=64 * 1024, + label="reviewed approval", + ) + reviewed_digest = _sha256(reviewed_bytes) + if ( + reviewed_row.get("bytes") != len(reviewed_bytes) + or reviewed_row.get("expected_sha256") != reviewed_digest + or reviewed_row.get("verified_sha256") != reviewed_digest + ): + _fail("live run receipt", "reviewed-approval content binding changed") + try: + reviewed_document = roll_service._strict_json_loads( # noqa: SLF001 + reviewed_bytes + ) + except (UnicodeDecodeError, ValueError, TypeError) as error: + raise DeepAcceptanceError(f"live run reviewed approval is invalid JSON: {error}") from error + reviewed_document = _require_dict( + reviewed_document, + label="live run reviewed approval", + ) + if _canonical_json(reviewed_document, newline=True, ensure_ascii=True) != reviewed_bytes: + _fail("live run receipt", "reviewed approval is not canonical JSON") + expected_approval_rows = [approvals[slot] for slot in sorted(APPROVED_SLOTS)] + reviewed_contact = _require_dict( + reviewed_document.get("contact_sheet"), + label="reviewed contact sheet", + ) + if ( + reviewed_document.get("schema") != "negpy.ls5000-reviewed-approval.v1" + or reviewed_document.get("approvals") != expected_approval_rows + or reviewed_document.get("reviewed_fingerprint_sha256") != getattr(frames[0].ownership, "reviewed_fingerprint_sha256") + or reviewed_row.get("reviewed_fingerprint_sha256") != reviewed_document.get("reviewed_fingerprint_sha256") + or reviewed_row.get("contact_sheet") + != { + "path": reviewed_contact.get("path"), + "sha256": reviewed_contact.get("sha256"), + } + ): + _fail("live run receipt", "reviewed approvals differ from frame receipts") + return {"path": str(path), "bytes": len(payload), "sha256": _sha256(payload)} + + +def _validate_six_frame_batch_locked( + outputs: Sequence[RollFrameOutput], + *, + output_dir: str | os.PathLike[str], + run_receipt_path: str | os.PathLike[str] | None = None, + allowed_output_lock_name: str | None = None, + builder: PortableStage1Builder | None = None, + evaluator: PortableCMSOnEvaluator | None = None, + hybrid_runtime: _HybridRuntime | None = None, +) -> dict[str, Any]: + """Deeply validate one exact six-frame batch and its recursive inventory.""" + + if isinstance(outputs, (str, bytes, bytearray)): + raise DeepAcceptanceError("batch must contain exactly six RollFrameOutput values") + try: + frame_outputs = tuple(outputs) + except TypeError as error: + raise DeepAcceptanceError("batch must contain exactly six RollFrameOutput values") from error + if len(frame_outputs) != 6 or any(type(output) is not RollFrameOutput for output in frame_outputs): + raise DeepAcceptanceError("batch must contain exactly six RollFrameOutput values") + if tuple(output.slot for output in frame_outputs) != SLOTS: + raise DeepAcceptanceError("batch slots must be exactly 1,2,3,4,5,6 in order") + root = _output_root(output_dir) + runtime = _validated_runtime(hybrid_runtime) + active_builder, active_evaluator = _color_dependencies(builder, evaluator) + frames = tuple( + _validate_frame( + output, + root=root, + expected_slot=slot, + builder=active_builder, + evaluator=active_evaluator, + runtime=runtime, + ) + for slot, output in zip(SLOTS, frame_outputs, strict=True) + ) + + common = None + density_receipt_sha256 = None + device_identity = None + approval_rows: dict[int, dict[str, Any]] = {} + for index, frame in enumerate(frames, start=1): + ownership = frame.ownership + if ( + getattr(ownership, "frame_index") != index + or getattr(ownership, "frame_total") != 6 + or tuple(getattr(ownership, "selected_slots")) != SLOTS + or getattr(ownership, "selected_slot") != index + ): + _fail(f"slot {index}", "batch coordinates are wrong") + approval_row = _validate_manual_approval( + frame, + expected=index in APPROVED_SLOTS, + ) + if approval_row is not None: + approval_rows[index] = approval_row + candidate = ( + getattr(ownership, "reservation_id"), + getattr(ownership, "batch_session_id"), + getattr(ownership, "preview_sha256"), + getattr(ownership, "preview_identity_sha256"), + getattr(ownership, "transport_table_sha256"), + getattr(ownership, "transport_identity_sha256"), + getattr(ownership, "reviewed_fingerprint_sha256"), + getattr(ownership, "fresh_fingerprint_sha256"), + ) + if common is None: + common = candidate + elif candidate != common: + raise DeepAcceptanceError("batch frames disagree on reservation, preview, transport, or fingerprint identity") + current_density = frame.builder_receipt.density_evidence_receipt_sha256 + if density_receipt_sha256 is None: + density_receipt_sha256 = current_density + elif current_density != density_receipt_sha256: + raise DeepAcceptanceError("batch frames disagree on Nikon density evidence") + current_device = ( + frame.receipt.get("device_id"), + frame.receipt.get("device_model"), + ) + if device_identity is None: + device_identity = current_device + elif current_device != device_identity: + raise DeepAcceptanceError("batch frames disagree on scanner identity") + + inventory = _inventory( + root, + frames, + allowed_output_lock_name=allowed_output_lock_name, + ) + run_receipt = _run_receipt_binding( + run_receipt_path, + root=root, + frames=frames, + approvals=approval_rows, + ) + assert common is not None + result: dict[str, Any] = { + "schema": SCHEMA, + "status": "passed", + "scope": "six-frame-batch", + "output_dir": str(root), + "slots": list(SLOTS), + "approved_slots": sorted(APPROVED_SLOTS), + "reservation_id": common[0], + "preview_sha256": common[2], + "preview_identity_sha256": common[3], + "transport_identity_sha256": common[5], + "reviewed_fingerprint_sha256": common[6], + "fresh_fingerprint_sha256": common[7], + "density_evidence_receipt_sha256": density_receipt_sha256, + "frames": [frame.summary for frame in frames], + "referenced_files": sorted(str(path) for path in set().union(*(frame.referenced_files for frame in frames))), + "inventory": inventory, + } + if run_receipt is not None: + result["run_receipt"] = run_receipt + assert device_identity is not None + result.update( + device_id=device_identity[0], + device_model=device_identity[1], + manual_approval_bindings=[ + { + "slot": slot, + "binding_sha256": approval_rows[slot]["binding_sha256"], + } + for slot in sorted(APPROVED_SLOTS) + ], + ) + return result + + +def validate_six_frame_batch( + outputs: Sequence[RollFrameOutput], + *, + output_dir: str | os.PathLike[str], + run_receipt_path: str | os.PathLike[str] | None = None, + allowed_output_lock_name: str | None = None, + builder: PortableStage1Builder | None = None, + evaluator: PortableCMSOnEvaluator | None = None, + hybrid_runtime: _HybridRuntime | None = None, +) -> dict[str, Any]: + """Deeply validate one locked, exact six-frame batch and its inventory.""" + + if isinstance(outputs, (str, bytes, bytearray)): + raise DeepAcceptanceError("batch must contain exactly six RollFrameOutput values") + try: + frame_outputs = tuple(outputs) + except TypeError as error: + raise DeepAcceptanceError("batch must contain exactly six RollFrameOutput values") from error + if len(frame_outputs) != 6 or any(type(output) is not RollFrameOutput for output in frame_outputs): + raise DeepAcceptanceError("batch must contain exactly six RollFrameOutput values") + if tuple(output.slot for output in frame_outputs) != SLOTS: + raise DeepAcceptanceError("batch slots must be exactly 1,2,3,4,5,6 in order") + root = _output_root(output_dir) + with _hold_frame_locks(frame_outputs, root=root): + return _validate_six_frame_batch_locked( + frame_outputs, + output_dir=root, + run_receipt_path=run_receipt_path, + allowed_output_lock_name=allowed_output_lock_name, + builder=builder, + evaluator=evaluator, + hybrid_runtime=hybrid_runtime, + ) + + +__all__ = [ + "DeepAcceptanceError", + "SCHEMA", + "collect_completed_frame_files", + "validate_completed_frame", + "validate_six_frame_batch", +] diff --git a/negpy/services/roll/exact_color.py b/negpy/services/roll/exact_color.py new file mode 100644 index 00000000..a6ea3ae0 --- /dev/null +++ b/negpy/services/roll/exact_color.py @@ -0,0 +1,1477 @@ +"""Fail-closed boundary for portable Nikon exact-color evaluation. + +This module deliberately contains no builder or CML4 math. It is the narrow +seam between NegPy's repaired uint16 RGB buffer and two separately validated +operations: + +* a Stage-3 evidence/replay bridge or a fresh, identity-bound native builder + receipt using three validated pre-F LUTs and the pinned LS5000.md3 post-F + composition; and +* a verified portable CMS evaluator, injected by the caller. NegPy ships one + adapter for the captured CML4 Stage-1/Stage-2 transform. + +Receipts are immutable bytes rather than mutable dictionaries. NegPy owns the +aggregate builder envelope; the embedded Stage-3 validation and CMS payloads +retain their producing components' schemas. Every payload has an exact +content hash and explicit attestation. Results are bound to repaired input, +computed Stage-1 input, and final output before any TIFF is written. + +The native receipt exists only when distinct 97-dpi density, 285-dpi analyzer, +and final-exposure evidence are explicitly bound to the same frame. +""" + +from __future__ import annotations + +import hashlib +import hmac +import json +import math +import os +import re +import stat +import struct +from dataclasses import dataclass, field +from enum import StrEnum +from pathlib import Path +from types import MappingProxyType +from typing import Any, Final, Mapping, Protocol, cast + +import numpy as np + + +_SHA256 = re.compile(r"^[0-9a-f]{64}$") +_MAX_RECEIPT_BYTES = 1024 * 1024 +_BUILDER_CHANNELS = ("r", "g", "b") +_PRE_F_LUT_BYTES = 65_536 * 2 +BUILDER_RECEIPT_SCHEMA = "negpy.validated-stage1-builder" +STAGE3_REPORT_SCHEMA = "nikonre.ls5000_stage3_validation" +FIXED_COMPOSITION_SHA256 = "8729cae5a7aa551ae35926b80d097d73d92ecb6bde471130d6344c6c10ecbe7a" +STAGE3_REPLAY_SCOPE = "stage3-captured-pref-evidence-replay-bridge" +NATIVE_BUILDER_SCOPE = "ls5000-native-per-acquisition-stage1-builder" +NATIVE_BUILDER_ALGORITHM_ID = "ls5000-md3-prescan-to-pref-v1" +NATIVE_RESOURCE_SHA256 = "cd934185df496f071d307ba4f96a2a2b6ac31c3c85efc62a7fa1e3216fdba70c" + +CMS_RECEIPT_KIND: Final = "negpy.portable-cms-on-receipt" +CMS_RECEIPT_VERSION: Final = 1 +CMS_ALGORITHM_ID: Final = "cml4-captured-optimized-stage1-stage2-v1" +CMS_SCOPE: Final = "captured-cml4-stage1-stage2-only" +CMS_ORACLE_SOURCE_SHA256: Final = "2a9bad4b89cefb9fcb2bebbc59009ea0248e1ea93897cfa99c2c320c7f675490" +CMS_VALIDATION_RECEIPT_SHA256: Final = "edf6f3f89158810f1de4ce3b4ff8938326bc50e1b3035af59af472258e7d95e8" +CMS_ASSET_SHA256: Mapping[str, str] = MappingProxyType( + { + "lch-atan-u16le.bin": "56b8ac82456941a0a8aad6d7de2c79b21785529037b504582731ce6abcd143b1", + "lch-sincos-i16le.bin": "dc8e71681bc46e60e33448171865fc96770dc17d3cfd0db835f6173e6fed7a35", + "lch-reciprocal-u16le.bin": "6959ef7deeb57dc96eb4653fe13b4d37fcb4250c28be15654b8e8dd236849042", + "cml4-stage1-clut0.bin": "a2abbc1e76dc037e6b364a58b483ddf01b9dde7c0e072f85885cb1f8c9dcbf1c", + "cml4-stage1-input-lut0.bin": "1a487c024ceaf83018c8ab0e405e9c12b25a0500a2ae6e3465e20b29638729d7", + "cml4-stage1-output-lut0.bin": "fe87ce159ec126597f9fb605b57cebec9b0264da1ca16d1725efe34db2e4fd2b", + "cml4-stage2-clut0.bin": "d14b7c76091552bf03899327ca6a7c74c712a0423e429de8f8cc8203d5c98da3", + "cml4-stage2-input-lut0.bin": "60fa510492f2adad2be9b00107b3d7ed7188a798f29748c3401560b70be3a248", + "cml4-stage2-output-lut0.bin": "b88574377d1a0cf47fe8806335641db2ca35197817cd055a4cd55141708c8291", + } +) + +_STAGE3_MODULE_BYTES = 1_052_672 +_STAGE3_MODULE_SHA256 = "45afd6fb61a9517ff95d1896dc4257779c319310c2e8bbe75f3b4f3dada920af" +_STAGE3_RESOURCE_BYTES = 1_024 +_STAGE3_RESOURCE_SHA256 = "cd934185df496f071d307ba4f96a2a2b6ac31c3c85efc62a7fa1e3216fdba70c" +_STAGE3_RESOURCE_VA = "0x100ce578" +_STAGE3_FIXED_ARTIFACT_BYTES = { + "analyzer-desc.bin": 96, + "analyzer-pixels.bin": 281 * 425 * 3 * 2, + "builder-args.bin": 204, + **{f"builder-control-{axis}-{channel}.bin": 256 for channel in _BUILDER_CHANNELS for axis in ("x", "y")}, + **{f"builder-preF-{channel}.bin": _PRE_F_LUT_BYTES for channel in _BUILDER_CHANNELS}, +} +_STAGE3_DYNAMIC_ARTIFACTS = { + "callback-buffer.bin", + "debugger-session.log", + "stage3-capture-state.json", +} +_STAGE3_ARTIFACTS = frozenset(_STAGE3_FIXED_ARTIFACT_BYTES) | _STAGE3_DYNAMIC_ARTIFACTS +_STAGE3_CALLBACK_BYTES = frozenset({281 * 425 * 3 * 2, 281 * 425 * 4 * 2}) +_TRUSTED_BUILDER_RECEIPT_TOKEN = object() +_TRUSTED_NATIVE_BUILDER_RECEIPT_TOKEN = object() +_TRUSTED_CMS_RECEIPT_TOKEN = object() + + +class ExactColorUnavailable(RuntimeError): + """Exact Nikon color cannot be produced from the supplied evidence.""" + + +class ExactColorIntegrityError(ExactColorUnavailable): + """An evaluator result or receipt is malformed or not content-bound.""" + + +class PositiveColorMode(StrEnum): + """Receipt-visible Tier-3 color paths; exact never implies fallback.""" + + NEGPY_APPROXIMATE = "negpy-approximate" + NIKON_EXACT = "nikon-exact" + + +@dataclass(frozen=True) +class ValidatedBuilderReceipt: + """File-validated Stage-3 replay evidence and exact builder artifacts. + + This is an evidence/replay bridge. It is not the future macOS-native, + per-acquisition builder, and direct caller construction is never trusted. + """ + + payload: bytes + sha256: str + stage3_receipt: bytes + stage3_receipt_sha256: str + pre_f_luts: tuple[bytes, bytes, bytes] + pre_f_lut_sha256: tuple[str, str, str] + fixed_composition_sha256: str + evidence_filenames: tuple[str, str, str, str] + _factory_token: object = field(repr=False, compare=False) + + @property + def attested(self) -> bool: + return self._factory_token is _TRUSTED_BUILDER_RECEIPT_TOKEN + + +@dataclass(frozen=True) +class NativeValidatedBuilderReceipt: + """Fresh pre-F LUTs derived from identity-bound native scan evidence.""" + + payload: bytes + sha256: str + evidence_payload: bytes + evidence_sha256: str + frame_ownership_receipt: bytes + frame_ownership_receipt_sha256: str + density_evidence_receipt: bytes + density_evidence_receipt_sha256: str + analyzer_rgb: bytes + analyzer_rgb_sha256: str + analyzer_shape: tuple[int, int, int] + pre_f_luts: tuple[bytes, bytes, bytes] + pre_f_lut_sha256: tuple[str, str, str] + fixed_composition_sha256: str + session_id: str + reservation_id: str + batch_session_id: str + preview_sha256: str + preview_identity_sha256: str + capture_attempt_id: str + scan_identity: str + slot: int + _factory_token: object = field(repr=False, compare=False) + + @property + def attested(self) -> bool: + return self._factory_token is _TRUSTED_NATIVE_BUILDER_RECEIPT_TOKEN + + +BuilderReceipt = ValidatedBuilderReceipt | NativeValidatedBuilderReceipt + + +@dataclass(frozen=True) +class VerifiedBuilderApplicationReceipt: + """Immutable receipt for applying validated builder artifacts to one frame.""" + + payload: bytes + sha256: str + attested: bool + + +@dataclass(frozen=True) +class Stage1BuilderResult: + """Computed CML Stage-1 input plus bindings to source and builder evidence.""" + + rgb: np.ndarray + source_rgb_sha256: str + stage1_input_rgb_sha256: str + builder_receipt: BuilderReceipt + application_receipt: VerifiedBuilderApplicationReceipt + + +@dataclass(frozen=True) +class VerifiedCMSReceipt: + """Opaque receipt issued only by the verified portable CMS adapter.""" + + payload: bytes + sha256: str + _factory_token: object = field(repr=False, compare=False) + + @property + def attested(self) -> bool: + return self._factory_token is _TRUSTED_CMS_RECEIPT_TOKEN + + +@dataclass(frozen=True) +class ExactColorResult: + """Final RGB plus receipts and hashes binding both sides of evaluation.""" + + rgb: np.ndarray + input_rgb_sha256: str + output_rgb_sha256: str + builder_receipt: BuilderReceipt + cms_receipt: VerifiedCMSReceipt + source_rgb_sha256: str | None = None + builder_application_receipt: VerifiedBuilderApplicationReceipt | None = None + + +class VerifiedStage1Builder(Protocol): + """Apply independently validated builder artifacts at the exact boundary.""" + + def apply( + self, + rgb: np.ndarray, + *, + builder_receipt: BuilderReceipt, + ) -> Stage1BuilderResult: ... + + +class VerifiedPortableCMSEvaluator(Protocol): + """Injected adapter around a separately verified portable CMS engine.""" + + def evaluate( + self, + rgb: np.ndarray, + *, + builder_receipt: BuilderReceipt, + ) -> ExactColorResult: ... + + +def rgb16_content_sha256(rgb: np.ndarray) -> str: + """Hash canonical RGB16 content, including its shape and byte order. + + Evaluator adapters should call this helper rather than reimplementing the + content-identity grammar. Shape is included so equal byte strings with a + different raster geometry cannot share an identity. + """ + + _validate_rgb16(rgb, label="RGB input") + canonical = np.asarray(rgb, dtype=" ValidatedBuilderReceipt: + """Load one real Stage-3 PASS report and its three adjacent pre-F LUTs. + + The four files are read as one fail-closed snapshot using non-symlink + descriptor reads. The report is then checked against the complete + Stage-3 PASS/provenance/artifact contract before a trusted replay receipt + can exist. Callers cannot replace this with an ``attested=True`` claim. + """ + + report_path = Path(stage3_report_path).expanduser().absolute() + lut_paths = tuple(report_path.parent / f"builder-preF-{channel}.bin" for channel in _BUILDER_CHANNELS) + report_blob, report_identity = _stable_read_non_symlink( + report_path, + label="Stage-3 validation report", + max_bytes=_MAX_RECEIPT_BYTES, + ) + lut_snapshots = tuple( + _stable_read_non_symlink( + path, + label=f"Stage-3 pre-F {channel} LUT", + expected_bytes=_PRE_F_LUT_BYTES, + ) + for channel, path in zip(_BUILDER_CHANNELS, lut_paths, strict=True) + ) + all_paths = (report_path, *lut_paths) + all_identities = (report_identity, *(identity for _, identity in lut_snapshots)) + _assert_paths_unchanged(all_paths, all_identities) + + pre_f_luts = ( + lut_snapshots[0][0], + lut_snapshots[1][0], + lut_snapshots[2][0], + ) + pre_f_hashes = ( + hashlib.sha256(pre_f_luts[0]).hexdigest(), + hashlib.sha256(pre_f_luts[1]).hexdigest(), + hashlib.sha256(pre_f_luts[2]).hexdigest(), + ) + report = _parse_json_object(report_blob, label="Stage-3 validation") + _validate_stage3_report(report, pre_f_hashes) + report_hash = hashlib.sha256(report_blob).hexdigest() + envelope = _canonical_json( + { + "fixed_composition": { + "lut_sha256": FIXED_COMPOSITION_SHA256, + "order": "F[B_c(i)]", + }, + "native_per_acquisition_builder": False, + "pre_f_luts": [ + { + "bytes": _PRE_F_LUT_BYTES, + "channel": channel, + "sha256": digest, + } + for channel, digest in zip(_BUILDER_CHANNELS, pre_f_hashes, strict=True) + ], + "schema": BUILDER_RECEIPT_SCHEMA, + "scope": STAGE3_REPLAY_SCOPE, + "stage3_receipt_sha256": report_hash, + "version": 1, + } + ) + receipt = ValidatedBuilderReceipt( + payload=envelope, + sha256=hashlib.sha256(envelope).hexdigest(), + stage3_receipt=report_blob, + stage3_receipt_sha256=report_hash, + pre_f_luts=pre_f_luts, + pre_f_lut_sha256=pre_f_hashes, + fixed_composition_sha256=FIXED_COMPOSITION_SHA256, + evidence_filenames=(report_path.name, lut_paths[0].name, lut_paths[1].name, lut_paths[2].name), + _factory_token=_TRUSTED_BUILDER_RECEIPT_TOKEN, + ) + builder_receipt_payload(receipt) + return receipt + + +def load_native_builder_receipt( + native_receipt_path: str | os.PathLike[str], +) -> NativeValidatedBuilderReceipt: + """Reload one retained native builder snapshot for an exact-color retry. + + Every acquisition-bound input is snapshotted through non-symlink file + descriptors with strict byte bounds, then revalidated as one native + receipt. Merely retaining three LUTs is insufficient: ownership, density, + analyzer, builder envelope, and LUTs all have to remain mutually bound. + """ + + receipt_path = Path(native_receipt_path).expanduser().absolute() + directory = receipt_path.parent + evidence_path = directory / "native-builder-evidence.json" + ownership_path = directory / "nikon-density-frame-ownership.json" + density_path = directory / "nikon-density-evidence.json" + analyzer_path = directory / "analyzer-rgb-u16le.bin" + lut_paths = tuple( + directory / f"builder-preF-{channel}.bin" + for channel in _BUILDER_CHANNELS + ) + paths = ( + receipt_path, + evidence_path, + ownership_path, + density_path, + analyzer_path, + *lut_paths, + ) + reads = ( + _stable_read_non_symlink( + receipt_path, + label="native builder receipt", + max_bytes=_MAX_RECEIPT_BYTES, + ), + _stable_read_non_symlink( + evidence_path, + label="native builder evidence", + max_bytes=_MAX_RECEIPT_BYTES, + ), + _stable_read_non_symlink( + ownership_path, + label="native frame-ownership receipt", + max_bytes=_MAX_RECEIPT_BYTES, + ), + _stable_read_non_symlink( + density_path, + label="native density-evidence receipt", + max_bytes=_MAX_RECEIPT_BYTES, + ), + _stable_read_non_symlink( + analyzer_path, + label="native analyzer RGB", + expected_bytes=425 * 281 * 3 * 2, + ), + *( + _stable_read_non_symlink( + path, + label=f"native pre-F {channel} LUT", + expected_bytes=_PRE_F_LUT_BYTES, + ) + for channel, path in zip( + _BUILDER_CHANNELS, + lut_paths, + strict=True, + ) + ), + ) + _assert_paths_unchanged( + paths, + tuple(identity for _, identity in reads), + label="native builder evidence", + ) + ( + receipt_blob, + evidence_blob, + ownership_blob, + density_blob, + analyzer_blob, + pre_f_r, + pre_f_g, + pre_f_b, + ) = (payload for payload, _ in reads) + + envelope = _parse_json_object(receipt_blob, label="native builder receipt") + if _canonical_json(envelope) != receipt_blob: + raise ExactColorIntegrityError( + "native builder receipt is not canonical JSON" + ) + # Import lazily: native_builder owns the pinned derivation and imports this + # boundary for its receipt factory. Re-running that derivation is what + # prevents a writable evidence directory from self-attesting new LUTs. + from negpy.services.roll import native_builder + + rebuilt = native_builder.rebuild_retained_native_builder_receipt( + envelope_payload=receipt_blob, + evidence_payload=evidence_blob, + frame_ownership_receipt=ownership_blob, + density_evidence_receipt=density_blob, + analyzer_rgb=analyzer_blob, + pre_f_luts=(pre_f_r, pre_f_g, pre_f_b), + ) + if directory.name != rebuilt.sha256: + raise ExactColorIntegrityError( + "native builder evidence is outside its content-addressed directory" + ) + return rebuilt + + +def _issue_verified_cms_receipt(payload: bytes) -> VerifiedCMSReceipt: + """Issue a CMS receipt from the hash-verified production adapter only.""" + + if type(payload) is not bytes or not payload or len(payload) > _MAX_RECEIPT_BYTES: + raise ExactColorIntegrityError("CMS receipt payload is missing or unbounded") + return VerifiedCMSReceipt( + payload=payload, + sha256=hashlib.sha256(payload).hexdigest(), + _factory_token=_TRUSTED_CMS_RECEIPT_TOKEN, + ) + + +def _issue_native_builder_receipt( + *, + payload: bytes, + evidence_payload: bytes, + frame_ownership_receipt: bytes, + frame_ownership_receipt_sha256: str, + density_evidence_receipt: bytes, + density_evidence_receipt_sha256: str, + analyzer_rgb: bytes, + analyzer_rgb_sha256: str, + analyzer_shape: tuple[int, int, int], + pre_f_luts: tuple[bytes, bytes, bytes], + session_id: str, + reservation_id: str, + batch_session_id: str, + preview_sha256: str, + preview_identity_sha256: str, + capture_attempt_id: str, + scan_identity: str, + slot: int, +) -> NativeValidatedBuilderReceipt: + """Issue a native receipt after the package-local builder closes its gates.""" + + receipt = NativeValidatedBuilderReceipt( + payload=payload, + sha256=hashlib.sha256(payload).hexdigest(), + evidence_payload=evidence_payload, + evidence_sha256=hashlib.sha256(evidence_payload).hexdigest(), + frame_ownership_receipt=frame_ownership_receipt, + frame_ownership_receipt_sha256=frame_ownership_receipt_sha256, + density_evidence_receipt=density_evidence_receipt, + density_evidence_receipt_sha256=density_evidence_receipt_sha256, + analyzer_rgb=analyzer_rgb, + analyzer_rgb_sha256=analyzer_rgb_sha256, + analyzer_shape=analyzer_shape, + pre_f_luts=pre_f_luts, + pre_f_lut_sha256=( + hashlib.sha256(pre_f_luts[0]).hexdigest(), + hashlib.sha256(pre_f_luts[1]).hexdigest(), + hashlib.sha256(pre_f_luts[2]).hexdigest(), + ), + fixed_composition_sha256=FIXED_COMPOSITION_SHA256, + session_id=session_id, + reservation_id=reservation_id, + batch_session_id=batch_session_id, + preview_sha256=preview_sha256, + preview_identity_sha256=preview_identity_sha256, + capture_attempt_id=capture_attempt_id, + scan_identity=scan_identity, + slot=slot, + _factory_token=_TRUSTED_NATIVE_BUILDER_RECEIPT_TOKEN, + ) + builder_receipt_payload(receipt) + return receipt + + +def evaluate_exact_color( + rgb: np.ndarray, + *, + builder_receipt: BuilderReceipt | None, + builder: VerifiedStage1Builder | None, + evaluator: VerifiedPortableCMSEvaluator | None, +) -> ExactColorResult: + """Evaluate and independently validate one exact-color result. + + Nothing falls back to NegPy's approximate renderer here. Missing or + invalid evidence raises ExactColorUnavailable and callers must expose + that state rather than label any other output exact. + """ + + _validate_rgb16(rgb, label="exact-color input") + if builder_receipt is None: + raise ExactColorUnavailable("validated Stage-3 replay or native builder receipt is not supplied") + if builder is None: + raise ExactColorUnavailable("verified Stage-1 builder applicator is not supplied") + if evaluator is None: + raise ExactColorUnavailable("verified portable CMS evaluator is not supplied") + builder_receipt_payload(builder_receipt) + + source_snapshot = np.array(rgb, dtype=np.uint16, order="C", copy=True) + expected_source = rgb16_content_sha256(source_snapshot) + source_snapshot.setflags(write=False) + try: + builder_result = builder.apply(source_snapshot, builder_receipt=builder_receipt) + except ExactColorUnavailable: + raise + except Exception as error: + raise ExactColorUnavailable(f"Stage-1 builder applicator failed: {error}") from error + if not isinstance(builder_result, Stage1BuilderResult): + raise ExactColorIntegrityError("Stage-1 builder returned an invalid result type") + if builder_result.builder_receipt != builder_receipt: + raise ExactColorIntegrityError("Stage-1 builder substituted a different builder receipt") + builder_receipt_payload(builder_result.builder_receipt) + application_payload = _receipt_payload( + builder_result.application_receipt, + VerifiedBuilderApplicationReceipt, + label="builder application", + ) + if rgb16_content_sha256(source_snapshot) != expected_source: + raise ExactColorIntegrityError("Stage-1 builder mutated its repaired RGB input") + if builder_result.source_rgb_sha256 != expected_source: + raise ExactColorIntegrityError("builder source hash does not match the repaired RGB content") + _validate_rgb16(builder_result.rgb, label="Stage-1 builder output") + if builder_result.rgb.shape != source_snapshot.shape: + raise ExactColorIntegrityError("Stage-1 builder output geometry differs from its input") + stage1_snapshot = np.array(builder_result.rgb, dtype=np.uint16, order="C", copy=True) + expected_stage1 = rgb16_content_sha256(stage1_snapshot) + if builder_result.stage1_input_rgb_sha256 != expected_stage1: + raise ExactColorIntegrityError("builder output hash does not match the Stage-1 input content") + if application_payload.get("builder_receipt_sha256") != builder_receipt.sha256: + raise ExactColorIntegrityError("builder application receipt does not bind the builder receipt") + if application_payload.get("source_rgb_sha256") != expected_source: + raise ExactColorIntegrityError("builder application receipt does not bind its repaired RGB input") + if application_payload.get("stage1_input_rgb_sha256") != expected_stage1: + raise ExactColorIntegrityError("builder application receipt does not bind its Stage-1 output") + _validate_builder_application_receipt(application_payload, builder_receipt) + + stage1_snapshot.setflags(write=False) + try: + result = evaluator.evaluate(stage1_snapshot, builder_receipt=builder_receipt) + except ExactColorUnavailable: + raise + except Exception as error: + raise ExactColorUnavailable(f"portable CMS evaluator failed: {error}") from error + if not isinstance(result, ExactColorResult): + raise ExactColorIntegrityError("portable CMS evaluator returned an invalid result type") + if result.builder_receipt != builder_receipt: + raise ExactColorIntegrityError("evaluator result substituted a different builder receipt") + builder_receipt_payload(result.builder_receipt) + cms_payload = _receipt_payload(result.cms_receipt, VerifiedCMSReceipt, label="CMS") + + if rgb16_content_sha256(stage1_snapshot) != expected_stage1: + raise ExactColorIntegrityError("portable CMS evaluator mutated its Stage-1 input") + if result.input_rgb_sha256 != expected_stage1: + raise ExactColorIntegrityError("evaluator input hash does not match the Stage-1 input content") + _validate_rgb16(result.rgb, label="exact-color output") + if result.rgb.shape != stage1_snapshot.shape: + raise ExactColorIntegrityError("exact-color output geometry differs from its input") + # Snapshot before hashing: the external evaluator may retain its original + # array, but it cannot change the content we bind and hand to the writer. + snapshot = np.array(result.rgb, dtype=np.uint16, order="C", copy=True) + expected_output = rgb16_content_sha256(snapshot) + if result.output_rgb_sha256 != expected_output: + raise ExactColorIntegrityError("evaluator output hash does not match the returned RGB content") + _validate_cms_receipt( + cms_payload, + builder_receipt_sha256=builder_receipt.sha256, + input_rgb_sha256=expected_stage1, + output_rgb_sha256=expected_output, + ) + + snapshot.setflags(write=False) + return ExactColorResult( + rgb=snapshot, + input_rgb_sha256=expected_stage1, + output_rgb_sha256=expected_output, + builder_receipt=result.builder_receipt, + cms_receipt=result.cms_receipt, + source_rgb_sha256=expected_source, + builder_application_receipt=builder_result.application_receipt, + ) + + +def receipt_payload( + receipt: BuilderReceipt | VerifiedBuilderApplicationReceipt | VerifiedCMSReceipt, +) -> dict[str, Any]: + """Return a JSON-safe copy for the outer NegPy scan receipt.""" + + if isinstance(receipt, (ValidatedBuilderReceipt, NativeValidatedBuilderReceipt)): + return builder_receipt_payload(receipt) + if type(receipt) is VerifiedBuilderApplicationReceipt: + expected = VerifiedBuilderApplicationReceipt + elif type(receipt) is VerifiedCMSReceipt: + expected = VerifiedCMSReceipt + else: + raise ExactColorIntegrityError("receipt has an invalid type") + return _receipt_payload(receipt, expected, label="receipt") + + +def builder_receipt_payload(receipt: BuilderReceipt) -> dict[str, Any]: + """Validate every artifact binding in a replay or native builder receipt.""" + + if type(receipt) is NativeValidatedBuilderReceipt: + return _native_builder_receipt_payload(receipt) + + if type(receipt) is not ValidatedBuilderReceipt or receipt._factory_token is not _TRUSTED_BUILDER_RECEIPT_TOKEN: + raise ExactColorIntegrityError("builder receipt was not produced by the trusted Stage-3 replay file loader") + payload = _receipt_payload(receipt, ValidatedBuilderReceipt, label="builder") + _validate_sha256(receipt.stage3_receipt_sha256, label="Stage-3 receipt") + if type(receipt.stage3_receipt) is not bytes or not receipt.stage3_receipt: + raise ExactColorIntegrityError("Stage-3 validation receipt is missing") + if not hmac.compare_digest( + hashlib.sha256(receipt.stage3_receipt).hexdigest(), + receipt.stage3_receipt_sha256, + ): + raise ExactColorIntegrityError("Stage-3 validation receipt does not match its SHA-256") + _validate_pre_f_artifacts(receipt) + stage3 = _parse_json_object(receipt.stage3_receipt, label="Stage-3 validation") + _validate_stage3_report(stage3, receipt.pre_f_lut_sha256) + + expected_luts = [ + { + "bytes": _PRE_F_LUT_BYTES, + "channel": channel, + "sha256": digest, + } + for channel, digest in zip(_BUILDER_CHANNELS, receipt.pre_f_lut_sha256, strict=True) + ] + if payload.get("schema") != BUILDER_RECEIPT_SCHEMA or type(payload.get("version")) is not int or payload.get("version") != 1: + raise ExactColorIntegrityError("builder receipt envelope schema is unsupported") + if payload.get("scope") != STAGE3_REPLAY_SCOPE or payload.get("native_per_acquisition_builder") is not False: + raise ExactColorIntegrityError("builder receipt does not identify the Stage-3 evidence/replay bridge") + if payload.get("stage3_receipt_sha256") != receipt.stage3_receipt_sha256: + raise ExactColorIntegrityError("builder receipt envelope does not bind the Stage-3 receipt") + if payload.get("pre_f_luts") != expected_luts: + raise ExactColorIntegrityError("builder receipt envelope does not bind the three pre-F LUTs") + if payload.get("fixed_composition") != { + "lut_sha256": FIXED_COMPOSITION_SHA256, + "order": "F[B_c(i)]", + }: + raise ExactColorIntegrityError("builder receipt envelope does not bind the fixed post-F composition") + if ( + type(receipt.evidence_filenames) is not tuple + or len(receipt.evidence_filenames) != 4 + or type(receipt.evidence_filenames[0]) is not str + or not receipt.evidence_filenames[0] + or receipt.evidence_filenames[1:] + != ( + "builder-preF-r.bin", + "builder-preF-g.bin", + "builder-preF-b.bin", + ) + ): + raise ExactColorIntegrityError("builder receipt evidence filenames are malformed") + return payload + + +def _native_builder_receipt_payload(receipt: NativeValidatedBuilderReceipt) -> dict[str, Any]: + if receipt._factory_token is not _TRUSTED_NATIVE_BUILDER_RECEIPT_TOKEN: + raise ExactColorIntegrityError("native builder receipt was not produced by the trusted native factory") + payload = _receipt_payload(receipt, NativeValidatedBuilderReceipt, label="native builder") + if type(receipt.evidence_payload) is not bytes or not receipt.evidence_payload: + raise ExactColorIntegrityError("native builder evidence payload is missing") + _validate_sha256(receipt.evidence_sha256, label="native builder evidence") + if not hmac.compare_digest(hashlib.sha256(receipt.evidence_payload).hexdigest(), receipt.evidence_sha256): + raise ExactColorIntegrityError("native builder evidence does not match its SHA-256") + evidence = _parse_json_object(receipt.evidence_payload, label="native builder evidence") + for label, blob, expected_sha256 in ( + ("frame ownership", receipt.frame_ownership_receipt, receipt.frame_ownership_receipt_sha256), + ("density evidence", receipt.density_evidence_receipt, receipt.density_evidence_receipt_sha256), + ): + _validate_sha256(expected_sha256, label=f"native {label}") + if type(blob) is not bytes or not blob or not hmac.compare_digest(hashlib.sha256(blob).hexdigest(), expected_sha256): + raise ExactColorIntegrityError(f"native {label} receipt does not match its SHA-256") + _validate_native_evidence_document(evidence, receipt) + if ( + type(receipt.analyzer_rgb) is not bytes + or type(receipt.analyzer_shape) is not tuple + or receipt.analyzer_shape != (425, 281, 3) + or len(receipt.analyzer_rgb) != 425 * 281 * 3 * 2 + ): + raise ExactColorIntegrityError("native builder analyzer snapshot has unsupported geometry") + _validate_sha256(receipt.analyzer_rgb_sha256, label="native builder analyzer RGB") + if not hmac.compare_digest(hashlib.sha256(receipt.analyzer_rgb).hexdigest(), receipt.analyzer_rgb_sha256): + raise ExactColorIntegrityError("native builder analyzer snapshot does not match its SHA-256") + _validate_pre_f_artifacts(receipt) + expected_luts = [ + {"bytes": _PRE_F_LUT_BYTES, "channel": channel, "sha256": digest} + for channel, digest in zip(_BUILDER_CHANNELS, receipt.pre_f_lut_sha256, strict=True) + ] + identity = { + "batch_session_id": receipt.batch_session_id, + "capture_attempt_id": receipt.capture_attempt_id, + "preview_identity_sha256": receipt.preview_identity_sha256, + "preview_sha256": receipt.preview_sha256, + "reservation_id": receipt.reservation_id, + "scan_identity": receipt.scan_identity, + "session_id": receipt.session_id, + "slot": receipt.slot, + } + if ( + set(payload) + != { + "algorithm", + "analyzer_source", + "density_source", + "evidence_sha256", + "fixed_composition", + "identity", + "native_per_acquisition_builder", + "pre_f_luts", + "schema", + "scope", + "version", + } + or payload.get("schema") != BUILDER_RECEIPT_SCHEMA + or type(payload.get("version")) is not int + or payload.get("version") != 1 + or payload.get("scope") != NATIVE_BUILDER_SCOPE + or payload.get("native_per_acquisition_builder") is not True + or payload.get("algorithm") != NATIVE_BUILDER_ALGORITHM_ID + or payload.get("density_source") != evidence.get("density_source") + or payload.get("analyzer_source") != evidence.get("analyzer_source") + or payload.get("identity") != identity + or payload.get("evidence_sha256") != receipt.evidence_sha256 + or payload.get("pre_f_luts") != expected_luts + or payload.get("fixed_composition") != {"lut_sha256": FIXED_COMPOSITION_SHA256, "order": "F[B_c(i)]"} + ): + raise ExactColorIntegrityError("native builder receipt envelope is malformed or incompletely bound") + if evidence.get("analyzer_source", {}).get("rgb_sha256") != receipt.analyzer_rgb_sha256: + raise ExactColorIntegrityError("native builder evidence does not bind its analyzer snapshot") + return payload + + +def _validate_native_evidence_document( + evidence: dict[str, Any], + receipt: NativeValidatedBuilderReceipt, +) -> None: + expected_identity = { + "batch_session_id": receipt.batch_session_id, + "capture_attempt_id": receipt.capture_attempt_id, + "preview_identity_sha256": receipt.preview_identity_sha256, + "preview_sha256": receipt.preview_sha256, + "reservation_id": receipt.reservation_id, + "scan_identity": receipt.scan_identity, + "session_id": receipt.session_id, + "slot": receipt.slot, + } + if set(evidence) != { + "algorithm", + "analyzer_source", + "calibration", + "density_evidence", + "density_source", + "frame_ownership", + "identity", + "resource", + "schema", + "version", + }: + raise ExactColorIntegrityError("native builder evidence fields are incomplete") + if ( + evidence.get("schema") != "negpy.native-stage1-builder-evidence" + or type(evidence.get("version")) is not int + or evidence.get("version") != 1 + or evidence.get("identity") != expected_identity + or evidence.get("resource") != {"bytes": 1_024, "sha256": NATIVE_RESOURCE_SHA256} + or evidence.get("algorithm") + != { + "curve": "ls5000-md3-10010c30-pref-v1", + "parameter_derivation": "ls5000-md3-100100d0-to-1000f470-v1", + } + ): + raise ExactColorIntegrityError("native builder evidence provenance is unsupported") + ownership = evidence.get("frame_ownership") + if type(ownership) is not dict or set(ownership) != { + "schema_version", + "scope", + "binding_status", + "session_reservation_retained", + "reservation_id", + "batch_session_id", + "preview_sha256", + "preview_identity_sha256", + "transport_table_sha256", + "transport_identity_sha256", + "reviewed_fingerprint_sha256", + "fresh_fingerprint_sha256", + "frame_capture_attempt_id", + "frame_index", + "frame_total", + "selected_slots", + "selected_slot", + }: + raise ExactColorIntegrityError("native frame-ownership evidence is malformed") + ownership_blob = _canonical_json(cast(dict[str, Any], ownership)) + if ( + ownership_blob != receipt.frame_ownership_receipt + or hashlib.sha256(ownership_blob).hexdigest() != receipt.frame_ownership_receipt_sha256 + or ownership.get("schema_version") != 1 + or ownership.get("scope") != "reservation-preview-frame" + or ownership.get("binding_status") != "proven-exact-reservation-preview-registration-and-transport" + or ownership.get("session_reservation_retained") is not True + or ownership.get("reservation_id") != receipt.reservation_id + or ownership.get("batch_session_id") != receipt.batch_session_id + or receipt.session_id != receipt.reservation_id + or receipt.reservation_id != receipt.batch_session_id + or ownership.get("preview_sha256") != receipt.preview_sha256 + or ownership.get("preview_identity_sha256") != receipt.preview_identity_sha256 + or ownership.get("frame_capture_attempt_id") != receipt.capture_attempt_id + or ownership.get("selected_slot") != receipt.slot + ): + raise ExactColorIntegrityError("native frame ownership belongs to another preview, reservation, or frame") + _validate_sha256(ownership.get("transport_table_sha256"), label="native transport table") + _validate_sha256(ownership.get("transport_identity_sha256"), label="native transport identity") + _validate_sha256(ownership.get("reviewed_fingerprint_sha256"), label="native reviewed fingerprint") + _validate_sha256(ownership.get("fresh_fingerprint_sha256"), label="native fresh fingerprint") + selected_slots = ownership.get("selected_slots") + frame_index = ownership.get("frame_index") + frame_total = ownership.get("frame_total") + if ( + type(selected_slots) is not list + or type(frame_index) is not int + or type(frame_total) is not int + or len(selected_slots) != frame_total + or not 1 <= frame_index <= frame_total + or selected_slots[frame_index - 1] != receipt.slot + ): + raise ExactColorIntegrityError("native frame ownership batch coordinates are malformed") + transport_material = { + "reservation_id": receipt.reservation_id, + "batch_session_id": receipt.batch_session_id, + "preview_sha256": receipt.preview_sha256, + "preview_identity_sha256": receipt.preview_identity_sha256, + "transport_table_sha256": ownership.get("transport_table_sha256"), + "reviewed_fingerprint_sha256": ownership.get("reviewed_fingerprint_sha256"), + "fresh_fingerprint_sha256": ownership.get("fresh_fingerprint_sha256"), + "selected_slots": selected_slots, + } + if hashlib.sha256(_canonical_json(transport_material)).hexdigest() != ownership.get("transport_identity_sha256"): + raise ExactColorIntegrityError("native frame ownership transport identity digest is invalid") + density_evidence = evidence.get("density_evidence") + density_evidence_document = _parse_json_object(receipt.density_evidence_receipt, label="native density evidence") + if ( + density_evidence != {"receipt_sha256": receipt.density_evidence_receipt_sha256, "scope": "reservation-preview"} + or set(density_evidence_document) + != { + "schema_version", + "scope", + "per_frame_binding_status", + "preview_identity_sha256", + "source_payload_bytes", + "calibration_binding", + "source_binding", + "exposure_binding", + "result", + } + or density_evidence_document.get("schema_version") != 1 + or density_evidence_document.get("scope") != "reservation-preview" + or density_evidence_document.get("per_frame_binding_status") != "requires-explicit-frame-ownership-receipt" + or density_evidence_document.get("source_payload_bytes") != 6_250_496 + or density_evidence_document.get("preview_identity_sha256") != receipt.preview_identity_sha256 + ): + raise ExactColorIntegrityError("native density evidence belongs to another preview or reservation") + calibration_binding = density_evidence_document.get("calibration_binding") + source_binding = density_evidence_document.get("source_binding") + exposure_binding = density_evidence_document.get("exposure_binding") + density_result = density_evidence_document.get("result") + if not all(type(value) is dict for value in (calibration_binding, source_binding, exposure_binding, density_result)): + raise ExactColorIntegrityError("native density evidence bindings are malformed") + calibration_binding = cast(dict[str, Any], calibration_binding) + source_binding = cast(dict[str, Any], source_binding) + exposure_binding = cast(dict[str, Any], exposure_binding) + density_result = cast(dict[str, Any], density_result) + density_calibration = calibration_binding.get("calibration") + if type(density_calibration) is not dict: + raise ExactColorIntegrityError("native density calibration evidence is malformed") + binding_identity = ( + source_binding.get("session_id"), + source_binding.get("capture_attempt_id"), + source_binding.get("scan_identity"), + ) + if ( + binding_identity + != ( + exposure_binding.get("session_id"), + exposure_binding.get("capture_attempt_id"), + exposure_binding.get("scan_identity"), + ) + or binding_identity + != ( + density_result.get("session_id"), + density_result.get("capture_attempt_id"), + density_result.get("scan_identity"), + ) + or binding_identity + != ( + density_calibration.get("session_id"), + calibration_binding.get("capture_attempt_id"), + calibration_binding.get("scan_identity"), + ) + or binding_identity[0] != receipt.reservation_id + or binding_identity[2] != receipt.scan_identity + or source_binding.get("wire_sha256") != receipt.preview_sha256 + or density_result.get("source_wire_sha256") != receipt.preview_sha256 + or density_result.get("promotable") is not True + ): + raise ExactColorIntegrityError("native density evidence identity is inconsistent") + calibration = evidence.get("calibration") + if type(calibration) is not dict or set(calibration) != {"read8c_numerators_rgb"}: + raise ExactColorIntegrityError("native calibration evidence is malformed") + numerators = calibration.get("read8c_numerators_rgb") + density = evidence.get("density_source") + analyzer = evidence.get("analyzer_source") + if type(density) is not dict or set(density) != { + "arithmetic", + "child_buffer_sha256", + "densities_binary64_hex_rgb", + "f03_denominators_raw_10ns_rgb", + "resolution_dpi", + "wire_sha256", + }: + raise ExactColorIntegrityError("native density-source evidence is malformed") + if type(analyzer) is not dict or set(analyzer) != { + "final_f02_denominators_raw_10ns_rgb", + "geometry", + "rectangle_inclusive", + "resolution_dpi", + "rgb_bytes", + "rgb_sha256", + }: + raise ExactColorIntegrityError("native analyzer-source evidence is malformed") + density_denominators = density.get("f03_denominators_raw_10ns_rgb") + final_denominators = analyzer.get("final_f02_denominators_raw_10ns_rgb") + for label, values in ( + ("calibration numerators", numerators), + ("density f03 denominators", density_denominators), + ("final f02 denominators", final_denominators), + ): + if type(values) is not list or len(values) != 3 or any(type(value) is not int or not 1 <= value <= 0xFFFFFFFF for value in values): + raise ExactColorIntegrityError(f"native {label} are malformed") + if density_denominators == final_denominators: + raise ExactColorIntegrityError("native density f03 and final f02 exposure triplets were conflated") + density_hex = density.get("densities_binary64_hex_rgb") + try: + decoded_density = [float.fromhex(value) for value in density_hex] if type(density_hex) is list else [] + except (TypeError, ValueError) as error: + raise ExactColorIntegrityError("native density doubles are malformed") from error + if len(decoded_density) != 3 or any(not math.isfinite(value) for value in decoded_density): + raise ExactColorIntegrityError("native density doubles are malformed") + density_binary64 = [struct.pack(">d", value).hex() for value in decoded_density] + if ( + source_binding.get("resolution_dpi") != 97 + or source_binding.get("wire_sha256") != density.get("wire_sha256") + or source_binding.get("child_buffer_sha256") != density.get("child_buffer_sha256") + or density_calibration.get("numerators_rgb") != numerators + or exposure_binding.get("density_f03_exposures_raw_10ns_rgb") != density_denominators + or density_result.get("algorithm_id") != density.get("arithmetic") + or density_result.get("source_wire_sha256") != density.get("wire_sha256") + or density_result.get("source_child_buffer_sha256") != density.get("child_buffer_sha256") + or density_result.get("numerators_rgb") != numerators + or density_result.get("density_f03_denominators_raw_10ns_rgb") != density_denominators + or density_result.get("densities_rgb") != decoded_density + or density_result.get("density_binary64_be_hex_rgb") != density_binary64 + ): + raise ExactColorIntegrityError("native density receipt does not bind the builder inputs") + _validate_sha256(density.get("wire_sha256"), label="native density source wire") + _validate_sha256(density.get("child_buffer_sha256"), label="native density source child") + rectangle = analyzer.get("rectangle_inclusive") + if ( + density.get("arithmetic") != "ls5000-md3-10088810-layout1-u16-proven-inputs-macos-binary64-exact-v6" + or density.get("resolution_dpi") != 97 + or analyzer.get("resolution_dpi") != 285 + or analyzer.get("geometry") != [425, 281, 3] + or analyzer.get("rgb_bytes") != 425 * 281 * 3 * 2 + or type(rectangle) is not list + or len(rectangle) != 4 + or any(type(value) is not int for value in rectangle) + or not (0 <= rectangle[0] <= rectangle[2] < 425 and 0 <= rectangle[1] <= rectangle[3] < 281) + ): + raise ExactColorIntegrityError("native density/analyzer geometry or arithmetic is unsupported") + _validate_sha256(analyzer.get("rgb_sha256"), label="native analyzer RGB") + + +def _validate_builder_application_receipt( + payload: dict[str, Any], + receipt: BuilderReceipt, +) -> None: + common_ok = ( + payload.get("kind") == "negpy.verified-stage1-builder-application" + and type(payload.get("version")) is int + and payload.get("version") == 1 + and payload.get("fixed_composition") == {"lut_sha256": FIXED_COMPOSITION_SHA256, "order": "F[B_c(i)]"} + and payload.get("pre_f_lut_sha256") == dict(zip(_BUILDER_CHANNELS, receipt.pre_f_lut_sha256, strict=True)) + ) + if isinstance(receipt, ValidatedBuilderReceipt): + specific_ok = ( + payload.get("scope") == STAGE3_REPLAY_SCOPE + and payload.get("native_per_acquisition_builder") is False + and payload.get("stage3_receipt_sha256") == receipt.stage3_receipt_sha256 + ) + label = "validated Stage-3 artifacts" + elif isinstance(receipt, NativeValidatedBuilderReceipt): + specific_ok = ( + payload.get("scope") == NATIVE_BUILDER_SCOPE + and payload.get("native_per_acquisition_builder") is True + and payload.get("native_evidence_sha256") == receipt.evidence_sha256 + ) + label = "validated native builder artifacts" + else: # pragma: no cover - BuilderReceipt is a closed union + raise ExactColorIntegrityError("builder receipt has an invalid type") + if not common_ok or not specific_ok: + raise ExactColorIntegrityError(f"builder application receipt does not bind the {label}") + + +def _receipt_payload( + receipt: object, + expected_type: ( + type[ValidatedBuilderReceipt] + | type[NativeValidatedBuilderReceipt] + | type[VerifiedBuilderApplicationReceipt] + | type[VerifiedCMSReceipt] + ), + *, + label: str, +) -> dict[str, Any]: + if ( + not isinstance( + receipt, + ( + ValidatedBuilderReceipt, + NativeValidatedBuilderReceipt, + VerifiedBuilderApplicationReceipt, + VerifiedCMSReceipt, + ), + ) + or type(receipt) is not expected_type + ): + raise ExactColorIntegrityError(f"{label} receipt has an invalid type") + payload = receipt.payload + if type(payload) is not bytes or not payload or len(payload) > _MAX_RECEIPT_BYTES: + raise ExactColorIntegrityError(f"{label} receipt payload is missing or unbounded") + if type(receipt) is VerifiedCMSReceipt and receipt._factory_token is not _TRUSTED_CMS_RECEIPT_TOKEN: + raise ExactColorIntegrityError("CMS receipt was not issued by the trusted portable CMS adapter") + if receipt.attested is not True: + raise ExactColorIntegrityError(f"{label} receipt is not attested") + if type(receipt.sha256) is not str or not _SHA256.fullmatch(receipt.sha256): + raise ExactColorIntegrityError(f"{label} receipt SHA-256 is malformed") + if not hmac.compare_digest(hashlib.sha256(payload).hexdigest(), receipt.sha256): + raise ExactColorIntegrityError(f"{label} receipt payload does not match its SHA-256") + return _parse_json_object(payload, label=f"{label} receipt") + + +def _parse_json_object(payload: bytes, *, label: str) -> dict[str, Any]: + if type(payload) is not bytes or not payload or len(payload) > _MAX_RECEIPT_BYTES: + raise ExactColorIntegrityError(f"{label} payload is missing or unbounded") + try: + parsed = json.loads( + payload, + object_pairs_hook=_reject_duplicate_keys, + parse_constant=_reject_json_constant, + ) + except (UnicodeDecodeError, ValueError, RecursionError) as error: + raise ExactColorIntegrityError(f"{label} is not valid JSON: {error}") from error + if type(parsed) is not dict: + raise ExactColorIntegrityError(f"{label} must contain a JSON object") + return parsed + + +def _validate_sha256(value: object, *, label: str) -> None: + if type(value) is not str or not _SHA256.fullmatch(value): + raise ExactColorIntegrityError(f"{label} SHA-256 is malformed") + + +def _validate_pre_f_artifacts(receipt: BuilderReceipt) -> None: + if type(receipt.pre_f_luts) is not tuple or len(receipt.pre_f_luts) != 3: + raise ExactColorIntegrityError("builder receipt must contain exactly three pre-F LUT byte blobs") + if type(receipt.pre_f_lut_sha256) is not tuple or len(receipt.pre_f_lut_sha256) != 3: + raise ExactColorIntegrityError("builder receipt must contain exactly three pre-F LUT hashes") + for channel, blob, expected in zip( + _BUILDER_CHANNELS, + receipt.pre_f_luts, + receipt.pre_f_lut_sha256, + strict=True, + ): + if type(blob) is not bytes or len(blob) != _PRE_F_LUT_BYTES: + raise ExactColorIntegrityError(f"builder pre-F {channel} LUT must be exactly {_PRE_F_LUT_BYTES} bytes") + _validate_sha256(expected, label=f"builder pre-F {channel} LUT") + if not hmac.compare_digest(hashlib.sha256(blob).hexdigest(), expected): + raise ExactColorIntegrityError(f"builder pre-F {channel} LUT does not match its SHA-256") + _validate_sha256(receipt.fixed_composition_sha256, label="fixed composition LUT") + if receipt.fixed_composition_sha256 != FIXED_COMPOSITION_SHA256: + raise ExactColorIntegrityError("builder receipt names an unsupported fixed composition LUT") + + +def _validate_stage3_report( + report: dict[str, Any], + pre_f_lut_sha256: tuple[str, str, str], +) -> None: + if ( + report.get("schema") != STAGE3_REPORT_SCHEMA + or type(report.get("schema_version")) is not int + or report.get("schema_version") != 1 + or report.get("status") != "pass" + ): + raise ExactColorIntegrityError("Stage-3 validation report is not a supported PASS receipt") + if report.get("errors") != []: + raise ExactColorIntegrityError("Stage-3 validation report errors must be exactly empty") + if type(report.get("capture_directory")) is not str or not report["capture_directory"]: + raise ExactColorIntegrityError("Stage-3 validation report capture directory is malformed") + required_summary = { + "callback_analyzer_exact": True, + "builder_scalars_exact": 21, + "builder_scalars_total": 21, + "builder_controls_exact": 6, + "builder_controls_total": 6, + "builder_pref_channels_exact": 3, + "builder_pref_channels_total": 3, + "builder_pref_mismatched_u16": 0, + "builder_pref_total_u16": 196_608, + "captured_args_replay_channels_exact": 3, + "lifecycle_exact": True, + } + summary = report.get("summary") + if ( + type(summary) is not dict + or summary != required_summary + or any(type(summary[key]) is not type(value) for key, value in required_summary.items()) + ): + raise ExactColorIntegrityError("Stage-3 validation report does not close every builder gate") + + provenance = report.get("provenance") + if type(provenance) is not dict or set(provenance) != { + "module", + "observer_executable", + "observer_source", + "resource", + }: + raise ExactColorIntegrityError("Stage-3 validation report provenance fields are incomplete") + _validate_observer_provenance(provenance.get("observer_source"), label="observer source") + _validate_observer_provenance(provenance.get("observer_executable"), label="observer executable") + module = provenance.get("module") + if ( + type(module) is not dict + or set(module) != {"path", "bytes", "sha256"} + or type(module.get("path")) is not str + or not module["path"] + or type(module.get("bytes")) is not int + or module.get("bytes") != _STAGE3_MODULE_BYTES + or module.get("sha256") != _STAGE3_MODULE_SHA256 + ): + raise ExactColorIntegrityError("Stage-3 module provenance is not the pinned LS5000.md3") + resource = provenance.get("resource") + if ( + type(resource) is not dict + or resource + != { + "bytes": _STAGE3_RESOURCE_BYTES, + "sha256": _STAGE3_RESOURCE_SHA256, + "virtual_address": _STAGE3_RESOURCE_VA, + } + or type(resource.get("bytes")) is not int + ): + raise ExactColorIntegrityError("Stage-3 resource provenance is not the pinned LS5000.md3 block") + + artifacts = report.get("artifacts") + if type(artifacts) is not list: + raise ExactColorIntegrityError("Stage-3 validation report has no artifact inventory") + inventory: dict[str, dict[str, Any]] = {} + for row in artifacts: + if type(row) is not dict or set(row) != {"bytes", "name", "sha256"} or type(row.get("name")) is not str: + raise ExactColorIntegrityError("Stage-3 artifact inventory is malformed") + name = row["name"] + if name in inventory: + raise ExactColorIntegrityError(f"Stage-3 artifact inventory repeats {name!r}") + if type(row.get("bytes")) is not int or row["bytes"] <= 0: + raise ExactColorIntegrityError(f"Stage-3 artifact inventory has an invalid byte size for {name!r}") + _validate_sha256(row.get("sha256"), label=f"Stage-3 artifact {name}") + inventory[name] = row + if set(inventory) != _STAGE3_ARTIFACTS: + raise ExactColorIntegrityError("Stage-3 artifact inventory does not contain the exact required file set") + for name, expected_bytes in _STAGE3_FIXED_ARTIFACT_BYTES.items(): + if inventory[name]["bytes"] != expected_bytes: + raise ExactColorIntegrityError(f"Stage-3 artifact inventory has the wrong size for {name}") + if inventory["callback-buffer.bin"]["bytes"] not in _STAGE3_CALLBACK_BYTES: + raise ExactColorIntegrityError("Stage-3 callback artifact has unsupported geometry") + for channel, expected in zip(_BUILDER_CHANNELS, pre_f_lut_sha256, strict=True): + if inventory.get(f"builder-preF-{channel}.bin") != { + "bytes": _PRE_F_LUT_BYTES, + "name": f"builder-preF-{channel}.bin", + "sha256": expected, + }: + raise ExactColorIntegrityError(f"Stage-3 validation report does not bind builder-preF-{channel}.bin") + + +def _validate_observer_provenance(value: object, *, label: str) -> None: + if type(value) is not dict: + raise ExactColorIntegrityError(f"Stage-3 {label} provenance is malformed") + row = cast(dict[str, Any], value) + if ( + set(row) != {"path", "bytes", "sha256"} + or type(row.get("path")) is not str + or not row["path"] + or type(row.get("bytes")) is not int + or row["bytes"] <= 0 + ): + raise ExactColorIntegrityError(f"Stage-3 {label} provenance is malformed") + _validate_sha256(row.get("sha256"), label=f"Stage-3 {label}") + + +def _validate_cms_receipt( + payload: dict[str, Any], + *, + builder_receipt_sha256: str, + input_rgb_sha256: str, + output_rgb_sha256: str, +) -> None: + required_keys = { + "algorithm", + "assets", + "builder_receipt_sha256", + "chunk_pixels", + "dll_free", + "input_rgb_sha256", + "kind", + "oracle_source", + "output_rgb_sha256", + "scope", + "stage_order", + "upstream_builder_included", + "validation", + "version", + } + if set(payload) != required_keys: + raise ExactColorIntegrityError("CMS receipt does not contain the exact production contract") + if ( + payload.get("kind") != CMS_RECEIPT_KIND + or type(payload.get("version")) is not int + or payload.get("version") != CMS_RECEIPT_VERSION + or payload.get("algorithm") != CMS_ALGORITHM_ID + ): + raise ExactColorIntegrityError("CMS receipt kind, version, or algorithm is unsupported") + if payload.get("assets") != dict(CMS_ASSET_SHA256): + raise ExactColorIntegrityError("CMS receipt does not bind the exact nine transform assets") + if payload.get("oracle_source") != { + "path": "portable_oracle_evaluator.py", + "sha256": CMS_ORACLE_SOURCE_SHA256, + }: + raise ExactColorIntegrityError("CMS receipt does not bind the verified oracle source") + validation = payload.get("validation") + expected_validation = { + "events": 12, + "full_payload_mismatched_bytes": 0, + "full_payload_total_bytes": 698_880, + "mismatched_u16": 0, + "receipt_sha256": CMS_VALIDATION_RECEIPT_SHA256, + "total_u16": 265_440, + } + if ( + type(validation) is not dict + or validation != expected_validation + or any(type(validation[key]) is not type(value) for key, value in expected_validation.items()) + ): + raise ExactColorIntegrityError("CMS receipt does not bind the zero-mismatch 12-event validation") + if ( + payload.get("scope") != CMS_SCOPE + or payload.get("stage_order") != ["stage1", "stage2"] + or payload.get("dll_free") is not True + or payload.get("upstream_builder_included") is not False + ): + raise ExactColorIntegrityError("CMS receipt scope or stage contract is unsupported") + chunk_pixels = payload.get("chunk_pixels") + if type(chunk_pixels) is not int or not 1 <= chunk_pixels <= 262_144: + raise ExactColorIntegrityError("CMS receipt chunk size is invalid") + if ( + payload.get("builder_receipt_sha256") != builder_receipt_sha256 + or payload.get("input_rgb_sha256") != input_rgb_sha256 + or payload.get("output_rgb_sha256") != output_rgb_sha256 + ): + raise ExactColorIntegrityError("CMS receipt does not bind its builder, input, and output") + + +def _stable_read_non_symlink( + path: Path, + *, + label: str, + expected_bytes: int | None = None, + max_bytes: int | None = None, +) -> tuple[bytes, tuple[int, int, int, int, int]]: + byte_limit = expected_bytes if expected_bytes is not None else max_bytes + if byte_limit is None: + raise ExactColorIntegrityError(f"{label} read has no byte bound") + descriptor: int | None = None + try: + before = path.lstat() + if stat.S_ISLNK(before.st_mode) or not stat.S_ISREG(before.st_mode): + raise OSError("not a regular non-symlink file") + if expected_bytes is not None and before.st_size != expected_bytes: + raise ExactColorIntegrityError(f"{label} must be exactly {expected_bytes} bytes") + if max_bytes is not None and before.st_size > max_bytes: + raise ExactColorIntegrityError(f"{label} exceeds the bounded receipt size") + flags = ( + os.O_RDONLY + | getattr(os, "O_CLOEXEC", 0) + | getattr(os, "O_NOFOLLOW", 0) + | getattr(os, "O_NONBLOCK", 0) + ) + descriptor = os.open(path, flags) + opened = os.fstat(descriptor) + chunks: list[bytes] = [] + total = 0 + while total <= byte_limit: + chunk = os.read( + descriptor, + min(1024 * 1024, byte_limit + 1 - total), + ) + if not chunk: + break + chunks.append(chunk) + total += len(chunk) + if total > byte_limit: + raise ExactColorIntegrityError( + f"{label} exceeds its bounded byte length" + ) + after = os.fstat(descriptor) + final = path.lstat() + except ExactColorIntegrityError: + raise + except OSError as error: + raise ExactColorUnavailable(f"{label} is unavailable: {path}: {error}") from error + finally: + if descriptor is not None: + os.close(descriptor) + identities = tuple(_file_identity(item) for item in (before, opened, after, final)) + if len(set(identities)) != 1: + raise ExactColorIntegrityError(f"{label} changed while being read: {path}") + payload = b"".join(chunks) + if len(payload) != before.st_size: + raise ExactColorIntegrityError(f"{label} changed byte length while being read: {path}") + return payload, identities[0] + + +def _assert_paths_unchanged( + paths: tuple[Path, ...], + identities: tuple[tuple[int, int, int, int, int], ...], + *, + label: str = "Stage-3 replay evidence", +) -> None: + for path, expected in zip(paths, identities, strict=True): + try: + current = path.lstat() + except OSError as error: + raise ExactColorUnavailable( + f"{label} disappeared: {path}: {error}" + ) from error + if stat.S_ISLNK(current.st_mode) or not stat.S_ISREG(current.st_mode) or _file_identity(current) != expected: + raise ExactColorIntegrityError( + f"{label} changed during snapshot: {path}" + ) + + +def _file_identity(value: os.stat_result) -> tuple[int, int, int, int, int]: + return ( + value.st_dev, + value.st_ino, + value.st_size, + value.st_mtime_ns, + value.st_ctime_ns, + ) + + +def _canonical_json(value: dict[str, Any]) -> bytes: + return json.dumps( + value, + sort_keys=True, + separators=(",", ":"), + ensure_ascii=False, + allow_nan=False, + ).encode("utf-8") + + +def _reject_duplicate_keys(pairs: list[tuple[str, Any]]) -> dict[str, Any]: + parsed: dict[str, Any] = {} + for key, value in pairs: + if key in parsed: + raise ValueError(f"duplicate receipt key {key!r}") + parsed[key] = value + return parsed + + +def _reject_json_constant(value: str) -> None: + raise ValueError(f"non-standard JSON constant {value!r}") + + +def _validate_rgb16(rgb: object, *, label: str) -> None: + if ( + not isinstance(rgb, np.ndarray) + or rgb.dtype != np.uint16 + or rgb.ndim != 3 + or rgb.shape[2] != 3 + or rgb.shape[0] == 0 + or rgb.shape[1] == 0 + ): + raise ExactColorIntegrityError(f"{label} must be a non-empty HxWx3 uint16 array") + + +__all__ = [ + "BUILDER_RECEIPT_SCHEMA", + "BuilderReceipt", + "CMS_ALGORITHM_ID", + "CMS_ASSET_SHA256", + "CMS_ORACLE_SOURCE_SHA256", + "CMS_RECEIPT_KIND", + "CMS_RECEIPT_VERSION", + "CMS_SCOPE", + "CMS_VALIDATION_RECEIPT_SHA256", + "ExactColorIntegrityError", + "ExactColorResult", + "ExactColorUnavailable", + "FIXED_COMPOSITION_SHA256", + "NATIVE_BUILDER_ALGORITHM_ID", + "NATIVE_BUILDER_SCOPE", + "NATIVE_RESOURCE_SHA256", + "NativeValidatedBuilderReceipt", + "PositiveColorMode", + "STAGE3_REPORT_SCHEMA", + "STAGE3_REPLAY_SCOPE", + "Stage1BuilderResult", + "ValidatedBuilderReceipt", + "VerifiedBuilderApplicationReceipt", + "VerifiedCMSReceipt", + "VerifiedPortableCMSEvaluator", + "VerifiedStage1Builder", + "builder_receipt_payload", + "evaluate_exact_color", + "load_native_builder_receipt", + "load_stage3_replay_builder_receipt", + "receipt_payload", + "rgb16_content_sha256", +] diff --git a/negpy/services/roll/live_acceptance.py b/negpy/services/roll/live_acceptance.py new file mode 100644 index 00000000..deb9c1fd --- /dev/null +++ b/negpy/services/roll/live_acceptance.py @@ -0,0 +1,1015 @@ +"""One-shot, fail-closed LS-5000 six-frame live acceptance runner.""" + +from __future__ import annotations + +import argparse +import dataclasses +import hashlib +import hmac +import json +import math +import os +import re +import stat +import uuid +from collections.abc import Callable +from datetime import UTC, datetime +from pathlib import Path +from typing import Any + +from negpy.infrastructure.roll.repair import RepairMode +from negpy.services.repair.hybrid_runtime_manifest import ( + load_default_hybrid_runtime_manifest, +) +from negpy.services.repair.fauxice_hybrid_runner import HybridRuntimeConfig +from negpy.services.roll.exact_color import PositiveColorMode +from negpy.services.roll.deep_acceptance import ( + collect_completed_frame_files, + validate_six_frame_batch, +) +from negpy.services.roll.live_reservation import ( + OUTPUT_LOCK_NAME, + ExclusiveReceiptReservation, + FixedOutputLease, + ReservationConflict, +) +from negpy.services.roll.live_review import ( + ValidatedReviewedApproval, + approval_payload, + load_reviewed_approval, + validate_restored_thumbnails, +) +from negpy.services.roll.service import RollFrameOutput, RollScanningService + + +SCHEMA = "negpy.ls5000-live-acceptance.v2" +SLOTS = (1, 2, 3, 4, 5, 6) +APPROVED_SLOTS = (1, 6) +EXPECTED_DEVICE_MODEL = "Nikon LS-5000 ED 1.03" +FILENAME_PATTERN = 'acceptance_slot{{ "%02d" % seq }}' +_SESSION_MAX_BYTES = 64 * 1024 +_FRAME_RECEIPT_MAX_BYTES = 16 * 1024 * 1024 +_SHA256_RE = re.compile(r"[0-9a-f]{64}") +_MAX_TELEMETRY_ERRORS = 16 + + +class LiveAcceptanceError(RuntimeError): + """The one-shot live acceptance run stopped without passing.""" + + def __init__(self, message: str, *, receipt: dict[str, Any]) -> None: + self.receipt = receipt + super().__init__(message) + + +@dataclasses.dataclass(frozen=True) +class LiveAcceptanceRequest: + device_id: str + preview_session_path: Path + preview_session_sha256: str + reviewed_approval_path: Path + reviewed_approval_sha256: str + output_dir: Path + run_receipt_path: Path + confirm_live: bool + hybrid_runtime_manifest_path: Path | None = None + + +@dataclasses.dataclass(frozen=True) +class _SessionPayload: + text: str + sha256: str + size: int + + +def _utc_now() -> str: + return datetime.now(UTC).isoformat() + + +def _json_event(event: dict[str, Any]) -> None: + print( + json.dumps( + event, + sort_keys=True, + separators=(",", ":"), + ensure_ascii=True, + allow_nan=False, + ), + flush=True, + ) + + +def _read_stable_regular_file( + path: Path, + *, + maximum_bytes: int, + label: str, +) -> bytes: + descriptor: int | None = None + try: + linked = path.lstat() + if stat.S_ISLNK(linked.st_mode) or not stat.S_ISREG(linked.st_mode): + raise ValueError(f"{label} must be a regular non-symlink file") + if linked.st_size > maximum_bytes: + raise ValueError(f"{label} exceeds its safe size limit") + flags = os.O_RDONLY | getattr(os, "O_CLOEXEC", 0) | getattr(os, "O_NOFOLLOW", 0) | getattr(os, "O_NONBLOCK", 0) + descriptor = os.open(path, flags) + with os.fdopen(descriptor, "rb", closefd=True) as handle: + descriptor = None + opened = os.fstat(handle.fileno()) + if not stat.S_ISREG(opened.st_mode) or (opened.st_dev, opened.st_ino, opened.st_size) != ( + linked.st_dev, + linked.st_ino, + linked.st_size, + ): + raise ValueError(f"{label} changed while opening") + payload = handle.read(maximum_bytes + 1) + after = os.fstat(handle.fileno()) + final = path.lstat() + + def identity(item: os.stat_result) -> tuple[int, int, int, int, int]: + return ( + item.st_dev, + item.st_ino, + item.st_size, + item.st_mtime_ns, + item.st_ctime_ns, + ) + + if ( + len(payload) != opened.st_size + or len(payload) > maximum_bytes + or identity(opened) != identity(after) + or identity(opened) != identity(final) + ): + raise ValueError(f"{label} changed while reading") + return payload + except FileNotFoundError as error: + raise ValueError(f"{label} is missing: {path}") from error + except OSError as error: + raise ValueError(f"could not read {label}: {error}") from error + finally: + if descriptor is not None: + os.close(descriptor) + + +def _load_session(path: Path, expected_sha256: str) -> _SessionPayload: + if _SHA256_RE.fullmatch(expected_sha256) is None: + raise ValueError("preview-session pin must be a lowercase SHA-256 digest") + payload = _read_stable_regular_file( + path, + maximum_bytes=_SESSION_MAX_BYTES, + label="preview session", + ) + actual_sha256 = hashlib.sha256(payload).hexdigest() + if not hmac.compare_digest(actual_sha256, expected_sha256): + raise ValueError(f"preview-session SHA-256 mismatch: expected {expected_sha256}, got {actual_sha256}") + try: + text = payload.decode("utf-8") + except UnicodeDecodeError as error: + raise ValueError("preview session is not UTF-8") from error + return _SessionPayload(text=text, sha256=actual_sha256, size=len(payload)) + + +def _default_review_loader( + request: LiveAcceptanceRequest, + session: _SessionPayload, +) -> ValidatedReviewedApproval: + return load_reviewed_approval( + request.reviewed_approval_path, + request.reviewed_approval_sha256, + preview_session_path=request.preview_session_path, + preview_session_payload=session.text, + preview_session_sha256=session.sha256, + ) + + +def _preflight_paths(request: LiveAcceptanceRequest) -> tuple[Path, Path]: + output = request.output_dir.resolve(strict=True) + linked_output = request.output_dir.lstat() + if stat.S_ISLNK(linked_output.st_mode) or not stat.S_ISDIR(linked_output.st_mode): + raise ValueError("output directory must be an existing non-symlink directory") + receipt = request.run_receipt_path.absolute() + receipt_parent = receipt.parent.resolve(strict=True) + if not receipt_parent.is_dir(): + raise ValueError("run-receipt parent must be a directory") + receipt = receipt_parent / receipt.name + if receipt == output or receipt.is_relative_to(output): + raise ValueError("run receipt must be outside the output directory") + return output, receipt + + +def _require_artifact(path_value: str | None, *, output_dir: Path, label: str) -> str: + if not path_value: + raise ValueError(f"{label} was not produced") + path = Path(path_value) + linked = path.lstat() + if stat.S_ISLNK(linked.st_mode) or not stat.S_ISREG(linked.st_mode): + raise ValueError(f"{label} must be a regular non-symlink file") + resolved = path.resolve(strict=True) + if not resolved.is_relative_to(output_dir): + raise ValueError(f"{label} escaped the output directory") + return str(resolved) + + +def _require_true(document: dict[str, Any], key: str, *, label: str) -> None: + if document.get(key) is not True: + raise ValueError(f"{label} is not verified") + + +def _validate_frame_output( + output: RollFrameOutput, + *, + expected_slot: int, + output_dir: Path, +) -> dict[str, Any]: + if output.slot != expected_slot: + raise ValueError(f"writer returned slot {output.slot}; expected {expected_slot}") + artifacts = { + field: _require_artifact( + getattr(output, field), + output_dir=output_dir, + label=f"slot {expected_slot} {field}", + ) + for field in ( + "rgb_path", + "ir_path", + "repaired_rgb_path", + "repaired_ir_path", + "positive_path", + "receipt_path", + "synthesis_mask_path", + "native_synthesis_mask_path", + "hybrid_receipt_path", + ) + } + receipt_path = Path(artifacts["receipt_path"]) + receipt_bytes = _read_stable_regular_file( + receipt_path, + maximum_bytes=_FRAME_RECEIPT_MAX_BYTES, + label=f"slot {expected_slot} frame receipt", + ) + try: + frame_receipt = json.loads(receipt_bytes) + except (UnicodeDecodeError, json.JSONDecodeError) as error: + raise ValueError(f"slot {expected_slot} frame receipt is invalid JSON") from error + if not isinstance(frame_receipt, dict) or frame_receipt.get("slot") != expected_slot: + raise ValueError(f"slot {expected_slot} frame receipt identity is wrong") + outputs = frame_receipt.get("outputs") + if not isinstance(outputs, dict): + raise ValueError(f"slot {expected_slot} frame receipt has no outputs object") + for tier in ("unrepaired", "repaired", "positive"): + entry = outputs.get(tier) + if not isinstance(entry, dict) or entry.get("written") is not True: + raise ValueError(f"slot {expected_slot} {tier} tier was not written") + repaired = outputs["repaired"] + if ( + repaired.get("mode_requested") != RepairMode.HYBRID.value + or repaired.get("mode_resolved") != RepairMode.HYBRID.value + or repaired.get("degraded") is not False + ): + raise ValueError(f"slot {expected_slot} did not complete non-degraded Hybrid repair") + positive = outputs["positive"] + if positive.get("color_mode") != PositiveColorMode.NIKON_EXACT.value: + raise ValueError(f"slot {expected_slot} positive is not Nikon exact") + for key in ( + "exact_nikon_color", + "native_per_acquisition_builder", + "builder_validated", + "cms_verified", + ): + _require_true(positive, key, label=f"slot {expected_slot} positive {key}") + for evidence_key, truth_keys in ( + ("repair_acquisition_evidence", ("retained", "replayable")), + ("native_color_evidence", ("retained",)), + ): + evidence = outputs.get(evidence_key) + if not isinstance(evidence, dict): + raise ValueError(f"slot {expected_slot} has no {evidence_key}") + for key in truth_keys: + _require_true( + evidence, + key, + label=f"slot {expected_slot} {evidence_key} {key}", + ) + return { + "slot": expected_slot, + "artifacts": artifacts, + "frame_receipt_sha256": hashlib.sha256(receipt_bytes).hexdigest(), + } + + +def _event_value(value: object) -> str | int | float | bool | None: + if value is None: + return None + if isinstance(value, (str, int, bool)): + return value + if isinstance(value, float): + return value if math.isfinite(value) else repr(value) + try: + return repr(value)[:512] + except Exception: + try: + name = type(value).__name__ + except Exception: + name = "object" + return f"" + + +def _progress_event(progress: object) -> dict[str, Any]: + event: dict[str, Any] = {"event": "scan_progress"} + for field in ("stage", "slot", "index", "total", "fraction", "message"): + value = getattr(progress, field, None) + if value is not None: + event[field] = _event_value(value) + return event + + +def _error_payload(error: BaseException) -> dict[str, str]: + try: + message = str(error)[:4096] + except Exception: + message = "" + return { + "type": type(error).__name__, + "message": message, + } + + +def _combine_error( + current: BaseException | None, + later: BaseException, + *, + label: str, +) -> BaseException: + if current is None: + return later + current_payload = _error_payload(current) + later_payload = _error_payload(later) + return RuntimeError( + f"{current_payload['type']}: {current_payload['message']}; {label}: {later_payload['type']}: {later_payload['message']}" + ) + + +def _default_service_factory( + *, + hybrid_runtime: HybridRuntimeConfig, +) -> RollScanningService: + return RollScanningService(hybrid_runtime=hybrid_runtime) + + +def run_live_acceptance( + request: LiveAcceptanceRequest, + *, + service_factory: Callable[..., Any] = _default_service_factory, + hybrid_runtime_loader: Callable[[Path | None], Any] = (load_default_hybrid_runtime_manifest), + review_loader: Callable[[LiveAcceptanceRequest, _SessionPayload], ValidatedReviewedApproval] = _default_review_loader, + frame_inventory_collector: Callable[..., list[str]] = (collect_completed_frame_files), + batch_validator: Callable[..., dict[str, Any]] = validate_six_frame_batch, + receipt_reserver: Callable[..., Any] = ExclusiveReceiptReservation.reserve, + output_lease_factory: Callable[..., Any] = FixedOutputLease.acquire, + emit: Callable[[dict[str, Any]], None] = _json_event, +) -> dict[str, Any]: + """Run one exact six-slot batch; any discrepancy ends it without retry.""" + + started_at = _utc_now() + run_id = uuid.uuid4().hex + phase = "starting" + frames: list[dict[str, Any]] = [] + outputs: list[RollFrameOutput] = [] + restored_slots: list[object] = [] + required_approval_slots: list[int] = [] + approved_slots: list[int] = [] + yielded_slots: list[object] = [] + committed_slots: list[object] = [] + verified_slots: list[int] = [] + telemetry_errors: list[dict[str, str]] = [] + telemetry_error_count = 0 + last_progress: dict[str, Any] | None = None + processing_slot: int | None = None + batch_prepared = False + batch_exhausted = False + transport_may_have_advanced_beyond_yielded = False + + session: _SessionPayload | None = None + review: ValidatedReviewedApproval | None = None + runtime: Any = None + deep_batch: dict[str, Any] | None = None + output_dir: Path | None = None + receipt_path = request.run_receipt_path.absolute() + receipt_reservation: Any = None + receipt_inode: tuple[int, int] | None = None + receipt_reserved = False + output_lease: Any = None + lease_acquired = False + lease_release_attempted = False + lease_released = False + inventory_snapshot: Any = None + owned_files: set[Path] = set() + service: Any = None + service_constructed = False + roll_opened = False + iterator: Any = None + iterator_created = False + iterator_close_attempted = False + iterator_close_succeeded = False + close_attempted = False + close_succeeded = False + operation_error: BaseException | None = None + + def record_telemetry_error(event_name: str, error: BaseException) -> None: + nonlocal telemetry_error_count + telemetry_error_count += 1 + if len(telemetry_errors) < _MAX_TELEMETRY_ERRORS: + telemetry_errors.append( + { + "event": event_name[:128], + **_error_payload(error), + } + ) + + def safe_emit(event: dict[str, Any]) -> None: + try: + emit(event) + except Exception as error: + record_telemetry_error( + str(event.get("event", "unknown")), + error, + ) + + def operation_state() -> dict[str, Any]: + return { + "phase": phase, + "service_constructed": service_constructed, + "roll_opened": roll_opened, + "requested_slots": list(SLOTS), + "restored_slots": list(restored_slots), + "required_approval_slots": list(required_approval_slots), + "approved_slots": list(approved_slots), + "batch_prepared": batch_prepared, + "yielded_slots": list(yielded_slots), + "committed_slots": list(committed_slots), + "verified_slots": list(verified_slots), + "processing_slot": processing_slot, + "batch_exhausted": batch_exhausted, + "last_progress": last_progress, + "transport_may_have_advanced_beyond_yielded": (transport_may_have_advanced_beyond_yielded), + } + + def receipt_document( + status: str, + *, + finished_at: str | None = None, + error: BaseException | None = None, + ) -> dict[str, Any]: + document: dict[str, Any] = { + "schema": SCHEMA, + "status": status, + "phase": phase, + "run_id": run_id, + "started_at": started_at, + "finished_at": finished_at, + "device_id": request.device_id, + "preview_session": { + "path": str(request.preview_session_path.absolute()), + "expected_sha256": request.preview_session_sha256, + "verified_sha256": session.sha256 if session is not None else None, + "bytes": session.size if session is not None else None, + }, + "reviewed_approval": { + "path": str(request.reviewed_approval_path.absolute()), + "expected_sha256": request.reviewed_approval_sha256, + "verified_sha256": review.sha256 if review is not None else None, + "bytes": review.byte_length if review is not None else None, + "reviewed_fingerprint_sha256": (review.reviewed_fingerprint_sha256 if review is not None else None), + "contact_sheet": ( + { + "path": str(review.contact_sheet_path), + "sha256": review.contact_sheet_sha256, + } + if review is not None + else None + ), + }, + "output_dir": str(output_dir or request.output_dir.absolute()), + "run_receipt": str(receipt_path), + "receipt_reservation": { + "reserved": receipt_reserved, + "inode": list(receipt_inode) if receipt_inode is not None else None, + }, + "output_lease": { + "acquired": lease_acquired, + "lock_name": OUTPUT_LOCK_NAME, + "release_attempted": lease_release_attempted, + "released": lease_released, + }, + "slots": list(SLOTS), + "approved_slots": list(approved_slots), + "operation_state": operation_state(), + "settings": { + "write_unrepaired": True, + "write_repaired": True, + "write_positive": True, + "repair_mode": RepairMode.HYBRID.value, + "positive_mode": PositiveColorMode.NIKON_EXACT.value, + "filename_pattern": FILENAME_PATTERN, + }, + "frames": frames, + "deep_acceptance": deep_batch, + "close": { + "iterator": { + "attempted": iterator_close_attempted, + "succeeded": iterator_close_succeeded, + }, + "roll": { + "attempted": close_attempted, + "succeeded": close_succeeded, + }, + }, + "telemetry_errors": list(telemetry_errors), + "telemetry_error_count": telemetry_error_count, + "telemetry_errors_truncated": (telemetry_error_count > len(telemetry_errors)), + "retry_count": 0, + "eject_requested": False, + } + if error is not None: + document["error"] = _error_payload(error) + return document + + def on_scan_progress(progress: object) -> None: + nonlocal last_progress + try: + event = _progress_event(progress) + except Exception as error: + record_telemetry_error("scan_progress", error) + return + last_progress = event + safe_emit(event) + + try: + safe_emit({"event": "run_started", "slots": list(SLOTS), "run_id": run_id}) + if request.confirm_live is not True: + raise ValueError("--confirm-live is required") + if not request.device_id.strip(): + raise ValueError("device id must be non-empty") + output_dir, receipt_path = _preflight_paths(request) + + phase = "reserving_receipt" + receipt_reservation = receipt_reserver( + receipt_path, + receipt_document("in_progress"), + ) + receipt_reserved = True + receipt_inode = tuple(receipt_reservation.inode) + + phase = "reserving_output" + output_lease = output_lease_factory( + output_dir, + { + "schema": "negpy.ls5000-live-output-lock.v1", + "run_id": run_id, + "run_receipt": str(receipt_path), + "created_at": started_at, + }, + require_empty=True, + ) + lease_acquired = True + inventory_snapshot = output_lease.assert_inventory(()) + phase = "output_reserved" + receipt_reservation.publish(receipt_document("in_progress")) + + phase = "validating_offline_inputs" + session = _load_session( + request.preview_session_path, + request.preview_session_sha256, + ) + review = review_loader(request, session) + runtime = hybrid_runtime_loader(request.hybrid_runtime_manifest_path) + if runtime is None: + raise ValueError("the pinned Hybrid runtime is not installed") + runtime.validate_files() + receipt_reservation.assert_owned() + inventory_snapshot = output_lease.assert_inventory( + (), + previous=inventory_snapshot, + ) + + phase = "ready_to_open" + receipt_reservation.publish(receipt_document("in_progress")) + safe_emit( + { + "event": "preflight_passed", + "preview_session_sha256": session.sha256, + "reviewed_approval_sha256": review.sha256, + } + ) + + service = service_factory(hybrid_runtime=runtime) + service_constructed = True + receipt_reservation.assert_owned() + inventory_snapshot = output_lease.assert_inventory( + (), + previous=inventory_snapshot, + ) + phase = "opening_roll" + service.open_roll(request.device_id) + roll_opened = True + safe_emit({"event": "roll_opened", "device_id": request.device_id}) + + phase = "restoring_review" + thumbnails = service.restore_preview_session(session.text, slots=SLOTS) + restored_slots.extend(getattr(thumbnail, "slot", None) for thumbnail in thumbnails) + if tuple(restored_slots) != SLOTS: + raise ValueError(f"restored slots are {tuple(restored_slots)}; expected exactly {SLOTS}") + validate_restored_thumbnails(thumbnails, review) + required_approval_slots.extend(slot for slot in SLOTS if service.needs_approval(slot)) + if tuple(required_approval_slots) != APPROVED_SLOTS: + raise ValueError(f"restored session approval set is {tuple(required_approval_slots)}; expected {APPROVED_SLOTS}") + safe_emit({"event": "preview_session_restored", "slots": list(restored_slots)}) + + phase = "applying_reviewed_approvals" + for slot in APPROVED_SLOTS: + returned = service.approve(slot) + expected_payload = approval_payload(review.approvals[slot]) + if approval_payload(returned) != expected_payload: + raise ValueError(f"service approval for slot {slot} differs from reviewed evidence") + approved_slots.append(slot) + safe_emit({"event": "slot_approved", "slot": slot}) + receipt_reservation.publish(receipt_document("in_progress")) + + phase = "preparing_batch" + service.prepare_batch() + batch_prepared = True + receipt_reservation.assert_owned() + inventory_snapshot = output_lease.assert_inventory( + owned_files, + previous=inventory_snapshot, + ) + phase = "scanning" + receipt_reservation.publish(receipt_document("in_progress")) + iterator = iter( + service.scan_many( + SLOTS, + on_progress=on_scan_progress, + ) + ) + iterator_created = True + while True: + try: + frame = next(iterator) + except StopIteration: + batch_exhausted = True + break + actual_slot = getattr(frame, "slot", None) + yielded_slots.append(_event_value(actual_slot)) + if len(yielded_slots) > len(SLOTS): + raise ValueError("scanner yielded more than six frames") + expected_slot = SLOTS[len(yielded_slots) - 1] + processing_slot = expected_slot + if actual_slot != expected_slot: + raise ValueError(f"scanner yielded slot {actual_slot}; expected {expected_slot}") + receipt_reservation.assert_owned() + inventory_snapshot = output_lease.assert_inventory( + owned_files, + previous=inventory_snapshot, + ) + + def on_repair_progress( + fraction: object, + *, + slot: int = expected_slot, + ) -> None: + nonlocal last_progress + try: + event = { + "event": "repair_progress", + "slot": slot, + "fraction": _event_value(fraction), + } + except Exception as error: + record_telemetry_error("repair_progress", error) + return + last_progress = event + safe_emit(event) + + output = service.write_frame( + frame, + str(output_dir), + FILENAME_PATTERN, + write_unrepaired=True, + write_repaired=True, + write_positive=True, + repair_mode=RepairMode.HYBRID.value, + positive_mode=PositiveColorMode.NIKON_EXACT.value, + on_repair_progress=on_repair_progress, + ) + outputs.append(output) + returned_slot = _event_value(output.slot) + committed_slots.append(returned_slot) + frame_entry: dict[str, Any] = { + "slot": returned_slot, + "expected_slot": expected_slot, + "returned_output": { + field: getattr(output, field) + for field in ( + "rgb_path", + "ir_path", + "repaired_rgb_path", + "repaired_ir_path", + "positive_path", + "receipt_path", + "synthesis_mask_path", + "native_synthesis_mask_path", + "hybrid_receipt_path", + ) + }, + } + frames.append(frame_entry) + safe_emit({"event": "frame_committed", "slot": expected_slot}) + + frame_entry.update( + _validate_frame_output( + output, + expected_slot=expected_slot, + output_dir=output_dir, + ) + ) + referenced = frame_inventory_collector( + output, + output_dir=output_dir, + expected_slot=expected_slot, + ) + if type(referenced) is not list or not referenced or any(type(path) is not str for path in referenced): + raise ValueError(f"slot {expected_slot} publication inventory is invalid") + owned_files.update(Path(path) for path in referenced) + inventory_snapshot = output_lease.assert_inventory( + owned_files, + previous=inventory_snapshot, + ) + frame_entry["publication_inventory"] = list(referenced) + processing_slot = None + receipt_reservation.publish(receipt_document("in_progress")) + safe_emit({"event": "frame_checkpointed", "slot": expected_slot}) + + if tuple(yielded_slots) != SLOTS: + raise ValueError(f"scanner yielded {tuple(yielded_slots)}; expected exactly {SLOTS}") + if tuple(committed_slots) != SLOTS: + raise ValueError("not every yielded frame was durably committed") + phase = "batch_exhausted" + receipt_reservation.publish(receipt_document("in_progress")) + except ReservationConflict as error: + operation_error = error + except BaseException as error: + operation_error = error + finally: + if iterator is not None: + close_iterator = getattr(iterator, "close", None) + if callable(close_iterator): + iterator_close_attempted = True + try: + close_iterator() + except BaseException as iterator_error: + operation_error = _combine_error( + operation_error, + iterator_error, + label="scan iterator close failed", + ) + else: + iterator_close_succeeded = True + if service is not None: + close_attempted = True + try: + service.close() + except BaseException as close_error: + operation_error = _combine_error( + operation_error, + close_error, + label="scanner close failed", + ) + else: + close_succeeded = True + safe_emit({"event": "roll_closed"}) + + if iterator_created and not batch_exhausted: + transport_may_have_advanced_beyond_yielded = True + + if operation_error is None: + try: + phase = "validating_six_frame_batch" + if output_dir is None or output_lease is None or runtime is None: + raise RuntimeError("live acceptance state is incomplete") + if not close_succeeded: + raise RuntimeError("scanner was not closed before deep acceptance") + receipt_reservation.publish(receipt_document("in_progress")) + deep_batch = batch_validator( + outputs, + output_dir=output_dir, + allowed_output_lock_name=OUTPUT_LOCK_NAME, + hybrid_runtime=runtime, + ) + if review is None: + raise RuntimeError("reviewed approval disappeared before deep acceptance") + expected_manual_approval_bindings: list[dict[str, Any]] = [] + for slot in APPROVED_SLOTS: + binding = approval_payload(review.approvals[slot]).get("binding_sha256") + if type(binding) is not str or _SHA256_RE.fullmatch(binding) is None: + raise ValueError(f"reviewed approval binding for slot {slot} is invalid") + expected_manual_approval_bindings.append( + { + "slot": slot, + "binding_sha256": binding, + } + ) + if ( + type(deep_batch) is not dict + or deep_batch.get("status") != "passed" + or deep_batch.get("slots") != list(SLOTS) + or deep_batch.get("approved_slots") != list(APPROVED_SLOTS) + or deep_batch.get("device_id") != request.device_id + or deep_batch.get("device_model") != EXPECTED_DEVICE_MODEL + or deep_batch.get("reviewed_fingerprint_sha256") != review.reviewed_fingerprint_sha256 + or deep_batch.get("manual_approval_bindings") != expected_manual_approval_bindings + or type(deep_batch.get("referenced_files")) is not list + or any(type(path) is not str for path in deep_batch["referenced_files"]) + or type(deep_batch.get("frames")) is not list + or len(deep_batch["frames"]) != len(SLOTS) + ): + raise ValueError("six-frame deep acceptance returned an invalid result") + for expected_slot, frame_entry, deep_frame in zip( + SLOTS, + frames, + deep_batch["frames"], + strict=True, + ): + if ( + type(deep_frame) is not dict + or deep_frame.get("slot") != expected_slot + or type(deep_frame.get("referenced_files")) is not list + or any(type(path) is not str for path in deep_frame["referenced_files"]) + or set(deep_frame["referenced_files"]) != set(frame_entry["publication_inventory"]) + ): + raise ValueError(f"slot {expected_slot} deep inventory differs from its live checkpoint") + frame_entry["deep_acceptance"] = deep_frame + verified_slots.append(expected_slot) + safe_emit({"event": "frame_verified", "slot": expected_slot}) + batch_files = {Path(path) for path in deep_batch["referenced_files"]} + if batch_files != owned_files: + raise ValueError("six-frame deep acceptance inventory differs from frame checkpoints") + if tuple(verified_slots) != SLOTS: + raise ValueError("not every committed frame was deeply verified") + inventory_snapshot = output_lease.assert_inventory( + batch_files, + previous=inventory_snapshot, + ) + receipt_reservation.assert_owned() + phase = "six_frame_batch_verified" + receipt_reservation.publish(receipt_document("in_progress")) + safe_emit({"event": "six_frame_batch_verified", "slots": list(SLOTS)}) + except BaseException as validation_error: + operation_error = validation_error + + receipt: dict[str, Any] | None = None + if operation_error is None: + if output_lease is None or inventory_snapshot is None: + operation_error = RuntimeError("output lease state is incomplete at finalization") + elif receipt_reservation is None: + operation_error = RuntimeError("run receipt was never reserved") + + if operation_error is None: + lease_release_attempted = True + phase = "finalizing_success" + safe_emit({"event": "run_finalizing", "status": "succeeded"}) + + def publish_success_while_directory_is_held() -> None: + nonlocal lease_released, phase, receipt + # release_verified invokes this only after unlinking the fixed + # lock, while retaining the directory descriptor for a second + # pathname + inventory check after publication. + lease_released = True + phase = "succeeded" + safe_emit( + { + "event": "run_finished", + "status": "succeeded", + "run_receipt": str(receipt_path), + } + ) + receipt = receipt_document( + "succeeded", + finished_at=_utc_now(), + ) + receipt_reservation.publish(receipt) + + try: + output_lease.release_verified( + owned_files, + previous=inventory_snapshot, + finalize=publish_success_while_directory_is_held, + ) + except BaseException as lease_error: + operation_error = _combine_error( + operation_error, + lease_error, + label="verified output lease release failed", + ) + finally: + lease_released = bool(output_lease.released) + + if operation_error is not None: + if output_lease is not None and not output_lease.released: + lease_release_attempted = True + try: + output_lease.release() + except BaseException as lease_error: + operation_error = _combine_error( + operation_error, + lease_error, + label="output lease cleanup failed", + ) + finally: + lease_released = bool(output_lease.released) + phase = "failed" + safe_emit({"event": "run_finalizing", "status": "failed"}) + safe_emit( + { + "event": "run_finished", + "status": "failed", + "run_receipt": str(receipt_path), + } + ) + receipt = receipt_document( + "failed", + finished_at=_utc_now(), + error=operation_error, + ) + + assert receipt is not None + if receipt_reservation is None: + message = _error_payload(operation_error or RuntimeError("run receipt reservation failed"))["message"] + raise LiveAcceptanceError(message, receipt=receipt) from operation_error + + if receipt.get("status") == "failed": + try: + receipt_reservation.publish(receipt) + except BaseException as receipt_error: + failed_receipt = dict(receipt) + failed_receipt["status"] = "failed" + failed_receipt["phase"] = "failed" + failed_receipt["error"] = _error_payload(receipt_error) + raise LiveAcceptanceError( + f"could not publish the final run receipt: {_error_payload(receipt_error)['message']}", + receipt=failed_receipt, + ) from receipt_error + + try: + receipt_reservation.close() + except Exception: + # The document was already fsynced into the held inode. A close(2) + # housekeeping error must not replace that durable outcome. + pass + + if operation_error is not None: + raise LiveAcceptanceError( + _error_payload(operation_error)["message"], + receipt=receipt, + ) from operation_error + return receipt + + +def _parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser( + description=("Run the pinned six-frame LS-5000 live acceptance exactly once. The output directory must already exist and be empty.") + ) + parser.add_argument("--device-id", required=True) + parser.add_argument("--preview-session", required=True, type=Path) + parser.add_argument("--preview-session-sha256", required=True) + parser.add_argument("--reviewed-approval", required=True, type=Path) + parser.add_argument("--reviewed-approval-sha256", required=True) + parser.add_argument("--output-dir", required=True, type=Path) + parser.add_argument("--run-receipt", required=True, type=Path) + parser.add_argument("--hybrid-runtime-manifest", type=Path) + parser.add_argument("--confirm-live", action="store_true") + return parser + + +def main(argv: list[str] | None = None) -> int: + args = _parser().parse_args(argv) + request = LiveAcceptanceRequest( + device_id=args.device_id, + preview_session_path=args.preview_session, + preview_session_sha256=args.preview_session_sha256, + reviewed_approval_path=args.reviewed_approval, + reviewed_approval_sha256=args.reviewed_approval_sha256, + output_dir=args.output_dir, + run_receipt_path=args.run_receipt, + confirm_live=args.confirm_live, + hybrid_runtime_manifest_path=args.hybrid_runtime_manifest, + ) + try: + run_live_acceptance(request) + except LiveAcceptanceError: + return 1 + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/negpy/services/roll/live_reservation.py b/negpy/services/roll/live_reservation.py new file mode 100644 index 00000000..a20c827b --- /dev/null +++ b/negpy/services/roll/live_reservation.py @@ -0,0 +1,750 @@ +"""Crash-visible, fail-closed ownership primitives for a live roll run. + +This module is deliberately hardware-inert. It owns only two filesystem +names: the caller-selected run receipt and a fixed lock inside an output +directory. A live runner can reserve both before opening a scanner, retain +their descriptors for the complete run, and refuse to overwrite names whose +ownership changes underneath it. +""" + +from __future__ import annotations + +import dataclasses +import json +import os +import stat +from collections.abc import Callable, Iterable, Mapping +from pathlib import Path +from typing import Any, Self + + +OUTPUT_LOCK_NAME = ".negpy-live-acceptance.lock" +DEFAULT_RECEIPT_MAX_BYTES = 1024 * 1024 +DEFAULT_LOCK_MAX_BYTES = 16 * 1024 + + +class LiveReservationError(RuntimeError): + """A filesystem ownership boundary could not be established or retained.""" + + +class ReservationConflict(LiveReservationError): + """The requested receipt or fixed output lock already exists.""" + + +class OwnershipLost(LiveReservationError): + """A reserved pathname no longer identifies the object we opened.""" + + +class InventoryConflict(LiveReservationError): + """The output directory contains missing, changed, or unowned entries.""" + + +@dataclasses.dataclass(frozen=True) +class FileIdentity: + """Stable-enough checkpoint identity for one regular output file.""" + + device: int + inode: int + size: int + mtime_ns: int + ctime_ns: int + + @classmethod + def from_stat(cls, metadata: os.stat_result) -> Self: + return cls( + device=metadata.st_dev, + inode=metadata.st_ino, + size=metadata.st_size, + mtime_ns=metadata.st_mtime_ns, + ctime_ns=metadata.st_ctime_ns, + ) + + @property + def object_identity(self) -> tuple[int, int]: + return self.device, self.inode + + +@dataclasses.dataclass(frozen=True) +class InventorySnapshot: + """Exact file/directory names observed at an ownership checkpoint.""" + + files: tuple[tuple[str, FileIdentity], ...] + directories: tuple[str, ...] + + +def canonical_json_bytes( + document: Mapping[str, Any], + *, + maximum_bytes: int, + label: str, +) -> bytes: + """Encode one bounded canonical JSON object, including its final newline.""" + + if maximum_bytes <= 0: + raise ValueError("maximum_bytes must be positive") + if not isinstance(document, Mapping): + raise LiveReservationError(f"{label} must be a JSON object") + try: + payload = ( + json.dumps( + dict(document), + sort_keys=True, + separators=(",", ":"), + ensure_ascii=True, + allow_nan=False, + ).encode("utf-8") + + b"\n" + ) + except (TypeError, ValueError) as error: + raise LiveReservationError(f"{label} is not canonical JSON: {error}") from error + if len(payload) > maximum_bytes: + raise LiveReservationError(f"{label} exceeds its safe size limit ({len(payload)} > {maximum_bytes} bytes)") + return payload + + +def _require_posix_descriptor_operations() -> None: + missing: list[str] = [] + for name in ("O_DIRECTORY", "O_NOFOLLOW"): + if not hasattr(os, name): + missing.append(name) + for function, capability, name in ( + (os.open, os.supports_dir_fd, "open(dir_fd)"), + (os.stat, os.supports_dir_fd, "stat(dir_fd)"), + (os.stat, os.supports_follow_symlinks, "stat(follow_symlinks)"), + (os.unlink, os.supports_dir_fd, "unlink(dir_fd)"), + (os.listdir, os.supports_fd, "listdir(fd)"), + ): + if function not in capability: + missing.append(name) + if missing: + raise LiveReservationError("safe POSIX descriptor operations are unavailable: " + ", ".join(missing)) + + +def _directory_flags() -> int: + return os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW | getattr(os, "O_CLOEXEC", 0) + + +def _file_create_flags() -> int: + return os.O_CREAT | os.O_EXCL | os.O_RDWR | os.O_NOFOLLOW | getattr(os, "O_CLOEXEC", 0) + + +def _same_object(left: os.stat_result, right: os.stat_result) -> bool: + return (left.st_dev, left.st_ino) == (right.st_dev, right.st_ino) + + +def _directory_checkpoint(metadata: os.stat_result) -> tuple[int, int, int, int]: + return ( + metadata.st_dev, + metadata.st_ino, + metadata.st_mtime_ns, + metadata.st_ctime_ns, + ) + + +def _write_all(descriptor: int, payload: bytes) -> None: + written = 0 + while written < len(payload): + count = os.write(descriptor, payload[written:]) + if count <= 0: + raise OSError("short write while publishing reserved JSON") + written += count + + +def _read_exact(descriptor: int, size: int) -> bytes: + chunks: list[bytes] = [] + offset = 0 + while offset < size: + chunk = os.pread(descriptor, size - offset, offset) + if not chunk: + break + chunks.append(chunk) + offset += len(chunk) + return b"".join(chunks) + + +def _replace_descriptor_payload(descriptor: int, payload: bytes) -> None: + os.lseek(descriptor, 0, os.SEEK_SET) + _write_all(descriptor, payload) + os.ftruncate(descriptor, len(payload)) + os.fsync(descriptor) + if _read_exact(descriptor, len(payload)) != payload: + raise OSError("reserved JSON did not verify after publication") + + +def _open_visible_directory(path: Path, *, label: str) -> tuple[Path, int]: + _require_posix_descriptor_operations() + canonical = path.resolve(strict=True) + descriptor = os.open(canonical, _directory_flags()) + try: + opened = os.fstat(descriptor) + visible = os.stat(canonical, follow_symlinks=False) + if not stat.S_ISDIR(opened.st_mode) or not stat.S_ISDIR(visible.st_mode) or not _same_object(opened, visible): + raise OwnershipLost(f"{label} changed while opening") + except BaseException: + os.close(descriptor) + raise + return canonical, descriptor + + +def _assert_visible_directory(path: Path, descriptor: int, *, label: str) -> None: + try: + opened = os.fstat(descriptor) + visible = os.stat(path, follow_symlinks=False) + except OSError as error: + raise OwnershipLost(f"{label} is no longer visible: {error}") from error + if not stat.S_ISDIR(opened.st_mode) or not stat.S_ISDIR(visible.st_mode) or not _same_object(opened, visible): + raise OwnershipLost(f"{label} pathname no longer identifies the opened directory") + + +def _child_metadata(directory_descriptor: int, name: str) -> os.stat_result: + return os.stat( + name, + dir_fd=directory_descriptor, + follow_symlinks=False, + ) + + +class ExclusiveReceiptReservation: + """An exact run-receipt pathname reserved before any live device open.""" + + def __init__( + self, + *, + path: Path, + parent: Path, + parent_descriptor: int, + descriptor: int, + maximum_bytes: int, + ) -> None: + self.path = path + self._parent = parent + self._parent_descriptor = parent_descriptor + self._descriptor = descriptor + self._maximum_bytes = maximum_bytes + self._closed = False + + @classmethod + def reserve( + cls, + path: Path, + initial_document: Mapping[str, Any], + *, + maximum_bytes: int = DEFAULT_RECEIPT_MAX_BYTES, + ) -> Self: + """Exclusively create and durably publish an in-progress receipt.""" + + if initial_document.get("status") != "in_progress": + raise LiveReservationError("initial run receipt status must be exactly 'in_progress'") + payload = canonical_json_bytes( + initial_document, + maximum_bytes=maximum_bytes, + label="run receipt", + ) + absolute = Path(os.path.abspath(os.fspath(path))) + if absolute.name in {"", ".", ".."}: + raise LiveReservationError("run receipt must name a file") + parent, parent_descriptor = _open_visible_directory( + absolute.parent, + label="run-receipt parent", + ) + canonical = parent / absolute.name + descriptor: int | None = None + try: + try: + descriptor = os.open( + absolute.name, + _file_create_flags(), + 0o600, + dir_fd=parent_descriptor, + ) + except FileExistsError as error: + raise ReservationConflict(f"run receipt already exists: {canonical}") from error + opened = os.fstat(descriptor) + visible = _child_metadata(parent_descriptor, absolute.name) + if not stat.S_ISREG(opened.st_mode) or opened.st_nlink != 1 or not _same_object(opened, visible): + raise OwnershipLost("new run receipt did not retain exclusive regular-file ownership") + _replace_descriptor_payload(descriptor, payload) + os.fsync(parent_descriptor) + reservation = cls( + path=canonical, + parent=parent, + parent_descriptor=parent_descriptor, + descriptor=descriptor, + maximum_bytes=maximum_bytes, + ) + reservation.assert_owned() + return reservation + except BaseException: + if descriptor is not None: + try: + opened = os.fstat(descriptor) + visible = _child_metadata(parent_descriptor, absolute.name) + if _same_object(opened, visible): + os.unlink(absolute.name, dir_fd=parent_descriptor) + os.fsync(parent_descriptor) + except OSError: + pass + os.close(descriptor) + os.close(parent_descriptor) + raise + + @property + def inode(self) -> tuple[int, int]: + self._require_open() + metadata = os.fstat(self._descriptor) + return metadata.st_dev, metadata.st_ino + + def _require_open(self) -> None: + if self._closed: + raise LiveReservationError("run-receipt reservation is closed") + + def assert_owned(self) -> None: + """Require the requested pathname to still name our held receipt inode.""" + + self._require_open() + _assert_visible_directory( + self._parent, + self._parent_descriptor, + label="run-receipt parent", + ) + try: + opened = os.fstat(self._descriptor) + visible = _child_metadata(self._parent_descriptor, self.path.name) + except OSError as error: + raise OwnershipLost(f"run receipt is no longer owned: {error}") from error + if ( + not stat.S_ISREG(opened.st_mode) + or not stat.S_ISREG(visible.st_mode) + or opened.st_nlink != 1 + or visible.st_nlink != 1 + or not _same_object(opened, visible) + ): + raise OwnershipLost("run-receipt pathname no longer identifies the reserved inode") + + def publish(self, document: Mapping[str, Any]) -> None: + """Publish new canonical JSON into, and only into, the reserved inode.""" + + payload = canonical_json_bytes( + document, + maximum_bytes=self._maximum_bytes, + label="run receipt", + ) + self.assert_owned() + _replace_descriptor_payload(self._descriptor, payload) + self.assert_owned() + os.fsync(self._parent_descriptor) + + def close(self) -> None: + """Release descriptors without deleting the durable receipt.""" + + if self._closed: + return + os.close(self._descriptor) + os.close(self._parent_descriptor) + self._closed = True + + def __enter__(self) -> Self: + self._require_open() + return self + + def __exit__(self, *_exc: object) -> None: + self.close() + + +class FixedOutputLease: + """A fixed, crash-visible output-directory lease held for one live run.""" + + def __init__( + self, + *, + output_dir: Path, + directory_descriptor: int, + lock_descriptor: int, + ) -> None: + self.output_dir = output_dir + self.lock_path = output_dir / OUTPUT_LOCK_NAME + self._directory_descriptor = directory_descriptor + self._lock_descriptor = lock_descriptor + self._released = False + + @classmethod + def acquire( + cls, + output_dir: Path, + lock_document: Mapping[str, Any], + *, + maximum_bytes: int = DEFAULT_LOCK_MAX_BYTES, + require_empty: bool = True, + ) -> Self: + """Exclusively own a real output directory through a fixed lock file.""" + + payload = canonical_json_bytes( + lock_document, + maximum_bytes=maximum_bytes, + label="output lock", + ) + absolute = Path(os.path.abspath(os.fspath(output_dir))) + try: + linked = absolute.lstat() + except OSError as error: + raise LiveReservationError(f"could not inspect output directory: {error}") from error + if stat.S_ISLNK(linked.st_mode) or not stat.S_ISDIR(linked.st_mode): + raise LiveReservationError("output directory must be an existing non-symlink directory") + canonical, directory_descriptor = _open_visible_directory( + absolute, + label="output directory", + ) + lock_descriptor: int | None = None + try: + try: + lock_descriptor = os.open( + OUTPUT_LOCK_NAME, + _file_create_flags(), + 0o600, + dir_fd=directory_descriptor, + ) + except FileExistsError as error: + raise ReservationConflict(f"output directory is already reserved: {canonical / OUTPUT_LOCK_NAME}") from error + opened = os.fstat(lock_descriptor) + visible = _child_metadata(directory_descriptor, OUTPUT_LOCK_NAME) + if not stat.S_ISREG(opened.st_mode) or opened.st_nlink != 1 or not _same_object(opened, visible): + raise OwnershipLost("new output lock did not retain exclusive regular-file ownership") + _replace_descriptor_payload(lock_descriptor, payload) + os.fsync(directory_descriptor) + lease = cls( + output_dir=canonical, + directory_descriptor=directory_descriptor, + lock_descriptor=lock_descriptor, + ) + lease.assert_owned() + if require_empty: + lease.assert_inventory(()) + return lease + except BaseException: + if lock_descriptor is not None: + try: + opened = os.fstat(lock_descriptor) + visible = _child_metadata(directory_descriptor, OUTPUT_LOCK_NAME) + if _same_object(opened, visible): + os.unlink(OUTPUT_LOCK_NAME, dir_fd=directory_descriptor) + os.fsync(directory_descriptor) + except OSError: + pass + os.close(lock_descriptor) + os.close(directory_descriptor) + raise + + @property + def released(self) -> bool: + return self._released + + def _require_active(self) -> None: + if self._released: + raise LiveReservationError("output lease is already released") + + def _lock_metadata(self) -> tuple[os.stat_result, os.stat_result]: + try: + opened = os.fstat(self._lock_descriptor) + visible = _child_metadata(self._directory_descriptor, OUTPUT_LOCK_NAME) + except OSError as error: + raise OwnershipLost(f"output lock is no longer owned: {error}") from error + return opened, visible + + def assert_owned(self) -> None: + """Require both the output pathname and fixed lock to remain ours.""" + + self._require_active() + _assert_visible_directory( + self.output_dir, + self._directory_descriptor, + label="output directory", + ) + opened, visible = self._lock_metadata() + if ( + not stat.S_ISREG(opened.st_mode) + or not stat.S_ISREG(visible.st_mode) + or opened.st_nlink != 1 + or visible.st_nlink != 1 + or not _same_object(opened, visible) + ): + raise OwnershipLost("fixed output-lock pathname no longer identifies the held inode") + + def _relative_entry(self, path: Path | str, *, label: str) -> str: + candidate = Path(path) + if candidate.is_absolute(): + absolute = Path(os.path.abspath(os.fspath(candidate))) + else: + absolute = Path(os.path.abspath(os.fspath(self.output_dir / candidate))) + try: + relative = absolute.relative_to(self.output_dir) + except ValueError as error: + raise InventoryConflict(f"{label} escapes the output directory: {path}") from error + if relative == Path(".") or any(part in {"", ".", ".."} for part in relative.parts): + raise InventoryConflict(f"{label} does not name a child entry: {path}") + name = os.fspath(relative) + if name == OUTPUT_LOCK_NAME: + raise InventoryConflict("the fixed output lock cannot be declared as an output") + return name + + def _scan_directory( + self, + descriptor: int, + *, + prefix: str, + files: dict[str, FileIdentity], + directories: set[str], + ) -> None: + before = os.fstat(descriptor) + if not stat.S_ISDIR(before.st_mode): + raise InventoryConflict("an opened output entry stopped being a directory") + try: + names = sorted(os.listdir(descriptor)) + except OSError as error: + raise InventoryConflict(f"could not list output directory: {error}") from error + for name in names: + relative = os.path.join(prefix, name) if prefix else name + try: + metadata = _child_metadata(descriptor, name) + except OSError as error: + raise InventoryConflict(f"output entry changed while inspecting {relative}: {error}") from error + if stat.S_ISLNK(metadata.st_mode): + raise InventoryConflict(f"output entry is a symbolic link: {relative}") + if stat.S_ISREG(metadata.st_mode): + files[relative] = FileIdentity.from_stat(metadata) + continue + if not stat.S_ISDIR(metadata.st_mode): + raise InventoryConflict(f"output entry is not a regular file or directory: {relative}") + directories.add(relative) + try: + child_descriptor = os.open( + name, + _directory_flags(), + dir_fd=descriptor, + ) + except OSError as error: + raise InventoryConflict(f"could not open output directory {relative}: {error}") from error + try: + opened = os.fstat(child_descriptor) + if not _same_object(opened, metadata): + raise InventoryConflict(f"output directory changed while opening: {relative}") + self._scan_directory( + child_descriptor, + prefix=relative, + files=files, + directories=directories, + ) + finally: + os.close(child_descriptor) + after = os.fstat(descriptor) + if _directory_checkpoint(before) != _directory_checkpoint(after): + raise InventoryConflict("output directory changed while its inventory was inspected") + + @staticmethod + def _ancestor_directories(entries: Iterable[str]) -> set[str]: + ancestors: set[str] = set() + for entry in entries: + parent = os.path.dirname(entry) + while parent: + ancestors.add(parent) + parent = os.path.dirname(parent) + return ancestors + + def assert_inventory( + self, + owned_files: Iterable[Path | str], + *, + owned_directories: Iterable[Path | str] = (), + previous: InventorySnapshot | None = None, + ) -> InventorySnapshot: + """Require an exact, regular, non-symlink output inventory. + + ``owned_files`` and ``owned_directories`` exclude the fixed lease file; + it is included automatically. Passing the prior checkpoint also + refuses mutation or replacement of every previously owned file. + """ + + return self._assert_inventory( + owned_files, + owned_directories=owned_directories, + previous=previous, + include_lock=True, + ) + + def _assert_inventory( + self, + owned_files: Iterable[Path | str], + *, + owned_directories: Iterable[Path | str] = (), + previous: InventorySnapshot | None = None, + include_lock: bool, + ) -> InventorySnapshot: + """Check the exact held-directory inventory before or after unlock.""" + + self._require_active() + if include_lock: + self.assert_owned() + else: + _assert_visible_directory( + self.output_dir, + self._directory_descriptor, + label="output directory", + ) + expected_files = {self._relative_entry(path, label="owned file") for path in owned_files} + expected_directories = {self._relative_entry(path, label="owned directory") for path in owned_directories} + if include_lock: + expected_files.add(OUTPUT_LOCK_NAME) + expected_directories.update(self._ancestor_directories(expected_files)) + expected_directories.update(self._ancestor_directories(expected_directories)) + + actual_files: dict[str, FileIdentity] = {} + actual_directories: set[str] = set() + self._scan_directory( + self._directory_descriptor, + prefix="", + files=actual_files, + directories=actual_directories, + ) + if include_lock: + self.assert_owned() + else: + _assert_visible_directory( + self.output_dir, + self._directory_descriptor, + label="output directory", + ) + + missing_files = sorted(expected_files - actual_files.keys()) + unexpected_files = sorted(actual_files.keys() - expected_files) + missing_directories = sorted(expected_directories - actual_directories) + unexpected_directories = sorted(actual_directories - expected_directories) + differences: list[str] = [] + for label, entries in ( + ("missing files", missing_files), + ("unexpected files", unexpected_files), + ("missing directories", missing_directories), + ("unexpected directories", unexpected_directories), + ): + if entries: + differences.append(f"{label}: {', '.join(entries)}") + if differences: + raise InventoryConflict("output inventory conflict (" + "; ".join(differences) + ")") + + if previous is not None: + for name, prior_identity in previous.files: + if not include_lock and name == OUTPUT_LOCK_NAME: + continue + current_identity = actual_files.get(name) + if current_identity is None: + raise InventoryConflict(f"previously owned output disappeared: {name}") + if current_identity != prior_identity: + raise InventoryConflict(f"previously owned output changed: {name}") + + return InventorySnapshot( + files=tuple(sorted(actual_files.items())), + directories=tuple(sorted(actual_directories)), + ) + + def _unlink_owned_lock(self) -> None: + opened, visible = self._lock_metadata() + if ( + not stat.S_ISREG(opened.st_mode) + or not stat.S_ISREG(visible.st_mode) + or opened.st_nlink != 1 + or visible.st_nlink != 1 + or not _same_object(opened, visible) + ): + raise OwnershipLost("refusing to remove a fixed output lock that is no longer exclusively owned") + os.unlink(OUTPUT_LOCK_NAME, dir_fd=self._directory_descriptor) + os.fsync(self._directory_descriptor) + + def release_verified( + self, + owned_files: Iterable[Path | str], + *, + previous: InventorySnapshot, + finalize: Callable[[], None], + ) -> InventorySnapshot: + """Unlock and publish success inside two exact pathname checks. + + The fixed lock is removed only after the visible output pathname and + full inventory pass. ``finalize`` then publishes the success receipt + while the directory descriptor remains held. A second check catches + any pathname swap or artifact mutation during that publication. If + either check fails, the caller can overwrite a prematurely published + success receipt with a failed receipt before returning. + """ + + self._require_active() + expected_files = tuple(owned_files) + error: BaseException | None = None + result: InventorySnapshot | None = None + lock_unlinked = False + try: + self._assert_inventory( + expected_files, + previous=previous, + include_lock=True, + ) + self._unlink_owned_lock() + lock_unlinked = True + unlocked = self._assert_inventory( + expected_files, + previous=previous, + include_lock=False, + ) + finalize() + result = self._assert_inventory( + expected_files, + previous=unlocked, + include_lock=False, + ) + except BaseException as caught: + error = caught + finally: + if not lock_unlinked: + try: + self._unlink_owned_lock() + except BaseException as cleanup_error: + if error is None: + error = cleanup_error + else: + error = RuntimeError(f"{error}; output lock cleanup failed: {cleanup_error}") + try: + os.close(self._lock_descriptor) + except BaseException as close_error: + if error is None: + error = close_error + else: + error = RuntimeError(f"{error}; output lock descriptor close failed: {close_error}") + try: + os.close(self._directory_descriptor) + except BaseException as close_error: + if error is None: + error = close_error + else: + error = RuntimeError(f"{error}; output directory descriptor close failed: {close_error}") + self._released = True + if error is not None: + if isinstance(error, LiveReservationError): + raise error + raise LiveReservationError(f"could not verify and release fixed output lock: {error}") from error + assert result is not None + return result + + def release(self) -> None: + """Remove only the fixed lock inode opened by this lease, then fsync.""" + + self._require_active() + error: BaseException | None = None + try: + self._unlink_owned_lock() + except BaseException as caught: + error = caught + finally: + os.close(self._lock_descriptor) + os.close(self._directory_descriptor) + self._released = True + if error is not None: + if isinstance(error, LiveReservationError): + raise error + raise LiveReservationError(f"could not release fixed output lock: {error}") from error diff --git a/negpy/services/roll/live_review.py b/negpy/services/roll/live_review.py new file mode 100644 index 00000000..8b59d674 --- /dev/null +++ b/negpy/services/roll/live_review.py @@ -0,0 +1,291 @@ +"""Pinned operator-review evidence for one LS-5000 live acceptance run.""" + +from __future__ import annotations + +import hashlib +import hmac +import io +import json +import os +import stat +from collections.abc import Callable, Mapping +from dataclasses import dataclass +from pathlib import Path +from types import MappingProxyType +from typing import Any, TypeGuard, cast + +import numpy as np +from PIL import Image + + +SCHEMA = "negpy.ls5000-reviewed-approval.v1" +REVIEW_BASIS = "visual-inspection-of-six-frame-contact-sheet-and-canonical-restored-thumbnails" +APPROVED_SLOTS = (1, 6) +_SHA256_CHARS = frozenset("0123456789abcdef") +_MAX_REVIEW_BYTES = 64 * 1024 +_MAX_CONTACT_SHEET_BYTES = 64 * 1024 * 1024 + + +@dataclass(frozen=True) +class ValidatedReviewedApproval: + """One SHA-pinned visual review, rederived from the restored preview.""" + + sha256: str + byte_length: int + reviewed_fingerprint_sha256: str + contact_sheet_path: Path + contact_sheet_sha256: str + approvals: Mapping[int, object] + + def __post_init__(self) -> None: + object.__setattr__( + self, + "approvals", + MappingProxyType(dict(self.approvals)), + ) + + +def _is_sha256(value: object) -> TypeGuard[str]: + return type(value) is str and len(value) == 64 and not (set(value) - _SHA256_CHARS) + + +def _identity(value: os.stat_result) -> tuple[int, int, int, int, int]: + return ( + value.st_dev, + value.st_ino, + value.st_size, + value.st_mtime_ns, + value.st_ctime_ns, + ) + + +def _stable_regular_bytes( + path: Path, + *, + maximum_bytes: int, + label: str, +) -> bytes: + descriptor: int | None = None + try: + before = path.lstat() + if stat.S_ISLNK(before.st_mode) or not stat.S_ISREG(before.st_mode): + raise ValueError(f"{label} must be a regular non-symlink file") + if not 1 <= before.st_size <= maximum_bytes: + raise ValueError(f"{label} has an invalid byte length") + flags = os.O_RDONLY | getattr(os, "O_CLOEXEC", 0) | getattr(os, "O_NOFOLLOW", 0) | getattr(os, "O_NONBLOCK", 0) + descriptor = os.open(path, flags) + opened = os.fstat(descriptor) + if not stat.S_ISREG(opened.st_mode) or _identity(opened) != _identity(before): + raise ValueError(f"{label} changed while opening") + chunks: list[bytes] = [] + total = 0 + while chunk := os.read(descriptor, min(1024 * 1024, maximum_bytes + 1 - total)): + chunks.append(chunk) + total += len(chunk) + if total > maximum_bytes: + raise ValueError(f"{label} exceeds its safe size limit") + after = os.fstat(descriptor) + final = path.lstat() + except FileNotFoundError as error: + raise ValueError(f"{label} is missing: {path}") from error + except OSError as error: + raise ValueError(f"could not read {label}: {error}") from error + finally: + if descriptor is not None: + os.close(descriptor) + if _identity(opened) != _identity(after) or _identity(opened) != _identity(final): + raise ValueError(f"{label} changed while reading") + payload = b"".join(chunks) + if len(payload) != opened.st_size: + raise ValueError(f"{label} changed byte length while reading") + return payload + + +def _strict_json_object(payload: bytes, *, label: str) -> dict[str, Any]: + def unique(pairs: list[tuple[str, object]]) -> dict[str, object]: + result: dict[str, object] = {} + for key, value in pairs: + if key in result: + raise ValueError(f"duplicate key {key!r}") + result[key] = value + return result + + def reject_constant(value: str) -> object: + raise ValueError(f"non-finite constant {value!r}") + + try: + document = json.loads( + payload, + object_pairs_hook=unique, + parse_constant=reject_constant, + ) + except (UnicodeDecodeError, json.JSONDecodeError, ValueError) as error: + raise ValueError(f"{label} is invalid JSON: {error}") from error + if type(document) is not dict: + raise ValueError(f"{label} must contain a JSON object") + return document + + +def thumbnail_sha256(image: np.ndarray) -> str: + """Reproduce CoolscanPy's ManualFrameApproval thumbnail identity.""" + + thumbnail = np.asarray(image) + if thumbnail.dtype != np.uint16 or thumbnail.ndim != 3 or thumbnail.shape[2] != 3: + raise ValueError("reviewed thumbnail must be HxWx3 uint16") + digest = hashlib.sha256() + digest.update(str(thumbnail.shape).encode("ascii")) + digest.update(thumbnail.dtype.str.encode("ascii")) + digest.update(np.ascontiguousarray(thumbnail).tobytes()) + return digest.hexdigest() + + +def approval_payload(value: object) -> dict[str, Any]: + """Return a full ManualFrameApproval payload without trusting duck types.""" + + to_payload = getattr(value, "to_payload", None) + payload = to_payload() if callable(to_payload) else value + if type(payload) is not dict or any(type(key) is not str for key in payload): + raise ValueError("manual approval did not return a payload object") + return dict(cast(dict[str, Any], payload)) + + +def _default_session_loader(payload: str) -> object: + from coolscanpy.roll.preview_session import RollPreviewSession + + return RollPreviewSession.from_json(payload) + + +def _default_approval_parser(payload: object) -> object: + from coolscanpy.protocol.ls5000_single_pass.capture_process import ( + ManualFrameApproval, + ) + + return ManualFrameApproval.from_payload(payload) + + +def load_reviewed_approval( + review_path: Path, + expected_sha256: str, + *, + preview_session_path: Path, + preview_session_payload: str, + preview_session_sha256: str, + session_loader: Callable[[str], object] = _default_session_loader, + approval_parser: Callable[[object], object] = _default_approval_parser, +) -> ValidatedReviewedApproval: + """Load, pin, and rederive the exact approvals for selected edge slots.""" + + if not _is_sha256(expected_sha256): + raise ValueError("reviewed-approval pin must be a lowercase SHA-256 digest") + if not _is_sha256(preview_session_sha256): + raise ValueError("preview-session SHA-256 is malformed") + review_bytes = _stable_regular_bytes( + review_path, + maximum_bytes=_MAX_REVIEW_BYTES, + label="reviewed approval", + ) + actual_sha256 = hashlib.sha256(review_bytes).hexdigest() + if not hmac.compare_digest(actual_sha256, expected_sha256): + raise ValueError(f"reviewed-approval SHA-256 mismatch: expected {expected_sha256}, got {actual_sha256}") + document = _strict_json_object(review_bytes, label="reviewed approval") + if set(document) != { + "approvals", + "contact_sheet", + "preview_session", + "review_basis", + "reviewed_fingerprint_sha256", + "schema", + }: + raise ValueError("reviewed approval fields are incomplete") + if document.get("schema") != SCHEMA or document.get("review_basis") != REVIEW_BASIS: + raise ValueError("reviewed approval schema or review basis is unsupported") + + session_row = document.get("preview_session") + if type(session_row) is not dict or set(session_row) != {"bytes", "path", "sha256"}: + raise ValueError("reviewed approval preview-session binding is malformed") + try: + session_path = Path(session_row["path"]).absolute() + except TypeError as error: + raise ValueError("reviewed approval preview-session path is malformed") from error + if ( + session_path != preview_session_path.absolute() + or session_row.get("sha256") != preview_session_sha256 + or session_row.get("bytes") != len(preview_session_payload.encode("utf-8")) + ): + raise ValueError("reviewed approval belongs to another preview session") + + contact_row = document.get("contact_sheet") + if type(contact_row) is not dict or set(contact_row) != {"bytes", "path", "sha256"}: + raise ValueError("reviewed approval contact-sheet binding is malformed") + if type(contact_row.get("path")) is not str or not _is_sha256(contact_row.get("sha256")): + raise ValueError("reviewed approval contact-sheet identity is malformed") + contact_path = Path(contact_row["path"]).absolute() + contact_bytes = _stable_regular_bytes( + contact_path, + maximum_bytes=_MAX_CONTACT_SHEET_BYTES, + label="reviewed contact sheet", + ) + if contact_row.get("bytes") != len(contact_bytes) or not hmac.compare_digest( + hashlib.sha256(contact_bytes).hexdigest(), + contact_row["sha256"], + ): + raise ValueError("reviewed contact-sheet content changed") + try: + with Image.open(io.BytesIO(contact_bytes)) as image: + if image.format != "PNG" or image.width < 6 or image.height < 1: + raise ValueError("reviewed contact sheet has invalid PNG geometry") + image.load() + except Exception as error: + raise ValueError(f"reviewed contact sheet is not a valid PNG: {error}") from error + + session = session_loader(preview_session_payload) + reviewed_fingerprint = getattr(session, "reviewed_fingerprint", None) + if not callable(reviewed_fingerprint): + raise ValueError("restored preview has no reviewed fingerprint") + fingerprint = getattr(reviewed_fingerprint(), "binding_sha256", None) + if not _is_sha256(fingerprint) or document.get("reviewed_fingerprint_sha256") != fingerprint: + raise ValueError("reviewed approval fingerprint does not match the preview") + + rows = document.get("approvals") + if type(rows) is not list or len(rows) != len(APPROVED_SLOTS): + raise ValueError("reviewed approval must contain exactly slots 1 and 6") + approvals: dict[int, object] = {} + for expected_slot, row in zip(APPROVED_SLOTS, rows, strict=True): + parsed = approval_parser(row) + parsed_payload = approval_payload(parsed) + if parsed_payload != row or parsed_payload.get("slot") != expected_slot: + raise ValueError("reviewed approval payload is non-canonical or out of order") + approve = getattr(session, "approve_manual_origin", None) + if not callable(approve): + raise ValueError("restored preview cannot rederive manual approvals") + derived = approve(expected_slot, parsed_payload["boundary_offset_rows"]) + if approval_payload(derived) != parsed_payload: + raise ValueError(f"reviewed approval for slot {expected_slot} does not match its thumbnail") + approvals[expected_slot] = parsed + + return ValidatedReviewedApproval( + sha256=actual_sha256, + byte_length=len(review_bytes), + reviewed_fingerprint_sha256=fingerprint, + contact_sheet_path=contact_path, + contact_sheet_sha256=contact_row["sha256"], + approvals=approvals, + ) + + +def validate_restored_thumbnails( + thumbnails: object, + review: ValidatedReviewedApproval, +) -> None: + """Cross-check returned thumbnails against the already-pinned review.""" + + if not isinstance(thumbnails, (list, tuple)): + raise ValueError("restored thumbnails must be an ordered sequence") + by_slot = {getattr(item, "slot", None): item for item in thumbnails} + if set(by_slot) != set(range(1, 7)): + raise ValueError("restored thumbnails are not exactly slots 1 through 6") + for slot, approval in review.approvals.items(): + image = getattr(by_slot[slot], "image", None) + expected = approval_payload(approval).get("thumbnail_sha256") + if thumbnail_sha256(np.asarray(image)) != expected: + raise ValueError(f"restored thumbnail for slot {slot} changed after review") diff --git a/negpy/services/roll/native_builder.py b/negpy/services/roll/native_builder.py new file mode 100644 index 00000000..03e4adc2 --- /dev/null +++ b/negpy/services/roll/native_builder.py @@ -0,0 +1,740 @@ +"""Fail-closed native LS-5000 prescan-to-pre-F builder.""" + +from __future__ import annotations + +import base64 +import hashlib +import json +import math +import re +import struct +from dataclasses import dataclass +from typing import Final, cast + +import numpy as np + +from negpy.services.roll import exact_color + + +CHANNELS: Final = ("r", "g", "b") +ANALYZER_SHAPE: Final = (425, 281, 3) +ANALYZER_RESOLUTION_DPI: Final = 285 +DENSITY_SOURCE_RESOLUTION_DPI: Final = 97 +DENSITY_SOURCE_WIRE_BYTES: Final = 6_250_496 +DENSITY_ARITHMETIC: Final = "ls5000-md3-10088810-layout1-u16-proven-inputs-macos-binary64-exact-v6" +FRAME_OWNERSHIP_STATUS: Final = "proven-exact-reservation-preview-registration-and-transport" +DENSITY_PER_FRAME_BINDING_STATUS: Final = "requires-explicit-frame-ownership-receipt" +RESOURCE_SHA256: Final = exact_color.NATIVE_RESOURCE_SHA256 +PARAMETER_ALGORITHM_ID: Final = "ls5000-md3-100100d0-to-1000f470-v1" +CURVE_ALGORITHM_ID: Final = "ls5000-md3-10010c30-pref-v1" + +_SHA256 = re.compile(r"^[0-9a-f]{64}$") +_RESOURCE_B64 = "AAAAAAAADMCOBvAWSFALwPRsVn2utgnAAAAAAAAACMA/NV66SYwHwGb35GGhVgfAZapgVFInBsDBOSNKe4MFwCegibDh6QTAjgbwFkhQA8D0bFZ9rrYBwAAAAAAAAADAMzMzMzMz+79mZmZmZmb2v5qZmZmZmfG/mpmZmZmZ6b8AAAAAAADgv5qZmZmZmcm/mpmZmZmZuT+amZmZmZnZP2ZmZmZmZuY/AAAAAAAA8D/NzMzMzMz0P5qZmZmZmfk/ZmZmZmZm/j+amZmZmZkBQAAAAAAAAARAZmZmZmZmBkDNzMzMzMwIQDMzMzMzMwtAmpmZmZmZDUAAAAAAAAAQQNNNYhBYOdQ/001iEFg51D/TTWIQWDnUP9NNYhBYOdQ/001iEFg51D/TTWIQWDnUP5hMFYxK6tg/F0hQ/Bhz2z9t5/up8dLdPxSuR+F6FOI/c2iR7Xw/5T+kcD0K16PoPzEIrBxaZO0/30+Nl24S8T+mm8QgsHLzP23n+6nx0vU/MzMzMzMz+D/6fmq8dJP6P8HKoUW28/w/hxbZzvdT/z8nMQisHNoAQArXo3A9CgJA7nw/NV46A0DRItv5fmoEQLTIdr6fmgVAmG4Sg8DKBkB7FK5H4foHQF66SQwCKwlAQmDl0CJbCkAlBoGVQ4sLQAisHFpkuwxA7FG4HoXrDUAv3SQGgZXnPy/dJAaBlec/L90kBoGV5z8v3SQGgZXnPy/dJAaBlec/FK5H4XoU6D8dyeU/pN/qP/yp8dJNYuw/PnlYqDXN7T+1FfvL7snwP8zuycNCrfI/irDh6ZWy9D8r9pfdk4f3P807TtGRXPo/b4EExY8x/T+IY13cRgMAQFmGONbFbQFAKqkT0ETYAkD7y+7Jw0IEQMzuycNCrQVAnRGlvcEXB0BuNIC3QIIIQD9XW7G/7AlAEHo2qz5XC0DgnBGlvcEMQLG/7J48LA5AguLHmLuWD0CqglFJnYAQQBIUP8bcNRFAeqUsQxzrEUDjNhrAW6ASQEvIBz2bVRNAJzEIrBxa7D8nMQisHFrsPycxCKwcWuw/JzEIrBxa7D9JnYAmwobtP1D8GHPXEu4/+n5qvHST8D+cxCCwcmjxPxx8YTJVMPI/MCqpE9BE9D9F2PD0Sln2P/p+arx0k/g/GQRWDi2y+z83iUFg5dD+PyuHFtnO9wBAukkMAiuHAkBKDAIrhxYEQNnO91PjpQVAaJHtfD81B0D4U+Olm8QIQIcW2c73UwpAF9nO91PjC0Cmm8QgsHINQDVeukkMAg9AYhBYObRIEECq8dJNYhARQPLSTWIQ2BFAObTIdr6fEkCBlUOLbGcTQMl2vp8aLxRAEFg5tMj2FEBYObTIdr4VQA==" +_RESOURCE = base64.b64decode(_RESOURCE_B64) +if len(_RESOURCE) != 1_024 or hashlib.sha256(_RESOURCE).hexdigest() != RESOURCE_SHA256: + raise RuntimeError("packaged Nikon characteristic resource failed its hash pin") +_RESOURCE_TABLES = tuple(np.array(struct.unpack_from("<32d", _RESOURCE, index * 0x100), dtype=np.float64) for index in range(4)) + + +@dataclass(frozen=True) +class NativeBuilderEvidence: + """Distinct density, analyzer, and final-exposure evidence for one frame.""" + + session_id: str + capture_attempt_id: str + scan_identity: str + slot: int + density_source_wire_sha256: str + density_source_child_sha256: str + calibration_numerators: tuple[int, int, int] + density_f03_denominators: tuple[int, int, int] + densities: tuple[float, float, float] + density_arithmetic: str + frame_ownership_status: str + frame_ownership_receipt: bytes + frame_ownership_receipt_sha256: str + density_evidence_receipt: bytes + density_evidence_receipt_sha256: str + reservation_id: str + batch_session_id: str + preview_sha256: str + preview_identity_sha256: str + transport_table_sha256: str + transport_identity_sha256: str + reviewed_fingerprint_sha256: str + fresh_fingerprint_sha256: str + frame_index: int + frame_total: int + selected_slots: tuple[int, ...] + analyzer_rgb: np.ndarray + analyzer_rgb_sha256: str + analyzer_resolution_dpi: int + analyzer_rectangle: tuple[int, int, int, int] + final_f02_denominators: tuple[int, int, int] + + +@dataclass(frozen=True) +class _AnalyzerOutputs: + all_lower: tuple[float, float, float] + all_max: tuple[float, float, float] + gated_lower: tuple[float, float, float] + gated_max: tuple[float, float, float] + + +@dataclass(frozen=True) +class _BuilderArgs: + A: float + B: float + E: float + C: float + a: float + f: float + g: float + + +def adapt_native_builder_evidence(evidence: object) -> NativeBuilderEvidence: + """Convert Coolscanpy's exact neutral producer at the app boundary. + + The optional scanner dependency stays outside NegPy's builder core. Only + the exact Coolscanpy dataclass is accepted; similarly shaped objects are + deliberately rejected. ``build_native_builder_receipt`` subsequently + revalidates every copied identity, receipt digest, analyzer digest, and + numeric field under NegPy's own contract. + """ + + if type(evidence) is NativeBuilderEvidence: + return evidence + try: + from coolscanpy.protocol.ls5000_single_pass.density import ( + NikonExactBuilderEvidence, + ) + except ImportError as error: + raise exact_color.ExactColorUnavailable("frame native builder evidence has an invalid type") from error + if type(evidence) is not NikonExactBuilderEvidence: + raise exact_color.ExactColorUnavailable("frame native builder evidence has an invalid type") + return NativeBuilderEvidence( + session_id=evidence.session_id, + capture_attempt_id=evidence.capture_attempt_id, + scan_identity=evidence.scan_identity, + slot=evidence.slot, + density_source_wire_sha256=evidence.density_source_wire_sha256, + density_source_child_sha256=evidence.density_source_child_sha256, + calibration_numerators=evidence.calibration_numerators, + density_f03_denominators=evidence.density_f03_denominators, + densities=evidence.densities, + density_arithmetic=evidence.density_arithmetic, + frame_ownership_status=evidence.frame_ownership_status, + frame_ownership_receipt=evidence.frame_ownership_receipt, + frame_ownership_receipt_sha256=evidence.frame_ownership_receipt_sha256, + density_evidence_receipt=evidence.density_evidence_receipt, + density_evidence_receipt_sha256=evidence.density_evidence_receipt_sha256, + reservation_id=evidence.reservation_id, + batch_session_id=evidence.batch_session_id, + preview_sha256=evidence.preview_sha256, + preview_identity_sha256=evidence.preview_identity_sha256, + transport_table_sha256=evidence.transport_table_sha256, + transport_identity_sha256=evidence.transport_identity_sha256, + reviewed_fingerprint_sha256=evidence.reviewed_fingerprint_sha256, + fresh_fingerprint_sha256=evidence.fresh_fingerprint_sha256, + frame_index=evidence.frame_index, + frame_total=evidence.frame_total, + selected_slots=evidence.selected_slots, + analyzer_rgb=evidence.analyzer_rgb, + analyzer_rgb_sha256=evidence.analyzer_rgb_sha256, + analyzer_resolution_dpi=evidence.analyzer_resolution_dpi, + analyzer_rectangle=evidence.analyzer_rectangle, + final_f02_denominators=evidence.final_f02_denominators, + ) + + +def build_native_builder_receipt( + evidence: NativeBuilderEvidence, +) -> exact_color.NativeValidatedBuilderReceipt: + """Derive fresh pre-F LUTs only after every provenance gate closes.""" + + analyzer, analyzer_bytes = _validate_evidence(evidence) + r0, c0, r1, c1 = evidence.analyzer_rectangle + selected = analyzer[r0 : r1 + 1, c0 : c1 + 1].reshape(-1, 3) + exposure = ( + evidence.calibration_numerators[0] / evidence.final_f02_denominators[0], + evidence.calibration_numerators[1] / evidence.final_f02_denominators[1], + evidence.calibration_numerators[2] / evidence.final_f02_denominators[2], + ) + args, controls = _derive_parameters(selected, exposure, evidence.densities) + built_luts = [ + _build_lut(arg, controls[index][0], controls[index][1]).astype(" exact_color.NativeValidatedBuilderReceipt: + """Re-derive a retained snapshot instead of trusting its stored LUTs. + + A writable evidence directory is not an attestation boundary. The raw + analyzer and canonical acquisition receipts therefore reconstruct the + original ``NativeBuilderEvidence`` and run the pinned builder again. Only + byte-identical envelope, evidence, analyzer, and pre-F artifacts can be + promoted back into a trusted receipt. + """ + + try: + document = _parse_canonical_object( + evidence_payload, + label="retained native builder evidence", + ) + ownership = _parse_canonical_object( + frame_ownership_receipt, + label="retained frame ownership", + ) + _parse_canonical_object( + density_evidence_receipt, + label="retained density evidence", + ) + identity = document["identity"] + density = document["density_source"] + analyzer = document["analyzer_source"] + calibration = document["calibration"] + if not all(type(value) is dict for value in (identity, density, analyzer, calibration)): + raise TypeError("retained native builder sections are malformed") + identity_row = cast(dict[str, object], identity) + density_row = cast(dict[str, object], density) + analyzer_row = cast(dict[str, object], analyzer) + calibration_row = cast(dict[str, object], calibration) + if document.get("frame_ownership") != ownership: + raise ValueError("retained native builder evidence disagrees with frame ownership") + analyzer_array = np.frombuffer(analyzer_rgb, dtype=" tuple[np.ndarray, bytes]: + if type(evidence) is not NativeBuilderEvidence: + raise exact_color.ExactColorUnavailable("native builder evidence has an invalid type") + for label, value in ( + ("session_id", evidence.session_id), + ("capture_attempt_id", evidence.capture_attempt_id), + ("scan_identity", evidence.scan_identity), + ): + if type(value) is not str or not value: + raise exact_color.ExactColorUnavailable(f"native builder {label} is missing") + if type(evidence.slot) is not int or not 1 <= evidence.slot <= 40: + raise exact_color.ExactColorUnavailable("native builder slot must be an integer in 1..40") + for label, digest in ( + ("density source wire", evidence.density_source_wire_sha256), + ("density source child", evidence.density_source_child_sha256), + ("frame ownership", evidence.frame_ownership_receipt_sha256), + ("density evidence", evidence.density_evidence_receipt_sha256), + ("preview", evidence.preview_sha256), + ("preview identity", evidence.preview_identity_sha256), + ("transport table", evidence.transport_table_sha256), + ("transport identity", evidence.transport_identity_sha256), + ("reviewed fingerprint", evidence.reviewed_fingerprint_sha256), + ("fresh fingerprint", evidence.fresh_fingerprint_sha256), + ("analyzer RGB", evidence.analyzer_rgb_sha256), + ): + if type(digest) is not str or not _SHA256.fullmatch(digest): + raise exact_color.ExactColorUnavailable(f"native builder {label} SHA-256 is malformed") + if evidence.frame_ownership_status != FRAME_OWNERSHIP_STATUS: + raise exact_color.ExactColorUnavailable("97-dpi density evidence is not proven to belong to this frame") + ownership = _parse_canonical_object(evidence.frame_ownership_receipt, label="frame ownership") + density_receipt = _parse_canonical_object(evidence.density_evidence_receipt, label="density evidence") + if hashlib.sha256(evidence.frame_ownership_receipt).hexdigest() != evidence.frame_ownership_receipt_sha256: + raise exact_color.ExactColorUnavailable("frame ownership receipt does not match its SHA-256") + if hashlib.sha256(evidence.density_evidence_receipt).hexdigest() != evidence.density_evidence_receipt_sha256: + raise exact_color.ExactColorUnavailable("density evidence receipt does not match its SHA-256") + _validate_ownership_binding(evidence, ownership, density_receipt) + if evidence.density_arithmetic != DENSITY_ARITHMETIC: + raise exact_color.ExactColorUnavailable("density arithmetic is not x87 bit-certified") + for label, values in ( + ("calibration numerators", evidence.calibration_numerators), + ("density f03 denominators", evidence.density_f03_denominators), + ("final f02 denominators", evidence.final_f02_denominators), + ): + if type(values) is not tuple or len(values) != 3 or any(type(value) is not int or not 1 <= value <= 0xFFFFFFFF for value in values): + raise exact_color.ExactColorUnavailable(f"native builder {label} must contain three nonzero uint32 values") + if evidence.density_f03_denominators == evidence.final_f02_denominators: + raise exact_color.ExactColorUnavailable("density f03 and final f02 exposure triplets were conflated") + if ( + type(evidence.densities) is not tuple + or len(evidence.densities) != 3 + or any(type(value) is not float or not math.isfinite(value) for value in evidence.densities) + ): + raise exact_color.ExactColorUnavailable("native builder densities must contain three finite doubles") + if evidence.analyzer_resolution_dpi != ANALYZER_RESOLUTION_DPI: + raise exact_color.ExactColorUnavailable("native builder analyzer source must be the 285-dpi pass") + analyzer = np.asarray(evidence.analyzer_rgb) + if analyzer.dtype != np.uint16 or analyzer.shape != ANALYZER_SHAPE: + raise exact_color.ExactColorUnavailable(f"native builder analyzer RGB must be uint16 with shape {ANALYZER_SHAPE}") + snapshot = np.array(analyzer, dtype=" dict[str, object]: + return { + "batch_session_id": evidence.batch_session_id, + "capture_attempt_id": evidence.capture_attempt_id, + "preview_identity_sha256": evidence.preview_identity_sha256, + "preview_sha256": evidence.preview_sha256, + "reservation_id": evidence.reservation_id, + "scan_identity": evidence.scan_identity, + "session_id": evidence.session_id, + "slot": evidence.slot, + } + + +def _evidence_payload(evidence: NativeBuilderEvidence, analyzer: np.ndarray) -> dict[str, object]: + return { + "algorithm": { + "curve": CURVE_ALGORITHM_ID, + "parameter_derivation": PARAMETER_ALGORITHM_ID, + }, + "analyzer_source": { + "final_f02_denominators_raw_10ns_rgb": list(evidence.final_f02_denominators), + "geometry": list(analyzer.shape), + "rectangle_inclusive": list(evidence.analyzer_rectangle), + "resolution_dpi": evidence.analyzer_resolution_dpi, + "rgb_bytes": len(analyzer.tobytes()), + "rgb_sha256": evidence.analyzer_rgb_sha256, + }, + "calibration": {"read8c_numerators_rgb": list(evidence.calibration_numerators)}, + "density_source": { + "arithmetic": evidence.density_arithmetic, + "child_buffer_sha256": evidence.density_source_child_sha256, + "densities_binary64_hex_rgb": [float(value).hex() for value in evidence.densities], + "f03_denominators_raw_10ns_rgb": list(evidence.density_f03_denominators), + "resolution_dpi": DENSITY_SOURCE_RESOLUTION_DPI, + "wire_sha256": evidence.density_source_wire_sha256, + }, + "density_evidence": { + "receipt_sha256": evidence.density_evidence_receipt_sha256, + "scope": "reservation-preview", + }, + "frame_ownership": json.loads(evidence.frame_ownership_receipt), + "identity": _identity_payload(evidence), + "resource": {"bytes": len(_RESOURCE), "sha256": RESOURCE_SHA256}, + "schema": "negpy.native-stage1-builder-evidence", + "version": 1, + } + + +def _canonical_json(value: dict[str, object]) -> bytes: + return json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=False, allow_nan=False).encode("utf-8") + + +def _parse_canonical_object(payload: bytes, *, label: str) -> dict[str, object]: + if type(payload) is not bytes or not payload: + raise exact_color.ExactColorUnavailable(f"native builder {label} receipt is missing") + try: + parsed = json.loads(payload) + except (UnicodeDecodeError, ValueError, RecursionError) as error: + raise exact_color.ExactColorUnavailable(f"native builder {label} receipt is invalid JSON") from error + if type(parsed) is not dict or _canonical_json(parsed) != payload: + raise exact_color.ExactColorUnavailable(f"native builder {label} receipt is not canonical") + return parsed + + +def _validate_ownership_binding( + evidence: NativeBuilderEvidence, + ownership: dict[str, object], + density_receipt: dict[str, object], +) -> None: + expected = { + "schema_version": 1, + "scope": "reservation-preview-frame", + "binding_status": FRAME_OWNERSHIP_STATUS, + "session_reservation_retained": True, + "reservation_id": evidence.reservation_id, + "batch_session_id": evidence.batch_session_id, + "preview_sha256": evidence.preview_sha256, + "preview_identity_sha256": evidence.preview_identity_sha256, + "transport_table_sha256": evidence.transport_table_sha256, + "transport_identity_sha256": evidence.transport_identity_sha256, + "reviewed_fingerprint_sha256": evidence.reviewed_fingerprint_sha256, + "fresh_fingerprint_sha256": evidence.fresh_fingerprint_sha256, + "frame_capture_attempt_id": evidence.capture_attempt_id, + "frame_index": evidence.frame_index, + "frame_total": evidence.frame_total, + "selected_slots": list(evidence.selected_slots), + "selected_slot": evidence.slot, + } + if ownership != expected: + raise exact_color.ExactColorUnavailable("frame ownership receipt does not bind the native builder acquisition") + transport_material: dict[str, object] = { + "reservation_id": evidence.reservation_id, + "batch_session_id": evidence.batch_session_id, + "preview_sha256": evidence.preview_sha256, + "preview_identity_sha256": evidence.preview_identity_sha256, + "transport_table_sha256": evidence.transport_table_sha256, + "reviewed_fingerprint_sha256": evidence.reviewed_fingerprint_sha256, + "fresh_fingerprint_sha256": evidence.fresh_fingerprint_sha256, + "selected_slots": list(evidence.selected_slots), + } + if hashlib.sha256(_canonical_json(transport_material)).hexdigest() != evidence.transport_identity_sha256: + raise exact_color.ExactColorUnavailable("frame ownership transport identity digest is invalid") + if ( + type(evidence.reservation_id) is not str + or not evidence.reservation_id + or type(evidence.batch_session_id) is not str + or not evidence.batch_session_id + or evidence.session_id != evidence.reservation_id + or evidence.reservation_id != evidence.batch_session_id + or type(evidence.frame_index) is not int + or type(evidence.frame_total) is not int + or not 1 <= evidence.frame_index <= evidence.frame_total + or type(evidence.selected_slots) is not tuple + or len(evidence.selected_slots) != evidence.frame_total + or evidence.slot not in evidence.selected_slots + or evidence.selected_slots[evidence.frame_index - 1] != evidence.slot + or any(type(slot) is not int or not 1 <= slot <= 40 for slot in evidence.selected_slots) + or len(set(evidence.selected_slots)) != len(evidence.selected_slots) + ): + raise exact_color.ExactColorUnavailable("frame ownership batch identity is malformed") + _validate_density_receipt_binding(evidence, density_receipt) + + +def _validate_density_receipt_binding( + evidence: NativeBuilderEvidence, + density_receipt: dict[str, object], +) -> None: + if set(density_receipt) != { + "schema_version", + "scope", + "per_frame_binding_status", + "preview_identity_sha256", + "source_payload_bytes", + "calibration_binding", + "source_binding", + "exposure_binding", + "result", + }: + raise exact_color.ExactColorUnavailable("density evidence receipt fields are incomplete") + calibration_binding = density_receipt.get("calibration_binding") + source_binding = density_receipt.get("source_binding") + exposure_binding = density_receipt.get("exposure_binding") + result = density_receipt.get("result") + if not all(type(value) is dict for value in (calibration_binding, source_binding, exposure_binding, result)): + raise exact_color.ExactColorUnavailable("density evidence bindings are malformed") + calibration_binding = cast(dict[str, object], calibration_binding) + source_binding = cast(dict[str, object], source_binding) + exposure_binding = cast(dict[str, object], exposure_binding) + result = cast(dict[str, object], result) + calibration = calibration_binding.get("calibration") + if type(calibration) is not dict: + raise exact_color.ExactColorUnavailable("density calibration receipt is malformed") + calibration = cast(dict[str, object], calibration) + binding_identities = tuple( + (binding.get("session_id"), binding.get("capture_attempt_id"), binding.get("scan_identity")) + for binding in (source_binding, exposure_binding, result) + ) + calibration_identity = ( + calibration.get("session_id"), + calibration_binding.get("capture_attempt_id"), + calibration_binding.get("scan_identity"), + ) + density_hex = [struct.pack(">d", value).hex() for value in evidence.densities] + if ( + density_receipt.get("schema_version") != 1 + or density_receipt.get("scope") != "reservation-preview" + or density_receipt.get("per_frame_binding_status") != DENSITY_PER_FRAME_BINDING_STATUS + or density_receipt.get("preview_identity_sha256") != evidence.preview_identity_sha256 + or density_receipt.get("source_payload_bytes") != DENSITY_SOURCE_WIRE_BYTES + or any(identity != calibration_identity for identity in binding_identities) + or calibration_identity[0] != evidence.reservation_id + or calibration_identity[2] != evidence.scan_identity + or source_binding.get("resolution_dpi") != DENSITY_SOURCE_RESOLUTION_DPI + or source_binding.get("wire_sha256") != evidence.preview_sha256 + or source_binding.get("wire_sha256") != evidence.density_source_wire_sha256 + or source_binding.get("child_buffer_sha256") != evidence.density_source_child_sha256 + or calibration.get("numerators_rgb") != list(evidence.calibration_numerators) + or exposure_binding.get("density_f03_exposures_raw_10ns_rgb") != list(evidence.density_f03_denominators) + or result.get("algorithm_id") != evidence.density_arithmetic + or result.get("promotable") is not True + or result.get("source_wire_sha256") != evidence.density_source_wire_sha256 + or result.get("source_child_buffer_sha256") != evidence.density_source_child_sha256 + or result.get("numerators_rgb") != list(evidence.calibration_numerators) + or result.get("density_f03_denominators_raw_10ns_rgb") != list(evidence.density_f03_denominators) + or result.get("densities_rgb") != list(evidence.densities) + or result.get("density_binary64_be_hex_rgb") != density_hex + ): + raise exact_color.ExactColorUnavailable("density evidence belongs to a different preview or reservation") + + +def _curve(xt: np.ndarray, yt: np.ndarray, value: np.ndarray) -> np.ndarray: + index = np.full(value.shape, 32, dtype=np.int64) + for candidate in range(31, 0, -1): + index = np.where(value < xt[candidate], candidate, index) + hit = index < 32 + bounded = np.clip(index, 1, 31) + x1, x0, y1, y0 = xt[bounded], xt[bounded - 1], yt[bounded], yt[bounded - 1] + denominator = x1 - x0 + interpolated = np.where( + denominator != 0, + y0 + (value - x0) * (y1 - y0) / np.where(denominator == 0, 1, denominator), + y0, + ) + return np.where(hit, interpolated, yt[31]) + + +def _histogram(values: np.ndarray) -> np.ndarray: + return (np.bincount(values.astype(np.int64), minlength=65_536) % 65_536).astype(np.int64) + + +def _lower_cutoff(histogram: np.ndarray, population: int) -> float: + threshold = math.trunc(0.0042 * population) + hit = np.flatnonzero(np.cumsum(histogram) >= threshold) + return float(hit[0]) if hit.size else 0.0 + + +def _maximum(histogram: np.ndarray) -> float: + hit = np.flatnonzero(histogram) + return float(hit[-1]) if hit.size else 0.0 + + +def _analyse(pixels: np.ndarray, exposure: tuple[float, float, float], densities: tuple[float, float, float]) -> _AnalyzerOutputs: + count = pixels.shape[0] + scaled = pixels.astype(np.float64) * np.asarray(exposure, dtype=np.float64) + density = np.asarray(densities, dtype=np.float64) + with np.errstate(divide="ignore", invalid="ignore"): + ratio01 = scaled[:, 0] / (10.0 ** (density[1] - density[0]) * scaled[:, 1]) + ratio02 = scaled[:, 0] / (10.0 ** (density[2] - density[0]) * scaled[:, 2]) + ratio12 = scaled[:, 1] / (10.0 ** (density[2] - density[1]) * scaled[:, 2]) + + def good(ratio: np.ndarray) -> np.ndarray: + return (ratio > 1.0 / 3.0) & (ratio < 3.0) + + good01, good02, good12 = good(ratio01), good(ratio02), good(ratio12) + passes = (good01 | good02, good01 | good12, good02 | good12) + all_lower: list[float] = [] + all_max: list[float] = [] + gated_lower: list[float] = [] + gated_max: list[float] = [] + for channel in range(3): + values = pixels[:, channel] + histogram = _histogram(values) + all_lower.append(_lower_cutoff(histogram, count)) + all_max.append(_maximum(histogram)) + keep = passes[channel].copy() + failing = np.flatnonzero(~keep) + if failing.size: + budget = math.ceil(0.9 * count) + if failing.size > budget: + keep[failing[budget:]] = True + gated = values[keep] + histogram = _histogram(gated) + gated_lower.append(_lower_cutoff(histogram, gated.size)) + gated_max.append(_maximum(histogram)) + return _AnalyzerOutputs( + (all_lower[0], all_lower[1], all_lower[2]), + (all_max[0], all_max[1], all_max[2]), + (gated_lower[0], gated_lower[1], gated_lower[2]), + (gated_max[0], gated_max[1], gated_max[2]), + ) + + +def _scale(outputs: _AnalyzerOutputs, exposure: tuple[float, float, float]) -> tuple[tuple[float, ...], ...]: + zlo: list[float] = [] + zhi: list[float] = [] + Zlo: list[float] = [] + Zhi: list[float] = [] + for channel in range(3): + all_lo = max(1.0, outputs.all_lower[channel] * exposure[channel]) + all_hi = max(1.0, outputs.all_max[channel] * exposure[channel]) + gate_lo = outputs.gated_lower[channel] * exposure[channel] + gate_hi = outputs.gated_max[channel] * exposure[channel] + zlo.append(math.log10(65_535.0 / gate_lo)) + zhi.append(math.log10(65_535.0 / gate_hi)) + Zlo.append(math.log10(65_535.0 / all_lo)) + Zhi.append(math.log10(65_535.0 / all_hi)) + return tuple(zlo), tuple(zhi), tuple(Zlo), tuple(Zhi) + + +def _derive_parameters( + pixels: np.ndarray, + exposure: tuple[float, float, float], + densities: tuple[float, float, float], +) -> tuple[list[_BuilderArgs], list[tuple[np.ndarray, np.ndarray]]]: + outputs = _analyse(pixels, exposure, densities) + zlo, zhi, Zlo, Zhi = _scale(outputs, exposure) + D = tuple(min(densities[channel], zhi[channel]) for channel in range(3)) + X = [table + (D[channel] - table[0]) for channel, table in enumerate(_RESOURCE_TABLES[1:])] + master = _RESOURCE_TABLES[0] + + def first(channel: int, value: float) -> float: + return float(_curve(X[channel], master, np.array([value], dtype=np.float64))[0]) + + reference = [first(channel, D[channel] + 0.15) for channel in range(3)] + Lo = [first(channel, zlo[channel]) for channel in range(3)] + Hi = [first(channel, zhi[channel]) for channel in range(3)] + lo = [Lo[channel] - reference[channel] for channel in range(3)] + hi = [Hi[channel] - reference[channel] for channel in range(3)] + wh0 = 10.0 ** (-abs(hi[0] - hi[1])) + wh2 = 10.0 ** (-abs(hi[2] - hi[1])) + ws0 = 3.0 ** (-abs((lo[0] - hi[0]) - (lo[1] - hi[1]))) + ws2 = 3.0 ** (-abs((lo[2] - hi[2]) - (lo[1] - hi[1]))) + tone = min(1.0, 10.0 ** (max(lo) - 0.8)) + p = tone * ((1.0 - wh0) + wh0 * ws0) + q = tone * ((1.0 - wh2) + wh2 * ws2) + offset0 = p * (Lo[0] - Lo[1]) + wh0 * (1.0 - p) * (Hi[0] - Hi[1]) + offset1 = q * (Lo[1] - Lo[2]) + wh2 * (1.0 - q) * (Hi[1] - Hi[2]) + Y2 = [master - offset0, master.copy(), master + offset1] + + def second(channel: int, value: float) -> float: + return float(_curve(X[channel], Y2[channel], np.array([value], dtype=np.float64))[0]) + + reference2 = [second(channel, D[channel] + 0.15) for channel in range(3)] + vlo = [second(channel, zlo[channel]) - reference2[channel] for channel in range(3)] + vhi = [second(channel, zhi[channel]) - reference2[channel] for channel in range(3)] + E0 = min(vhi) + a = [min(1.0, 10.0 ** (-(vhi[channel] + 0.23) * 4.0)) for channel in range(3)] + m = max(a) + L = max(vlo) + A0 = max(second(channel, min(Zlo[channel], X[channel][31])) for channel in range(3)) + C = [second(channel, max(Zhi[channel], X[channel][0])) for channel in range(3)] + B0 = min(C) + difference0 = A0 - B0 + if difference0 >= 0.6: + B = B0 + A1 = A0 + 0.05 + else: + delta = 0.6 - difference0 + B = B0 - (1.0 - m) * delta + A1 = max(B + 0.6, A0 + 0.05) + difference = A1 - B + te = min(1.9, 1.9 * 0.4 ** (E0 + 0.23)) + E = min(1.9, max(difference, te)) + A = A1 + (difference / E) * (1.2 - L) if L < 1.2 else A1 + f = [float(_curve(Y2[channel], X[channel], np.array([A], dtype=np.float64))[0]) for channel in range(3)] + g = [float(_curve(Y2[channel], X[channel], np.array([B], dtype=np.float64))[0]) for channel in range(3)] + args = [_BuilderArgs(A, B, E, C[channel], a[channel], f[channel], g[channel]) for channel in range(3)] + return args, list(zip(X, Y2, strict=True)) + + +def _build_lut(arg: _BuilderArgs, xt: np.ndarray, yt: np.ndarray) -> np.ndarray: + slope = float(min(max((arg.A - ((1.0 - arg.a) * arg.B + arg.a * arg.C)) / arg.E, 0.01), 4.0)) + clip = max(0, min(int(10.0 ** (-(arg.f - arg.g)) * 65_535.0), 65_535)) + lut = np.zeros(65_536, dtype=np.uint16) + lut[: clip + 1] = 0xFFFF + if clip + 1 < 65_536: + index = np.arange(clip + 1, 65_536, dtype=np.float64) + tone = _curve(xt, yt, np.log10(65_535.0 / index) + arg.g) + output = 65_535.0 * np.power(10.0, (arg.A - tone) * (-1.0 / slope)) + lut[clip + 1 :] = (output.astype(np.int64) & 0xFFFF).astype(np.uint16) + return lut + + +__all__ = [ + "ANALYZER_RESOLUTION_DPI", + "DENSITY_SOURCE_RESOLUTION_DPI", + "NativeBuilderEvidence", + "adapt_native_builder_evidence", + "build_native_builder_receipt", + "rebuild_retained_native_builder_receipt", +] diff --git a/negpy/services/roll/nikon_icc.py b/negpy/services/roll/nikon_icc.py new file mode 100644 index 00000000..c126993a --- /dev/null +++ b/negpy/services/roll/nikon_icc.py @@ -0,0 +1,65 @@ +"""Package-safe Nikon Adobe RGB profile used by exact C-41 positives. + +Nikon Scan's LS-5000 C-41 CMS-on TIFFs embed this exact 492-byte profile. +Keeping the tiny payload in source avoids a filesystem-relative runtime asset +that can disappear from a wheel or frozen desktop app. The decoded bytes are +validated before use; exact output must fail closed if packaging or source +corruption changes either their size or identity. +""" + +from __future__ import annotations + +import base64 +import hashlib +from functools import lru_cache +from typing import Final + + +NIKON_ADOBE_RGB_PROFILE_NAME: Final = "Nikon Adobe RGB 4.0.0.3000" +NIKON_ADOBE_RGB_PROFILE_BYTES: Final = 492 +NIKON_ADOBE_RGB_PROFILE_SHA256: Final = "a8d0d753bd6129357cc2647435ce675e8637a679eb526fa180fba460874ce1d3" + +_NIKON_ADOBE_RGB_PROFILE_BASE64: Final = ( + "AAAB7E5LT04CIAAAbW50clJHQiBYWVogB88ADAAHABIAOwAWYWNzcEFQUEwAAAAA" + "bm9uZQAAAAEAAAAAAAAAAAAAAAAAAPbWAAEAAAAA0y0AAAAAAAAAAAAAAAAAAAAA" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJZGVzYwAAAPAAAABN" + "clhZWgAAAUAAAAAUZ1hZWgAAAVQAAAAUYlhZWgAAAWgAAAAUclRSQwAAAXwAAAAO" + "Z1RSQwAAAYwAAAAOYlRSQwAAAZwAAAAOd3RwdAAAAawAAAAUY3BydAAAAcAAAAAs" + "ZGVzYwAAAAAAAAAbTmlrb24gQWRvYmUgUkdCIDQuMC4wLjMwMDAAAAAAAAAAAAAA" + "ABtOaWtvbiBBZG9iZSBSR0IgNC4wLjAuMzAwMAAAAABYWVogAAAAAAAAnBkAAE+m" + "AAAE/FhZWiAAAAAAAAA0iwAAoCsAAA+VWFlaIAAAAAAAACYyAAAQLwAAvqBjdXJ2" + "AAAAAAAAAAECMwAAY3VydgAAAAAAAAABAjMAAGN1cnYAAAAAAAAAAQIzAABYWVog" + "AAAAAAAA81QAAQAAAAEWz3RleHQAAAAATmlrb24gSW5jLiAmIE5pa29uIENvcnBv" + "cmF0aW9uIDIwMDEA" +) + + +class NikonICCProfileError(RuntimeError): + """The source-embedded exact-output profile failed identity checks.""" + + +@lru_cache(maxsize=1) +def nikon_adobe_rgb_profile() -> bytes: + """Return the immutable, identity-checked NKAdobe ICC payload.""" + + try: + profile = base64.b64decode(_NIKON_ADOBE_RGB_PROFILE_BASE64, validate=True) + except ValueError as error: + raise NikonICCProfileError("embedded Nikon Adobe RGB profile is not valid base64") from error + if len(profile) != NIKON_ADOBE_RGB_PROFILE_BYTES: + raise NikonICCProfileError(f"embedded Nikon Adobe RGB profile is {len(profile)} bytes, expected {NIKON_ADOBE_RGB_PROFILE_BYTES}") + digest = hashlib.sha256(profile).hexdigest() + if digest != NIKON_ADOBE_RGB_PROFILE_SHA256: + raise NikonICCProfileError("embedded Nikon Adobe RGB profile does not match its pinned SHA-256") + return profile + + +def profile_receipt_binding() -> dict[str, str | int]: + """Receipt fields for the profile after reusing the validation boundary.""" + + nikon_adobe_rgb_profile() + return { + "name": NIKON_ADOBE_RGB_PROFILE_NAME, + "bytes": NIKON_ADOBE_RGB_PROFILE_BYTES, + "sha256": NIKON_ADOBE_RGB_PROFILE_SHA256, + } diff --git a/negpy/services/roll/portable_builder.py b/negpy/services/roll/portable_builder.py new file mode 100644 index 00000000..b145899b --- /dev/null +++ b/negpy/services/roll/portable_builder.py @@ -0,0 +1,194 @@ +"""Portable application of validated Nikon Stage-1 builder artifacts. + +The pre-F LUTs come from either the validated Stage-3 replay bridge or the +identity-bound native builder. This applicator composes each plane in the +recovered order ``F[B_c(i)]`` with the pinned LS5000.md3 fixed output LUT, +then applies the three composed u16 LUTs to repaired RGB in bounded chunks. +""" + +from __future__ import annotations + +import hashlib +import json +import os +import stat +from pathlib import Path +from typing import Final + +import numpy as np + +from negpy.services.roll import exact_color +from negpy.services.roll.exact_color import ( + BuilderReceipt, + ExactColorIntegrityError, + ExactColorUnavailable, + Stage1BuilderResult, + VerifiedBuilderApplicationReceipt, +) + + +ALGORITHM_ID: Final = "ls5000-md3-pref-fixed-postf-v1" +FIXED_LUT_FILENAME: Final = "ls5000-fixed-output-lut-u16le.bin" +FIXED_LUT_SHA256: Final = exact_color.FIXED_COMPOSITION_SHA256 +FIXED_LUT_BYTES: Final = 65_536 * 2 +DEFAULT_CHUNK_PIXELS: Final = 65_536 +MAX_CHUNK_PIXELS: Final = 262_144 + + +class PortableStage1Builder: + """Verified, chunked pre-F → fixed post-F Stage-1 input builder.""" + + def __init__( + self, + *, + assets_dir: Path | None = None, + chunk_pixels: int = DEFAULT_CHUNK_PIXELS, + ) -> None: + if type(chunk_pixels) is not int or not 1 <= chunk_pixels <= MAX_CHUNK_PIXELS: + raise ExactColorUnavailable(f"chunk_pixels must be an integer in 1..{MAX_CHUNK_PIXELS}") + self._chunk_pixels = chunk_pixels + self.assets_dir = ( + Path(__file__).resolve().parents[2] / "assets" / "portable_builder" + if assets_dir is None + else Path(assets_dir).expanduser().resolve() + ) + fixed_payload = _read_verified_file( + self.assets_dir / FIXED_LUT_FILENAME, + expected_sha256=FIXED_LUT_SHA256, + expected_bytes=FIXED_LUT_BYTES, + ) + self._fixed_lut = np.frombuffer(fixed_payload, dtype=" int: + return self._chunk_pixels + + def apply( + self, + rgb: np.ndarray, + *, + builder_receipt: BuilderReceipt, + ) -> Stage1BuilderResult: + exact_color.builder_receipt_payload(builder_receipt) + source_hash = exact_color.rgb16_content_sha256(rgb) + source = np.array(rgb, dtype=np.uint16, order="C", copy=True) + source.setflags(write=False) + flat_source = source.reshape(-1, 3) + + pre_f = [np.frombuffer(blob, dtype=" bytes: + try: + before = path.lstat() + if stat.S_ISLNK(before.st_mode) or not stat.S_ISREG(before.st_mode): + raise OSError("not a regular non-symlink file") + with path.open("rb") as stream: + opened = os.fstat(stream.fileno()) + payload = stream.read() + after = os.fstat(stream.fileno()) + except OSError as error: + raise ExactColorUnavailable(f"fixed composition LUT is unavailable: {path}: {error}") from error + if not (_identity(before) == _identity(opened) == _identity(after)): + raise ExactColorIntegrityError(f"fixed composition LUT changed while being read: {path}") + if len(payload) != expected_bytes: + raise ExactColorIntegrityError(f"fixed composition LUT byte size mismatch: {len(payload)} != {expected_bytes}") + actual = hashlib.sha256(payload).hexdigest() + if actual != expected_sha256: + raise ExactColorIntegrityError(f"fixed composition LUT hash mismatch: {path}: {actual} != {expected_sha256}") + return payload + + +def _identity(value: os.stat_result) -> tuple[int, int, int, int, int]: + return ( + value.st_dev, + value.st_ino, + value.st_size, + value.st_mtime_ns, + value.st_ctime_ns, + ) + + +def _canonical_json(value: dict[str, object]) -> bytes: + return json.dumps( + value, + sort_keys=True, + separators=(",", ":"), + ensure_ascii=False, + allow_nan=False, + ).encode("utf-8") + + +__all__ = [ + "ALGORITHM_ID", + "DEFAULT_CHUNK_PIXELS", + "FIXED_LUT_BYTES", + "FIXED_LUT_FILENAME", + "FIXED_LUT_SHA256", + "MAX_CHUNK_PIXELS", + "PortableStage1Builder", +] diff --git a/negpy/services/roll/portable_cms.py b/negpy/services/roll/portable_cms.py new file mode 100644 index 00000000..0c77a8f4 --- /dev/null +++ b/negpy/services/roll/portable_cms.py @@ -0,0 +1,510 @@ +"""Production adapter for the verified DLL-free two-stage CML4 evaluator. + +The integer implementation is reused byte-for-byte from +'portable_oracle_evaluator.py'. This wrapper owns production concerns: +stable/hash-checked asset loading, strict validation of the packaged 12-event +zero-mismatch receipt, bounded full-frame chunking, validated builder-evidence +binding, and an immutable CMS receipt. + +Scope is intentionally narrow. It evaluates only the captured Stage 1 then +Stage 2 CML transforms. It does not implement or invent the upstream +per-frame builder; callers must first apply the separately verified Stage-1 +builder and present that computed RGB at this boundary. +""" + +from __future__ import annotations + +import hashlib +import json +import os +import stat +import sys +from pathlib import Path +from types import MappingProxyType, ModuleType +from typing import Final, Mapping, cast + +import numpy as np + +from negpy.services.roll import exact_color +from negpy.services.roll.exact_color import ( + ExactColorIntegrityError, + ExactColorResult, + ExactColorUnavailable, + BuilderReceipt, +) + +ORACLE_SOURCE_SHA256: Final = exact_color.CMS_ORACLE_SOURCE_SHA256 +ORACLE_VALIDATION_RECEIPT_SHA256: Final = exact_color.CMS_VALIDATION_RECEIPT_SHA256 +VALIDATION_RECEIPT_FILENAME: Final = "portable-oracle-receipt.json" +VALIDATION_RECEIPT_BYTES: Final = 4_014 +VALIDATION_RECEIPT_SHA256: Final = ORACLE_VALIDATION_RECEIPT_SHA256 +ALGORITHM_ID: Final = exact_color.CMS_ALGORITHM_ID +DEFAULT_CHUNK_PIXELS: Final = 65_536 +MAX_CHUNK_PIXELS: Final = 262_144 +PACKAGED_ASSET_BYTES: Final = 2_506_760 +ASSET_SHA256: Mapping[str, str] = MappingProxyType(dict(exact_color.CMS_ASSET_SHA256)) +_ORACLE_MODULE_NAME = "negpy.services.roll.portable_oracle_evaluator" + +_VALIDATION_EVENTS = { + "stage1": { + "30": (32_448, 32_448), + "34": (16_848, 32_448), + "38": (22_464, 22_464), + "42": (11_664, 22_464), + "47": (32_448, 32_448), + "52": (16_848, 32_448), + }, + "stage2": { + "31": (32_448, 32_448), + "35": (16_848, 32_448), + "39": (22_464, 22_464), + "43": (11_664, 22_464), + "48": (32_448, 32_448), + "53": (16_848, 32_448), + }, +} + +_ASSET_LAYOUT = { + "lch-atan-u16le.bin": (" None: + arrays: dict[str, np.ndarray] = {} + for name, (dtype, shape) in _ASSET_LAYOUT.items(): + value = np.frombuffer(payloads[name], dtype=dtype).reshape(shape) + value.setflags(write=False) + arrays[name] = value + self.atan = arrays["lch-atan-u16le.bin"] + self.sincos = arrays["lch-sincos-i16le.bin"] + self.reciprocal = arrays["lch-reciprocal-u16le.bin"] + self.s1_clut = arrays["cml4-stage1-clut0.bin"] + self.s1_input = arrays["cml4-stage1-input-lut0.bin"] + self.s1_output = arrays["cml4-stage1-output-lut0.bin"] + self.s2_clut = arrays["cml4-stage2-clut0.bin"] + self.s2_input = arrays["cml4-stage2-input-lut0.bin"] + self.s2_output = arrays["cml4-stage2-output-lut0.bin"] + self._lab_to_lch_codes = oracle.lab_to_lch_codes + self._lch_to_lab_codes = oracle.lch_to_lab_codes + self._lookup_three = oracle.lookup_three + self._optimized_trilinear = oracle.optimized_trilinear + + def stage1(self, source: np.ndarray) -> np.ndarray: + working = self._lookup_three(self.s1_input, source) + working = self._optimized_trilinear(self.s1_clut, working) + working = self._lab_to_lch_codes(working, self.atan) + return self._lookup_three(self.s1_output, working) + + def stage2(self, source: np.ndarray) -> np.ndarray: + # Preserve the recovered hue bypass: Stage-2 input LUT plane 2 is not + # used by the active CML4 path. + working = source.copy() + working[:, 0] = self.s2_input[0, working[:, 0]] + working[:, 1] = self.s2_input[1, working[:, 1]] + working = self._lch_to_lab_codes(working, self.sincos, self.reciprocal) + working = self._optimized_trilinear(self.s2_clut, working) + return self._lookup_three(self.s2_output, working) + + +class PortableCMSOnEvaluator: + """Chunked implementation of VerifiedPortableCMSEvaluator. + + Construction verifies the byte-identical oracle source and reads every + required transform asset plus the exact validation receipt once through a + stable-file check. No DLL, Wine process, scanner, VM, or external + repository is used at runtime. + """ + + def __init__( + self, + *, + assets_dir: Path | None = None, + chunk_pixels: int = DEFAULT_CHUNK_PIXELS, + ) -> None: + if type(chunk_pixels) is not int or not 1 <= chunk_pixels <= MAX_CHUNK_PIXELS: + raise ExactColorUnavailable(f"chunk_pixels must be an integer in 1..{MAX_CHUNK_PIXELS}") + self._chunk_pixels = chunk_pixels + self.assets_dir = ( + Path(__file__).resolve().parents[2] / "assets" / "portable_cms" + if assets_dir is None + else Path(assets_dir).expanduser().resolve() + ) + validation_payload = _read_verified_payload( + self.assets_dir / VALIDATION_RECEIPT_FILENAME, + label="portable CMS validation receipt", + expected_bytes=VALIDATION_RECEIPT_BYTES, + ) + validation_digest = hashlib.sha256(validation_payload).hexdigest() + if validation_digest != VALIDATION_RECEIPT_SHA256: + raise ExactColorIntegrityError( + "portable CMS validation receipt hash mismatch: " + f"{validation_digest} != {VALIDATION_RECEIPT_SHA256}" + ) + self._validation_summary = MappingProxyType( + _validate_validation_receipt(validation_payload) + ) + oracle = _load_verified_oracle() + payloads = _read_verified_assets(self.assets_dir) + self._transforms = _LoadedTransforms(payloads, oracle) + + @property + def chunk_pixels(self) -> int: + return self._chunk_pixels + + def evaluate( + self, + rgb: np.ndarray, + *, + builder_receipt: BuilderReceipt, + ) -> ExactColorResult: + input_hash = exact_color.rgb16_content_sha256(rgb) + exact_color.builder_receipt_payload(builder_receipt) + + source = np.array(rgb, dtype=np.uint16, order="C", copy=True) + source.setflags(write=False) + flat_source = source.reshape(-1, 3) + output = np.empty_like(source) + flat_output = output.reshape(-1, 3) + for start in range(0, flat_source.shape[0], self.chunk_pixels): + stop = min(start + self.chunk_pixels, flat_source.shape[0]) + stage1 = self._transforms.stage1(flat_source[start:stop]) + flat_output[start:stop] = self._transforms.stage2(stage1) + + if exact_color.rgb16_content_sha256(source) != input_hash: + raise ExactColorIntegrityError("portable CMS evaluator mutated its Stage-1 input") + output_hash = exact_color.rgb16_content_sha256(output) + output.setflags(write=False) + cms_payload = _canonical_json( + { + "algorithm": ALGORITHM_ID, + "assets": dict(sorted(ASSET_SHA256.items())), + "builder_receipt_sha256": builder_receipt.sha256, + "chunk_pixels": self.chunk_pixels, + "dll_free": True, + "input_rgb_sha256": input_hash, + "kind": exact_color.CMS_RECEIPT_KIND, + "oracle_source": { + "path": "portable_oracle_evaluator.py", + "sha256": ORACLE_SOURCE_SHA256, + }, + "output_rgb_sha256": output_hash, + "scope": exact_color.CMS_SCOPE, + "stage_order": ["stage1", "stage2"], + "upstream_builder_included": False, + "validation": { + "events": self._validation_summary["events"], + "full_payload_mismatched_bytes": self._validation_summary[ + "full_payload_mismatched_bytes" + ], + "full_payload_total_bytes": self._validation_summary[ + "full_payload_total_bytes" + ], + "mismatched_u16": self._validation_summary["mismatched_u16"], + "receipt_sha256": ORACLE_VALIDATION_RECEIPT_SHA256, + "total_u16": self._validation_summary["total_u16"], + }, + "version": exact_color.CMS_RECEIPT_VERSION, + } + ) + cms_receipt = exact_color._issue_verified_cms_receipt(cms_payload) # noqa: SLF001 - trusted adapter boundary + return ExactColorResult( + rgb=output, + input_rgb_sha256=input_hash, + output_rgb_sha256=output_hash, + builder_receipt=builder_receipt, + cms_receipt=cms_receipt, + ) + + +def _load_verified_oracle(source_path: Path | None = None) -> ModuleType: + if _ORACLE_MODULE_NAME in sys.modules: + raise ExactColorIntegrityError("portable CMS oracle module is preloaded; verified isolated loading is required") + path = Path(__file__).with_name("portable_oracle_evaluator.py") if source_path is None else Path(source_path) + payload = _read_verified_payload(path, label="portable CMS oracle source") + if _ORACLE_MODULE_NAME in sys.modules: + raise ExactColorIntegrityError("portable CMS oracle module was substituted during verification") + actual = hashlib.sha256(payload).hexdigest() + if actual != ORACLE_SOURCE_SHA256: + raise ExactColorIntegrityError(f"portable CMS oracle source hash mismatch: {actual} != {ORACLE_SOURCE_SHA256}") + isolated_name = f"_negpy_verified_portable_oracle_{actual[:16]}" + oracle = ModuleType(isolated_name) + oracle.__file__ = str(path) + oracle.__package__ = "negpy.services.roll" + try: + code = compile(payload, str(path), "exec") + exec(code, oracle.__dict__) + except Exception as error: + raise ExactColorIntegrityError(f"verified portable CMS oracle could not execute: {error}") from error + if _ORACLE_MODULE_NAME in sys.modules: + raise ExactColorIntegrityError("portable CMS oracle module was substituted during isolated execution") + exports = ("lab_to_lch_codes", "lch_to_lab_codes", "lookup_three", "optimized_trilinear") + if oracle.__dict__.get("ASSET_SHA256") != dict(ASSET_SHA256) or any(not callable(oracle.__dict__.get(name)) for name in exports): + raise ExactColorIntegrityError("verified portable CMS oracle exports do not match the production contract") + return oracle + + +def _read_verified_assets(directory: Path) -> Mapping[str, bytes]: + payloads: dict[str, bytes] = {} + for name, expected in ASSET_SHA256.items(): + path = directory / name + dtype, shape = _ASSET_LAYOUT[name] + expected_bytes = int(np.prod(shape)) * np.dtype(dtype).itemsize + payload = _read_verified_payload( + path, + label="portable CMS asset", + expected_bytes=expected_bytes, + ) + actual = hashlib.sha256(payload).hexdigest() + if actual != expected: + raise ExactColorIntegrityError(f"portable CMS asset hash mismatch: {path}: {actual} != {expected}") + payloads[name] = payload + if sum(len(payload) for payload in payloads.values()) != PACKAGED_ASSET_BYTES: + raise ExactColorIntegrityError("portable CMS asset byte total is inconsistent") + return MappingProxyType(payloads) + + +def _read_verified_payload( + path: Path, + *, + label: str, + expected_bytes: int | None = None, +) -> bytes: + descriptor: int | None = None + try: + before = path.lstat() + if stat.S_ISLNK(before.st_mode) or not stat.S_ISREG(before.st_mode): + raise OSError("not a regular non-symlink file") + if expected_bytes is not None and before.st_size != expected_bytes: + raise ExactColorIntegrityError( + f"{label} byte size mismatch: {before.st_size} != {expected_bytes}" + ) + flags = os.O_RDONLY | getattr(os, "O_CLOEXEC", 0) | getattr(os, "O_NOFOLLOW", 0) + descriptor = os.open(path, flags) + opened = os.fstat(descriptor) + chunks: list[bytes] = [] + while chunk := os.read(descriptor, 1024 * 1024): + chunks.append(chunk) + after = os.fstat(descriptor) + final = path.lstat() + except OSError as error: + raise ExactColorUnavailable(f"{label} is unavailable: {path}: {error}") from error + finally: + if descriptor is not None: + os.close(descriptor) + if len({_identity(item) for item in (before, opened, after, final)}) != 1: + raise ExactColorIntegrityError(f"{label} changed while being read: {path}") + payload = b"".join(chunks) + if len(payload) != before.st_size: + raise ExactColorIntegrityError(f"{label} changed byte length while being read: {path}") + return payload + + +def _validate_validation_receipt(payload: bytes) -> dict[str, int]: + """Close the packaged zero-mismatch claim over its exact 12-event receipt.""" + + try: + document = json.loads( + payload, + object_pairs_hook=_reject_duplicate_keys, + parse_constant=_reject_json_constant, + ) + except (UnicodeDecodeError, json.JSONDecodeError, ValueError) as error: + raise ExactColorIntegrityError( + f"portable CMS validation receipt is invalid JSON: {error}" + ) from error + if type(document) is not dict or set(document) != { + "all_12_events", + "all_12_full_payloads", + "stage1", + "stage2", + }: + raise ExactColorIntegrityError( + "portable CMS validation receipt top-level schema is incomplete" + ) + + stage_totals: list[int] = [] + stage_full_totals: list[int] = [] + for stage_name, expected_events in _VALIDATION_EVENTS.items(): + stage = document.get(stage_name) + if type(stage) is not dict or set(stage) != { + "full_payload_total", + "per_event", + "total", + }: + raise ExactColorIntegrityError( + f"portable CMS validation receipt {stage_name} schema is incomplete" + ) + per_event = stage.get("per_event") + if type(per_event) is not dict or set(per_event) != set(expected_events): + raise ExactColorIntegrityError( + f"portable CMS validation receipt {stage_name} event inventory is incomplete" + ) + event_total = 0 + event_full_total = 0 + for event, (expected_total, expected_full_total) in expected_events.items(): + row = per_event.get(event) + if type(row) is not dict or set(row) != { + "full_payload", + "mae", + "max_abs", + "mismatched_u16", + "total_u16", + }: + raise ExactColorIntegrityError( + f"portable CMS validation receipt event {event} schema is incomplete" + ) + _require_zero_metrics( + { + key: row[key] + for key in ("mae", "max_abs", "mismatched_u16", "total_u16") + }, + expected_total=expected_total, + label=f"event {event}", + ) + full_payload = row.get("full_payload") + _require_zero_metrics( + full_payload, + expected_total=expected_full_total, + label=f"event {event} full payload", + ) + event_total += expected_total + event_full_total += expected_full_total + _require_zero_metrics( + stage.get("total"), + expected_total=event_total, + label=f"{stage_name} total", + ) + _require_zero_metrics( + stage.get("full_payload_total"), + expected_total=event_full_total, + label=f"{stage_name} full-payload total", + ) + stage_totals.append(event_total) + stage_full_totals.append(event_full_total) + + total_u16 = sum(stage_totals) + full_total_u16 = sum(stage_full_totals) + _require_zero_metrics( + document.get("all_12_events"), + expected_total=total_u16, + label="all-12-event total", + ) + full_payload = document.get("all_12_full_payloads") + if type(full_payload) is not dict or set(full_payload) != { + "mae", + "max_abs", + "mismatched_bytes", + "mismatched_u16", + "total_bytes", + "total_u16", + }: + raise ExactColorIntegrityError( + "portable CMS validation receipt full-payload schema is incomplete" + ) + _require_zero_metrics( + {key: full_payload[key] for key in ("mae", "max_abs", "mismatched_u16", "total_u16")}, + expected_total=full_total_u16, + label="all-12 full-payload total", + ) + if ( + type(full_payload.get("total_bytes")) is not int + or full_payload.get("total_bytes") != full_total_u16 * 2 + or type(full_payload.get("mismatched_bytes")) is not int + or full_payload.get("mismatched_bytes") != 0 + ): + raise ExactColorIntegrityError( + "portable CMS validation receipt byte totals are inconsistent" + ) + return { + "events": sum(len(events) for events in _VALIDATION_EVENTS.values()), + "mismatched_u16": 0, + "total_u16": total_u16, + "full_payload_mismatched_bytes": 0, + "full_payload_total_bytes": full_total_u16 * 2, + } + + +def _require_zero_metrics( + value: object, + *, + expected_total: int, + label: str, +) -> None: + if type(value) is not dict: + raise ExactColorIntegrityError( + f"portable CMS validation receipt {label} is not an exact zero-mismatch result" + ) + metrics = cast(dict[str, object], value) + if ( + set(metrics) != {"mae", "max_abs", "mismatched_u16", "total_u16"} + or type(metrics.get("mismatched_u16")) is not int + or metrics.get("mismatched_u16") != 0 + or type(metrics.get("total_u16")) is not int + or metrics.get("total_u16") != expected_total + or type(metrics.get("max_abs")) is not int + or metrics.get("max_abs") != 0 + or type(metrics.get("mae")) is not float + or metrics.get("mae") != 0.0 + ): + raise ExactColorIntegrityError( + f"portable CMS validation receipt {label} is not an exact zero-mismatch result" + ) + + +def _reject_duplicate_keys(pairs: list[tuple[str, object]]) -> dict[str, object]: + parsed: dict[str, object] = {} + for key, value in pairs: + if key in parsed: + raise ValueError(f"duplicate key {key!r}") + parsed[key] = value + return parsed + + +def _reject_json_constant(value: str) -> None: + raise ValueError(f"non-standard JSON constant {value!r}") + + +def _identity(value: os.stat_result) -> tuple[int, int, int, int, int]: + return ( + value.st_dev, + value.st_ino, + value.st_size, + value.st_mtime_ns, + value.st_ctime_ns, + ) + + +def _canonical_json(value: dict) -> bytes: + return json.dumps( + value, + sort_keys=True, + separators=(",", ":"), + ensure_ascii=False, + allow_nan=False, + ).encode("utf-8") + + +__all__ = [ + "ALGORITHM_ID", + "ASSET_SHA256", + "DEFAULT_CHUNK_PIXELS", + "MAX_CHUNK_PIXELS", + "ORACLE_SOURCE_SHA256", + "ORACLE_VALIDATION_RECEIPT_SHA256", + "PACKAGED_ASSET_BYTES", + "PortableCMSOnEvaluator", + "VALIDATION_RECEIPT_BYTES", + "VALIDATION_RECEIPT_FILENAME", + "VALIDATION_RECEIPT_SHA256", +] diff --git a/negpy/services/roll/portable_oracle_evaluator.py b/negpy/services/roll/portable_oracle_evaluator.py new file mode 100644 index 00000000..5fb11410 --- /dev/null +++ b/negpy/services/roll/portable_oracle_evaluator.py @@ -0,0 +1,354 @@ +#!/usr/bin/env python3 +"""DLL-free evaluator for the two captured CML4 optimized transforms. + +This program implements the integer runtime around two vendor-derived 32^3 +CLUTs. It intentionally treats the dumped CLUTs and 65,536-entry LUTs as +oracle data, verifies every asset hash before use, and never loads CML4.dll. + +The evaluator covers the twelve stored events: + +* Stage 1: NKLS5000_N + ramp8to16 -> NKLch (30,34,38,42,47,52) +* Stage 2: NKLch -> NKAdobe, no merged LUT (31,35,39,43,48,53) +""" + +from __future__ import annotations + +import argparse +import hashlib +import json +import math +from pathlib import Path + +import numpy as np + + +HERE = Path(__file__).resolve().parent +PROJECT = HERE.parents[3] +DEFAULT_LAB = PROJECT / "reverse_engineering/.work-cml-replay/lab" + +STAGE1_EVENTS = (30, 34, 38, 42, 47, 52) +STAGE2_EVENTS = (31, 35, 39, 43, 48, 53) +GEOMETRY = { + 30: (104, 104), 31: (104, 104), 34: (54, 104), 35: (54, 104), + 38: (104, 72), 39: (104, 72), 42: (54, 72), 43: (54, 72), + 47: (104, 104), 48: (104, 104), 52: (54, 104), 53: (54, 104), +} +ROW_PIXELS = 104 + +ASSET_SHA256 = { + "lch-atan-u16le.bin": + "56b8ac82456941a0a8aad6d7de2c79b21785529037b504582731ce6abcd143b1", + "lch-sincos-i16le.bin": + "dc8e71681bc46e60e33448171865fc96770dc17d3cfd0db835f6173e6fed7a35", + "lch-reciprocal-u16le.bin": + "6959ef7deeb57dc96eb4653fe13b4d37fcb4250c28be15654b8e8dd236849042", + "cml4-stage1-clut0.bin": + "a2abbc1e76dc037e6b364a58b483ddf01b9dde7c0e072f85885cb1f8c9dcbf1c", + "cml4-stage1-input-lut0.bin": + "1a487c024ceaf83018c8ab0e405e9c12b25a0500a2ae6e3465e20b29638729d7", + "cml4-stage1-output-lut0.bin": + "fe87ce159ec126597f9fb605b57cebec9b0264da1ca16d1725efe34db2e4fd2b", + "cml4-stage2-clut0.bin": + "d14b7c76091552bf03899327ca6a7c74c712a0423e429de8f8cc8203d5c98da3", + "cml4-stage2-input-lut0.bin": + "60fa510492f2adad2be9b00107b3d7ed7188a798f29748c3401560b70be3a248", + "cml4-stage2-output-lut0.bin": + "b88574377d1a0cf47fe8806335641db2ca35197817cd055a4cd55141708c8291", +} + + +def verify_assets(directory: Path) -> None: + for name, expected in ASSET_SHA256.items(): + path = directory / name + actual = hashlib.sha256(path.read_bytes()).hexdigest() + if actual != expected: + raise RuntimeError(f"asset hash mismatch: {path}: {actual} != {expected}") + + +def load_event(directory: Path, event: int, kind: str) -> np.ndarray: + width, height = GEOMETRY[event] + return load_event_payload(directory, event, kind)[:, :width, :].reshape(-1, 3) + + +def load_event_payload(directory: Path, event: int, kind: str) -> np.ndarray: + _, height = GEOMETRY[event] + path = directory / f"event{event}-{kind}.bin" + raw = np.fromfile(path, dtype=" np.ndarray: + return np.column_stack([lut[c, pixels[:, c]] for c in range(3)]).astype(np.uint16) + + +def optimized_trilinear(clut: np.ndarray, pixels: np.ndarray) -> np.ndarray: + """Exact scalar expression of optimized kernel CML4.dll+0x11e30. + + Nodes are four padded u16s. The MMX kernel halves every node, performs + three q15 linear interpolations with arithmetic shifts, then doubles the + result. Coordinate multiplication by 0x1f001f maps u16 endpoints onto a + 32-point grid and yields a 15-bit fraction. + """ + source = np.asarray(pixels, dtype=np.uint16).reshape(-1, 3) + packed = source.astype(np.uint64) * np.uint64(0x1F001F) + np.uint64(0x10000) + index = (packed >> np.uint64(32)).astype(np.int64) + fraction = ((packed & np.uint64(0xFFFFFFFF)) >> np.uint64(17)).astype(np.int64) + ix, iy, iz = index.T + fx, fy, fz = fraction.T + + def node(dx: int, dy: int, dz: int) -> np.ndarray: + # At the top endpoint the fraction is zero. The DLL may read a padded + # neighbor, but that value is multiplied by zero; clamping is exactly + # equivalent and avoids an out-of-array access in the portable model. + return ( + clut[ + np.minimum(ix + dx, 31), + np.minimum(iy + dy, 31), + np.minimum(iz + dz, 31), + ].astype(np.int64) + >> 1 + ) + + def lerp(a: np.ndarray, b: np.ndarray, q15: np.ndarray) -> np.ndarray: + return a + (((b - a) * q15[:, None]) >> 15) + + z0 = lerp( + lerp(node(0, 0, 0), node(1, 0, 0), fx), + lerp(node(0, 1, 0), node(1, 1, 0), fx), + fy, + ) + z1 = lerp( + lerp(node(0, 0, 1), node(1, 0, 1), fx), + lerp(node(0, 1, 1), node(1, 1, 1), fx), + fy, + ) + result = (lerp(z0, z1, fz) << 1) & 0xFFFF + return result[:, :3].astype(np.uint16) + + +def trunc_q15(product: np.ndarray) -> np.ndarray: + """Signed division by 32768, truncating toward zero (0x100041d0).""" + return np.where(product < 0, -((-product) >> 15), product >> 15) + + +def lch_to_lab_codes( + pixels: np.ndarray, + sincos: np.ndarray, + reciprocal: np.ndarray, +) -> np.ndarray: + """Exact pre-CLUT Lch special case at CML4.dll+0x100041d0.""" + source = np.asarray(pixels, dtype=np.uint16).reshape(-1, 3) + chroma = source[:, 1].astype(np.int64) + hue = source[:, 2].astype(np.int64) + used = np.minimum(chroma, reciprocal[hue].astype(np.int64)) + # Captured sincos records are [sin, cos]. + a = trunc_q15(sincos[hue, 1].astype(np.int64) * used) + b = trunc_q15(sincos[hue, 0].astype(np.int64) * used) + return np.column_stack( + [ + source[:, 0], + (32768 + a).astype(np.uint16), + (32768 + b).astype(np.uint16), + ] + ) + + +def hue_from_signed_ab(a: np.ndarray, b: np.ndarray, atan: np.ndarray) -> np.ndarray: + """Exact ratio table, axes, and quadrant reconstruction in 0x10004290.""" + a = np.asarray(a, dtype=np.int64) + b = np.asarray(b, dtype=np.int64) + aa, bb = np.abs(a), np.abs(b) + high, low = np.maximum(aa, bb), np.minimum(aa, bb) + ratio = np.zeros_like(high) + nz = high != 0 + ratio[nz] = (low[nz] << 13) // high[nz] + angle = atan[ratio].astype(np.int64) + hue = np.zeros_like(a) + a_major = aa >= bb + q1 = (a > 0) & (b > 0) + q2 = (a < 0) & (b > 0) + q3 = (a < 0) & (b < 0) + q4 = (a > 0) & (b < 0) + hue[q1] = np.where(a_major[q1], angle[q1], 16384 - angle[q1]) + hue[q2] = np.where(a_major[q2], 32768 - angle[q2], 16384 + angle[q2]) + hue[q3] = np.where(a_major[q3], 32768 + angle[q3], 49152 - angle[q3]) + hue[q4] = np.where(a_major[q4], 65536 - angle[q4], 49152 + angle[q4]) + hue[(a == 0) & (b > 0)] = 16384 + hue[(a < 0) & (b == 0)] = 32768 + hue[(a == 0) & (b < 0)] = 49152 + return (hue & 0xFFFF).astype(np.uint16) + + +def rounded_integer_hypot(a: np.ndarray, b: np.ndarray) -> np.ndarray: + """Round sqrt(a*a+b*b) to nearest, without host floating-point drift.""" + squared = a.astype(np.int64) ** 2 + b.astype(np.int64) ** 2 + floor_root = np.fromiter( + (math.isqrt(int(value)) for value in squared), + dtype=np.int64, + count=squared.size, + ) + # sqrt(integer) can never be exactly k+0.5, so no tie rule is needed. + return floor_root + ((squared - floor_root * floor_root) > floor_root) + + +def lab_to_lch_codes(pixels: np.ndarray, atan: np.ndarray) -> np.ndarray: + """Exact post-CLUT Lab special case at CML4.dll+0x10004290.""" + source = np.asarray(pixels, dtype=np.uint16).reshape(-1, 3) + + def rescale(code: np.ndarray) -> np.ndarray: + value = (code.astype(np.uint64) - np.uint64(8192)) * np.uint64(65535) + product = value * np.uint64(87384) # ceil(2**32 / 49151) + return ( + (product >> np.uint64(32)) + + ((product & np.uint64(0xFFFFFFFF)) >> np.uint64(31)) + ).astype(np.int64) + + a = rescale(source[:, 1]) - 32768 + b = rescale(source[:, 2]) - 32768 + chroma = rounded_integer_hypot(a, b) + hue = hue_from_signed_ab(a, b, atan) + return np.column_stack([source[:, 0], chroma, hue]).astype(np.uint16) + + +class CapturedTransforms: + def __init__(self, assets: Path): + verify_assets(assets) + self.atan = np.fromfile(assets / "lch-atan-u16le.bin", dtype=" np.ndarray: + working = lookup_three(self.s1_input, source) + working = optimized_trilinear(self.s1_clut, working) + working = lab_to_lch_codes(working, self.atan) + return lookup_three(self.s1_output, working) + + def stage2(self, source: np.ndarray) -> np.ndarray: + # CML4's Lch special-case deliberately bypasses the generated input + # LUT for hue. Its dumped plane 2 maps the top half down by one; using + # it causes 4,729 mismatches across these events. + working = source.copy() + working[:, 0] = self.s2_input[0, working[:, 0]] + working[:, 1] = self.s2_input[1, working[:, 1]] + working = lch_to_lab_codes(working, self.sincos, self.reciprocal) + working = optimized_trilinear(self.s2_clut, working) + return lookup_three(self.s2_output, working) + + +def metric(predicted: np.ndarray, expected: np.ndarray) -> dict[str, int | float]: + difference = predicted.astype(np.int64) - expected.astype(np.int64) + absolute = np.abs(difference) + return { + "mismatched_u16": int(np.count_nonzero(difference)), + "total_u16": int(difference.size), + "max_abs": int(absolute.max(initial=0)), + "mae": float(absolute.mean()), + } + + +def evaluate(model: CapturedTransforms, lab: Path) -> dict: + result: dict[str, dict] = {} + every_predicted_active = [] + every_expected_active = [] + every_predicted_payload = [] + every_expected_payload = [] + for label, events, function in ( + ("stage1", STAGE1_EVENTS, model.stage1), + ("stage2", STAGE2_EVENTS, model.stage2), + ): + predicted_all, expected_all = [], [] + predicted_payload_all, expected_payload_all = [], [] + per_event = {} + for event in events: + width, height = GEOMETRY[event] + source = load_event(lab, event, "source") + expected = load_event(lab, event, "expected") + predicted = function(source) + expected_payload = load_event_payload(lab, event, "expected") + predicted_payload = np.zeros_like(expected_payload) + predicted_payload[:, :width, :] = predicted.reshape(height, width, 3) + per_event[str(event)] = { + **metric(predicted, expected), + "full_payload": metric(predicted_payload, expected_payload), + } + predicted_all.append(predicted) + expected_all.append(expected) + predicted_payload_all.append(predicted_payload) + expected_payload_all.append(expected_payload) + result[label] = { + "per_event": per_event, + "total": metric(np.concatenate(predicted_all), np.concatenate(expected_all)), + "full_payload_total": metric( + np.concatenate(predicted_payload_all), + np.concatenate(expected_payload_all), + ), + } + every_predicted_active.extend(predicted_all) + every_expected_active.extend(expected_all) + every_predicted_payload.extend(predicted_payload_all) + every_expected_payload.extend(expected_payload_all) + result["all_12_events"] = metric( + np.concatenate(every_predicted_active), np.concatenate(every_expected_active) + ) + result["all_12_full_payloads"] = { + **metric( + np.concatenate(every_predicted_payload), + np.concatenate(every_expected_payload), + ), + "total_bytes": int(sum(value.nbytes for value in every_expected_payload)), + "mismatched_bytes": int( + np.count_nonzero( + np.concatenate(every_predicted_payload).view(np.uint8) + != np.concatenate(every_expected_payload).view(np.uint8) + ) + ), + } + return result + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--assets", type=Path, default=HERE) + parser.add_argument("--lab", type=Path, default=DEFAULT_LAB) + parser.add_argument("--json-out", type=Path) + args = parser.parse_args() + result = evaluate(CapturedTransforms(args.assets), args.lab) + print(json.dumps(result, indent=2)) + if args.json_out is not None: + args.json_out.write_text(json.dumps(result, indent=2) + "\n", encoding="utf-8") + return ( + 0 + if result["all_12_events"]["mismatched_u16"] == 0 + and result["all_12_full_payloads"]["mismatched_u16"] == 0 + and result["all_12_full_payloads"]["mismatched_bytes"] == 0 + else 1 + ) + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/negpy/services/roll/service.py b/negpy/services/roll/service.py index 83eae583..cdb26304 100644 --- a/negpy/services/roll/service.py +++ b/negpy/services/roll/service.py @@ -19,37 +19,169 @@ derived from the one before it. See its docstring for the tier definitions, the write order, and how a tier that cannot be produced degrades instead of losing the tiers that still can be. Tier 2 (repaired) is backed by -`negpy.infrastructure.roll.repair`, an optional-engine seam with nothing -registered in this codebase; Tier 3 (positive) is backed by -`negpy.services.roll.positive`, which reuses NegPy's own rendering engine. +`negpy.infrastructure.roll.repair`; the bundled `fauxice_bridge` registers +the portable engine and reports it unavailable if that import fails. Tier 3 +(positive) defaults to the receipt-bound portable Nikon builder/CMS path, +with `negpy.services.roll.positive` retained as an explicitly selected +approximate renderer. """ from __future__ import annotations import dataclasses +import functools +import hashlib +import io import json import os +import shutil +import stat import tempfile +import threading +from contextvars import ContextVar from datetime import date as _date -from typing import TYPE_CHECKING, Iterable, Iterator +from typing import TYPE_CHECKING, Callable, Iterable, Iterator, cast import numpy as np import tifffile +from PIL import Image + +try: + import fcntl +except ImportError: # pragma: no cover - exercised by Windows packaging smoke + fcntl = None # type: ignore[assignment] from negpy.infrastructure.roll import coolscanpy_roll from negpy.infrastructure.roll import fauxice_bridge as _fauxice_bridge # noqa: F401 — registers engine on import from negpy.infrastructure.roll import repair as roll_repair from negpy.infrastructure.roll.repair import RepairMode from negpy.kernel.system.logging import get_logger +from negpy.services.repair.fauxice_hybrid_runner import HybridRuntimeConfig from negpy.services.rendering.image_processor import ImageProcessor +from negpy.services.roll import exact_color as roll_exact_color +from negpy.services.roll import native_builder as roll_native_builder +from negpy.services.roll import nikon_icc as roll_nikon_icc from negpy.services.roll import positive as roll_positive from negpy.services.scanning.templating import render_scan_filename if TYPE_CHECKING: import coolscanpy + from coolscanpy.protocol.ls5000_single_pass.capture_process import ( + ManualFrameApproval as CoolscanManualFrameApproval, + ) + from coolscanpy.types import ProgressCallback as CoolscanProgressCallback logger = get_logger(__name__) +_RETAINED_EVIDENCE_DIRECTORIES = frozenset( + { + ".negpy-dice-hybrid", + ".negpy-dice-acquisition", + ".negpy-native-builder", + ".negpy-stage3-replay", + } +) +_MAX_RECEIPT_BYTES = 16 * 1024 * 1024 +_MAX_SHARED_EVIDENCE_BYTES = 64 * 1024 * 1024 + + +def _strict_json_loads(payload: bytes | str) -> object: + def unique_object(pairs: list[tuple[str, object]]) -> dict[str, object]: + result: dict[str, object] = {} + for key, value in pairs: + if key in result: + raise ValueError(f"duplicate JSON object key {key!r}") + result[key] = value + return result + + def reject_constant(value: str) -> object: + raise ValueError(f"non-finite JSON constant {value!r}") + + return json.loads( + payload, + object_pairs_hook=unique_object, + parse_constant=reject_constant, + ) + + +def _stable_regular_bytes( + path: str, + *, + maximum_bytes: int, + label: str, +) -> bytes: + before = os.lstat(path) + if stat.S_ISLNK(before.st_mode) or not stat.S_ISREG(before.st_mode): + raise OSError(f"{label} must be a regular non-symlink file") + if before.st_size > maximum_bytes: + raise OSError(f"{label} exceeds its safe size limit") + flags = os.O_RDONLY + if hasattr(os, "O_CLOEXEC"): + flags |= os.O_CLOEXEC + if hasattr(os, "O_NOFOLLOW"): + flags |= os.O_NOFOLLOW + if hasattr(os, "O_NONBLOCK"): + flags |= os.O_NONBLOCK + descriptor = os.open(path, flags) + try: + opened = os.fstat(descriptor) + + def identity(metadata: os.stat_result) -> tuple[int, int, int, int, int]: + return ( + metadata.st_dev, + metadata.st_ino, + metadata.st_size, + metadata.st_mtime_ns, + metadata.st_ctime_ns, + ) + + if not stat.S_ISREG(opened.st_mode) or identity(opened) != identity(before): + raise OSError(f"{label} changed while it was opened") + chunks: list[bytes] = [] + total = 0 + while True: + chunk = os.read(descriptor, min(1024 * 1024, maximum_bytes + 1 - total)) + if not chunk: + break + chunks.append(chunk) + total += len(chunk) + if total > maximum_bytes: + raise OSError(f"{label} exceeds its safe size limit") + after_read = os.fstat(descriptor) + finally: + os.close(descriptor) + after_path = os.lstat(path) + if identity(after_read) != identity(opened) or identity(after_path) != identity(opened): + raise OSError(f"{label} changed while it was read") + return b"".join(chunks) + + +def _load_bounded_receipt(path: str) -> dict: + payload = _stable_regular_bytes( + path, + maximum_bytes=_MAX_RECEIPT_BYTES, + label="frame receipt", + ) + try: + document = _strict_json_loads(payload) + except (UnicodeDecodeError, ValueError, TypeError) as error: + raise OSError(f"frame receipt JSON is invalid: {error}") from error + if not isinstance(document, dict): + raise OSError("frame receipt must contain a JSON object") + return document + + +def _fsync_directory(path: str) -> None: + flags = os.O_RDONLY + if hasattr(os, "O_DIRECTORY"): + flags |= os.O_DIRECTORY + descriptor = os.open(path or ".", flags) + try: + os.fsync(descriptor) + finally: + os.close(descriptor) + + # Re-exported so callers only need this module: `from negpy.services.roll # .service import available`, matching how the plain Scan sidebar checks # `_sane_available()` before showing its device combo. @@ -62,6 +194,17 @@ class RollScanningError(RuntimeError): coolscanpy itself. Mirrors `negpy.services.capture.service.CaptureError`.""" +class OutputRollbackError(RollScanningError): + """Publication failed and at least one prior file needs recovery.""" + + def __init__(self, recovery_path: str, errors: list[str]) -> None: + self.recovery_path = recovery_path + self.errors = tuple(errors) + super().__init__( + f"frame publication failed and rollback was incomplete; prior files are retained at {recovery_path}: {'; '.join(errors)}" + ) + + @dataclasses.dataclass(frozen=True) class RollFrameOutput: """Where one scanned frame's files landed on disk, one pair of fields per @@ -76,6 +219,523 @@ class RollFrameOutput: repaired_ir_path: str | None # Tier 2 (repaired): infrared plane (Tier 1's own, retained unchanged) positive_path: str | None # Tier 3 (positive) receipt_path: str + synthesis_mask_path: str | None + native_synthesis_mask_path: str | None + hybrid_receipt_path: str | None + + +_ACTIVE_OUTPUT_TRANSACTION: ContextVar[_OutputTransaction | None] = ContextVar( + "negpy_roll_output_transaction", + default=None, +) + + +class _OutputTransaction: + """Stage one frame's files, publish its receipt last, and roll back. + + The rollback guarantee covers process-level write/rename failures. File + and directory fsync ordering narrows power-loss exposure, but a set of + multiple POSIX pathnames is not one power-fail-atomic filesystem unit. + """ + + def __init__(self, output_folder: str) -> None: + self.output_folder = os.path.realpath(output_folder) + self._root = tempfile.mkdtemp( + prefix=".negpy-frame-stage-", + dir=self.output_folder, + ) + self._staged: dict[str, str] = {} + self._removals: set[str] = set() + self._finished = False + self._preserve_staging = False + self._lock_descriptor: int | None = None + + def _validate_output_path(self, final_path: str) -> str: + final = os.path.abspath(final_path) + try: + inside = os.path.commonpath((self.output_folder, final)) == self.output_folder + except ValueError: + inside = False + if not inside: + raise OSError("frame output escapes its selected output folder") + + parent = os.path.dirname(final) + if os.path.commonpath((self.output_folder, os.path.realpath(parent))) != self.output_folder: + raise OSError("frame output parent escapes through a symbolic link") + relative_parent = os.path.relpath(parent, self.output_folder) + current = self.output_folder + if relative_parent != ".": + for component in relative_parent.split(os.sep): + current = os.path.join(current, component) + try: + metadata = os.lstat(current) + except FileNotFoundError: + break + if stat.S_ISLNK(metadata.st_mode) or not stat.S_ISDIR(metadata.st_mode): + raise OSError("frame output parent is not a real directory") + return final + + def stage_path(self, final_path: str) -> str: + final = self._validate_output_path(final_path) + staged = self._staged.get(final) + if staged is None: + token = hashlib.sha256(final.encode("utf-8")).hexdigest() + staged = os.path.join(self._root, f"{token}-{os.path.basename(final)}") + self._staged[final] = staged + return staged + + def staged_path(self, final_path: str) -> str: + final = os.path.abspath(final_path) + try: + return self._staged[final] + except KeyError as error: + raise OSError(f"output was not staged: {final_path}") from error + + def discard(self, final_path: str) -> None: + staged = self._staged.pop(os.path.abspath(final_path), None) + if staged is not None: + try: + os.unlink(staged) + except FileNotFoundError: + pass + + def checkpoint(self) -> frozenset[str]: + """Remember the outputs already staged by lower tiers.""" + + return frozenset(self._staged) + + def discard_after(self, checkpoint: frozenset[str]) -> None: + """Discard staged outputs added after ``checkpoint``.""" + + for final_path in tuple(self._staged): + if final_path not in checkpoint: + self.discard(final_path) + + def schedule_remove(self, final_path: str) -> None: + final = self._validate_output_path(final_path) + self._removals.add(final) + + def _existing_receipt_paths(self, receipt_path: str) -> set[str]: + try: + os.lstat(receipt_path) + except FileNotFoundError: + return set() + document = _load_bounded_receipt(receipt_path) + return _receipt_output_paths(document, output_folder=self.output_folder) + + def _acquire_frame_lock(self, receipt_path: str) -> None: + if self._lock_descriptor is not None: + return + if fcntl is None: + raise OSError("safe cross-process frame locking is unavailable on this platform") + lock_directory = os.path.join(self.output_folder, ".negpy-locks") + try: + metadata = os.lstat(lock_directory) + except FileNotFoundError: + try: + os.mkdir(lock_directory, 0o700) + except FileExistsError: + pass + metadata = os.lstat(lock_directory) + if stat.S_ISLNK(metadata.st_mode) or not stat.S_ISDIR(metadata.st_mode): + raise OSError("frame lock directory is not a real directory") + lock_name = hashlib.sha256(os.path.abspath(receipt_path).encode("utf-8")).hexdigest() + ".lock" + flags = os.O_CREAT | os.O_RDWR + if hasattr(os, "O_CLOEXEC"): + flags |= os.O_CLOEXEC + if hasattr(os, "O_NOFOLLOW"): + flags |= os.O_NOFOLLOW + descriptor = os.open( + os.path.join(lock_directory, lock_name), + flags, + 0o600, + ) + if not stat.S_ISREG(os.fstat(descriptor).st_mode): + os.close(descriptor) + raise OSError("frame lock is not a regular file") + try: + # A crashed or wedged peer must not freeze the scanner worker/UI. + # Publication fails closed immediately; the caller may retry once + # the other process has completed or been recovered. + fcntl.flock(descriptor, fcntl.LOCK_EX | fcntl.LOCK_NB) + except BlockingIOError as error: + os.close(descriptor) + raise OSError("frame output is busy in another NegPy process") from error + except BaseException: + os.close(descriptor) + raise + self._lock_descriptor = descriptor + + def _release_frame_lock(self) -> None: + if self._lock_descriptor is None: + return + try: + assert fcntl is not None + fcntl.flock(self._lock_descriptor, fcntl.LOCK_UN) + finally: + os.close(self._lock_descriptor) + self._lock_descriptor = None + + def schedule_stale_outputs(self, receipt_path: str) -> None: + self._acquire_frame_lock(receipt_path) + staged_receipt = self._staged.get(os.path.abspath(receipt_path)) + if staged_receipt is None: + raise OSError("frame receipt was not staged") + new_document = _load_bounded_receipt(staged_receipt) + new_paths = _receipt_output_paths( + new_document, + output_folder=self.output_folder, + ) + excluded_receipt = os.path.abspath(receipt_path) + for final_path in tuple(self._staged): + if final_path == excluded_receipt: + continue + if not _other_receipt_references( + final_path, + output_folder=self.output_folder, + excluded_receipt=excluded_receipt, + ): + continue + if _is_retained_evidence_path(final_path, self.output_folder): + try: + staged_bytes = _stable_regular_bytes( + self._staged[final_path], + maximum_bytes=_MAX_SHARED_EVIDENCE_BYTES, + label="staged retained evidence", + ) + existing_bytes = _stable_regular_bytes( + final_path, + maximum_bytes=_MAX_SHARED_EVIDENCE_BYTES, + label="shared retained evidence", + ) + except OSError: + pass + else: + if staged_bytes == existing_bytes: + self.discard(final_path) + continue + raise OSError(f"frame output is already owned by another receipt: {final_path}") + staged_outputs = set(self._staged) - {os.path.abspath(receipt_path)} + unbound = sorted(staged_outputs - new_paths) + if unbound: + raise OSError("frame transaction contains outputs absent from its receipt: " + ", ".join(unbound)) + base_path = receipt_path[: -len("_receipt.json")] + for derived in ( + base_path + ".tif", + base_path + "_IR.tif", + base_path + "_repaired.tif", + base_path + "_repaired_IR.tif", + base_path + "_repaired_SYNTH.png", + base_path + "_positive.tif", + ): + if os.path.abspath(derived) not in new_paths and not _other_receipt_references( + derived, + output_folder=self.output_folder, + excluded_receipt=excluded_receipt, + ): + self.schedule_remove(derived) + old_paths = self._existing_receipt_paths(receipt_path) + for old_path in old_paths - new_paths: + if not _is_retained_evidence_path(old_path, self.output_folder): + continue + if not _other_receipt_references( + old_path, + output_folder=self.output_folder, + excluded_receipt=excluded_receipt, + ): + self.schedule_remove(old_path) + + def commit(self, *, receipt_path: str) -> None: + receipt = os.path.abspath(receipt_path) + if receipt not in self._staged: + raise OSError("frame receipt was not staged") + writes = sorted(path for path in self._staged if path != receipt) + removals = sorted(self._removals - set(self._staged)) + backup_root = os.path.join(self._root, "backups") + os.makedirs(backup_root, exist_ok=True) + backups: dict[str, str] = {} + installed: list[str] = [] + + def backup(path: str) -> None: + self._validate_output_path(path) + if not os.path.lexists(path): + return + if os.path.islink(path) or not os.path.isfile(path): + raise OSError(f"refusing to replace non-regular output {path}") + backup_path = os.path.join( + backup_root, + hashlib.sha256(path.encode("utf-8")).hexdigest(), + ) + os.replace(path, backup_path) + backups[path] = backup_path + + try: + for final in removals: + backup(final) + for final in writes: + backup(final) + self._validate_output_path(final) + os.makedirs(os.path.dirname(final) or ".", exist_ok=True) + self._validate_output_path(final) + os.replace(self._staged[final], final) + installed.append(final) + for directory in sorted({os.path.dirname(path) or "." for path in (*writes, *removals)}): + _fsync_directory(directory) + # Keep the prior receipt in place until every referenced artifact + # has landed. The receipt rename is the transaction's commit mark. + backup(receipt) + self._validate_output_path(receipt) + os.makedirs(os.path.dirname(receipt) or ".", exist_ok=True) + self._validate_output_path(receipt) + os.replace(self._staged[receipt], receipt) + installed.append(receipt) + _fsync_directory(os.path.dirname(receipt) or ".") + except BaseException as publication_error: + rollback_errors: list[str] = [] + for final in reversed(installed): + try: + if os.path.isfile(final) and not os.path.islink(final): + os.unlink(final) + elif os.path.lexists(final): + rollback_errors.append(f"refused to remove replacement {final}") + except OSError as error: + rollback_errors.append(f"could not remove replacement {final}: {error}") + for final, backup_path in reversed(tuple(backups.items())): + try: + os.makedirs(os.path.dirname(final) or ".", exist_ok=True) + os.replace(backup_path, final) + except OSError as error: + rollback_errors.append(f"could not restore {final} from {backup_path}: {error}") + if rollback_errors: + recovery_path = self._retain_recovery( + publication_error=publication_error, + rollback_errors=rollback_errors, + backups=backups, + ) + raise OutputRollbackError(recovery_path, rollback_errors) from publication_error + raise + else: + self._finished = True + self._cleanup_empty_evidence_directories(removals) + finally: + self._cleanup_staging() + + def _retain_recovery( + self, + *, + publication_error: BaseException, + rollback_errors: list[str], + backups: dict[str, str], + ) -> str: + self._preserve_staging = True + prior_root = self._root + recovery_name = os.path.basename(self._root).replace( + ".negpy-frame-stage-", + ".negpy-recovery-", + 1, + ) + recovery_path = os.path.join(os.path.dirname(self._root), recovery_name) + try: + os.rename(self._root, recovery_path) + except OSError as error: + recovery_path = self._root + rollback_errors.append(f"could not rename recovery directory: {error}") + else: + self._root = recovery_path + + def relocated(path: str) -> str: + return os.path.join(recovery_path, os.path.relpath(path, prior_root)) + + manifest = { + "publication_error": repr(publication_error), + "recovery_path": recovery_path, + "rollback_errors": rollback_errors, + "unrestored_backups": { + final: os.path.relpath(relocated(backup), recovery_path) + for final, backup in backups.items() + if os.path.exists(relocated(backup)) + }, + } + try: + with open( + os.path.join(recovery_path, "RECOVERY.json"), + "w", + encoding="utf-8", + ) as stream: + json.dump(manifest, stream, indent=2, sort_keys=True) + stream.write("\n") + stream.flush() + os.fsync(stream.fileno()) + except OSError as error: + rollback_errors.append(f"could not write recovery manifest: {error}") + return recovery_path + + def _cleanup_staging(self) -> None: + if not self._preserve_staging and os.path.isdir(self._root): + try: + shutil.rmtree(self._root) + except OSError as error: + logger.warning("Could not remove frame staging directory %s: %s", self._root, error) + self._release_frame_lock() + + def _cleanup_empty_evidence_directories(self, removals: list[str]) -> None: + for removed in removals: + evidence_root = _retained_evidence_root( + removed, + self.output_folder, + ) + if evidence_root is None: + continue + parent = os.path.dirname(removed) + while True: + try: + inside = os.path.commonpath((evidence_root, parent)) == evidence_root + except ValueError: + inside = False + if not inside: + break + try: + os.rmdir(parent) + except OSError: + break + if parent == evidence_root: + break + parent = os.path.dirname(parent) + + def abort(self) -> None: + if not self._finished: + self._cleanup_staging() + + +def _transactional_frame_output(method): + @functools.wraps(method) + def wrapped(self, frame, output_folder, *args, **kwargs): + os.makedirs(output_folder, exist_ok=True) + transaction = _OutputTransaction(output_folder) + token = _ACTIVE_OUTPUT_TRANSACTION.set(transaction) + try: + result = method( + self, + frame, + transaction.output_folder, + *args, + **kwargs, + ) + transaction.schedule_stale_outputs(result.receipt_path) + transaction.commit(receipt_path=result.receipt_path) + return result + finally: + _ACTIVE_OUTPUT_TRANSACTION.reset(token) + transaction.abort() + + return wrapped + + +def _receipt_output_paths( + document: object, + *, + output_folder: str, +) -> set[str]: + if not isinstance(document, dict): + return set() + outputs = document.get("outputs") + found: set[str] = set() + + def visit(value: object, key: str | None = None) -> None: + if isinstance(value, dict): + for child_key, child in value.items(): + visit(child, child_key) + elif isinstance(value, list): + for child in value: + visit(child, key) + elif isinstance(value, str) and key is not None and (key == "path" or key.endswith("_path")): + candidate = os.path.abspath(value) + try: + inside = os.path.commonpath((output_folder, candidate)) == output_folder + except ValueError: + inside = False + if inside: + found.add(candidate) + + visit(outputs) + return found + + +def _retained_evidence_root(path: str, output_folder: str) -> str | None: + candidate = os.path.abspath(path) + root = os.path.abspath(output_folder) + try: + if os.path.commonpath((root, candidate)) != root: + return None + except ValueError: + return None + relative_parts = os.path.relpath(candidate, root).split(os.sep) + for index, component in enumerate(relative_parts): + if component in _RETAINED_EVIDENCE_DIRECTORIES: + return os.path.join(root, *relative_parts[: index + 1]) + return None + + +def _is_retained_evidence_path(path: str, output_folder: str) -> bool: + evidence_root = _retained_evidence_root(path, output_folder) + return evidence_root is not None and os.path.abspath(path) != evidence_root + + +def _other_receipt_references( + path: str, + *, + output_folder: str, + excluded_receipt: str, +) -> bool: + try: + + def fail_walk(error: OSError) -> None: + raise error + + walker = os.walk( + output_folder, + topdown=True, + onerror=fail_walk, + followlinks=False, + ) + for directory, subdirectories, names in walker: + retained_subdirectories: list[str] = [] + for name in subdirectories: + child = os.path.join(directory, name) + if ( + name in _RETAINED_EVIDENCE_DIRECTORIES + or name.startswith(".negpy-frame-stage-") + or name.startswith(".negpy-recovery-") + or os.path.islink(child) + ): + continue + retained_subdirectories.append(name) + subdirectories[:] = retained_subdirectories + for name in names: + if not name.endswith("_receipt.json"): + continue + receipt = os.path.abspath(os.path.join(directory, name)) + if receipt == excluded_receipt: + continue + try: + document = _load_bounded_receipt(receipt) + except OSError: + # An unsafe/unreadable sibling may still own this path. + # Preserve rather than risk deleting or overwriting it. + return True + if os.path.abspath(path) in _receipt_output_paths( + document, + output_folder=output_folder, + ): + return True + except OSError: + return True + return False + + +def _prepare_evidence_directory(path: str) -> None: + if _ACTIVE_OUTPUT_TRANSACTION.get() is None: + os.makedirs(path, exist_ok=True) class RollScanningService: @@ -86,8 +746,20 @@ class RollScanningService: manager) before opening another. """ - def __init__(self) -> None: + def __init__( + self, + *, + exact_color_builder: roll_exact_color.VerifiedStage1Builder | None = None, + exact_color_evaluator: roll_exact_color.VerifiedPortableCMSEvaluator | None = None, + hybrid_runtime: HybridRuntimeConfig | None = None, + ) -> None: self._roll: coolscanpy_roll.RollHandle | None = None + # Explicitly injected: NegPy never treats its generic renderer as a + # Nikon-exact substitute when either verified stage is absent. + self._exact_color_builder = exact_color_builder + self._exact_color_evaluator = exact_color_evaluator + self._hybrid_runtime = hybrid_runtime + self._repair_cancel = threading.Event() # Built lazily, on the first frame that actually needs Tier 3 -- see # `_get_image_processor()`. Most batches never touch it. self._image_processor: ImageProcessor | None = None @@ -118,32 +790,50 @@ def __exit__(self, *exc: object) -> None: # -- preview / approval ------------------------------------------------ def preview( - self, slots: Iterable[int] | None = None, *, on_progress: "coolscanpy.ProgressCallback | None" = None + self, slots: Iterable[int] | None = None, *, on_progress: "CoolscanProgressCallback | None" = None ) -> "list[coolscanpy.Thumbnail]": return self._require_roll().preview(slots, on_progress=on_progress) + def restore_preview_session( + self, + payload: str, + slots: Iterable[int] | None = None, + ) -> "list[coolscanpy.Thumbnail]": + """Restore a saved, content-verified review on the open roll.""" + + return self._require_roll().restore_preview_session(payload, slots) + def set_spacing_offset(self, slot: int, offset_rows: int) -> None: self._require_roll().set_spacing_offset(slot, offset_rows) - def approve(self, slot: int) -> None: - self._require_roll().approve(slot) + def approve(self, slot: int) -> "CoolscanManualFrameApproval": + """Approve one reviewed slot and return its content-bound receipt.""" + + return self._require_roll().approve(slot) def needs_approval(self, slot: int) -> bool: return self._require_roll().needs_approval(slot) # -- scanning ---------------------------------------------------------- - def scan_many( - self, slots: Iterable[int], *, on_progress: "coolscanpy.ProgressCallback | None" = None - ) -> Iterator["coolscanpy.Frame"]: + def prepare_batch(self) -> None: + """Arm a new batch before its queued worker request is exposed.""" + + self._repair_cancel.clear() + + def scan_many(self, slots: Iterable[int], *, on_progress: "CoolscanProgressCallback | None" = None) -> Iterator["coolscanpy.Frame"]: + if self._repair_cancel.is_set(): + raise roll_repair.RepairCancelled("roll batch cancelled before scanning started") yield from self._require_roll().scan_many(slots, on_progress=on_progress) def safe_stop(self) -> None: + self._repair_cancel.set() if self._roll is not None: self._roll.safe_stop() # -- writing ------------------------------------------------------------- + @_transactional_frame_output def write_frame( self, frame: "coolscanpy.Frame", @@ -154,6 +844,9 @@ def write_frame( write_repaired: bool = False, write_positive: bool = False, repair_mode: str = RepairMode.EXACT.value, + positive_mode: str = roll_exact_color.PositiveColorMode.NIKON_EXACT.value, + builder_receipt: roll_exact_color.BuilderReceipt | None = None, + on_repair_progress: Callable[[float], None] | None = None, ) -> RollFrameOutput: """Write one scanned `Frame` to disk across up to three tiers, plus a `_receipt.json` sidecar. Each tier is independently selectable; any @@ -175,8 +868,12 @@ def write_frame( the original lets a later re-repair under a different mode start from the same evidence Tier 1 captured. - Tier 3, positive (`write_positive`): Tier 2 inverted through - `negpy.services.roll.positive`, written as `_positive.tif`. + Tier 3, positive (`write_positive`): Tier 2 inverted through either + receipt-bound portable Nikon Stage-1 and CMS stages (`positive_mode= + "nikon-exact", the fail-closed default) or NegPy's explicitly selected + approximate renderer (`"negpy-approximate"`). Exact mode accepts an explicit Stage-3 replay + receipt or automatically builds a native receipt when the frame carries + proven per-frame density and analyzer evidence. Requested independently of `write_repaired`, but always derived from Tier 2's in-memory result -- repair runs whenever either flag needs it, so a positive is never silently missing the repair pass a caller @@ -190,7 +887,16 @@ def write_frame( the receipt and leaving the corresponding `RollFrameOutput` path(s) `None`. Tier 1 always writes regardless (or Tier 2, for a Tier-3 failure): losing the archival capture to a problem in a derived, - regenerable tier is exactly what this must not do. + frame-bound tier is exactly what this must not do. When Tier 1 has an + infrared plane, its transaction also retains the scanner-native + prepass, IR-validity map, and acquisition binding required to replay + repair after the physical media has moved. + + When a frame carries the native per-acquisition Nikon builder + evidence, that evidence is validated and retained independently of + Tier 3 selection or success. The frame receipt binds the retained + artifacts under ``outputs.native_color_evidence`` so a later exact + render does not depend on the transient in-memory frame object. `filename_pattern` is the same Jinja2 template the plain scan path uses (variables: `date`, `seq`), but `seq` is seeded from the frame's @@ -199,14 +905,13 @@ def write_frame( replaces slot 5's old files instead of piling up a `..._002` beside them, for every tier. - `frame.ir_validity` (the per-pixel IR confidence mask) is not - persisted here -- it isn't part of NegPy's existing scan-output - convention and nothing downstream reads it yet. + `frame.ir_validity` is bound with the two scanner-native RGBI captures + and consumed by repair. Invalid IR pixels retain their original RGB. """ try: resolved_repair_mode = RepairMode(repair_mode) - except ValueError: - resolved_repair_mode = RepairMode.EXACT + except ValueError as error: + raise ValueError(f"unknown repair mode {repair_mode!r}") from error os.makedirs(output_folder, exist_ok=True) date_str = _date.today().strftime("%Y%m%d") @@ -225,10 +930,85 @@ def write_frame( else: outputs["unrepaired"] = {"written": False, "status": "not selected"} + # Tier-1 RGB/IR TIFFs are lossless, but by themselves omit the meter + # prepass, per-pixel IR validity, and frame identities needed to + # reconstruct the scanner-native RepairAcquisition after media moves. + # Retain that small unique evidence independently of immediate Tier-2 + # availability or success. A retention failure is disclosed without + # sacrificing the irreplaceable Tier-1 capture. + prepared_repair_acquisition: roll_repair.RepairAcquisition | None = None + if write_unrepaired and frame.ir is not None: + transaction = _ACTIVE_OUTPUT_TRANSACTION.get() + acquisition_checkpoint = transaction.checkpoint() if transaction is not None else frozenset() + try: + prepared_repair_acquisition = _repair_acquisition_from_frame(frame) + retained_repair_acquisition = _retain_repair_acquisition_evidence( + base_path, + prepared_repair_acquisition, + storage_rgb=frame.rgb, + storage_ir=frame.ir, + rgb_path=rgb_path, + ir_path=ir_path, + ) + except Exception as error: + if transaction is not None: + transaction.discard_after(acquisition_checkpoint) + outputs["repair_acquisition_evidence"] = { + "replayable": False, + "retained": False, + "status": f"unavailable: {error}", + } + else: + outputs["repair_acquisition_evidence"] = { + **retained_repair_acquisition, + "retained": True, + } + + # Preserve the scanner-native color inputs as an acquisition artifact, + # not merely as a side effect of a successful Tier-3 render. A TIFF or + # CMS failure must never discard the only evidence from which that + # exact render can be retried later. + native_receipt_from_frame: roll_exact_color.NativeValidatedBuilderReceipt | None = None + retained_native_evidence: dict[str, object] | None = None + native_receipt_error: Exception | None = None + if getattr(frame, "nikon_exact_builder_evidence", None) is not None: + transaction = _ACTIVE_OUTPUT_TRANSACTION.get() + evidence_checkpoint = transaction.checkpoint() if transaction is not None else frozenset() + try: + native_receipt_from_frame = _build_native_receipt_from_frame(frame) + retained_native_evidence = _retain_native_builder_evidence( + base_path, + native_receipt_from_frame, + ) + except Exception as error: + if transaction is not None: + transaction.discard_after(evidence_checkpoint) + native_receipt_from_frame = None + retained_native_evidence = None + native_receipt_error = error + outputs["native_color_evidence"] = { + "native_per_acquisition_builder": True, + "retained": False, + "scope": roll_exact_color.NATIVE_BUILDER_SCOPE, + "status": f"unavailable: {error}", + } + else: + outputs["native_color_evidence"] = { + "builder_receipt": roll_exact_color.receipt_payload(native_receipt_from_frame), + "builder_receipt_sha256": native_receipt_from_frame.sha256, + "native_per_acquisition_builder": True, + "retained": True, + "retained_builder_evidence": retained_native_evidence, + "scope": roll_exact_color.NATIVE_BUILDER_SCOPE, + } + # -- Tier 2: repaired (also feeds Tier 3, even when not itself written) -- outputs["repaired"] = {"written": False, "status": "not selected"} repair_result: roll_repair.RepairResult | None = None repaired_rgb_path = repaired_ir_path = None + synthesis_mask_path = None + native_synthesis_mask_path = None + hybrid_receipt_path = None if write_repaired or write_positive: if frame.ir is None: outputs["repaired"] = {"written": False, "status": "unavailable: frame has no infrared plane to guide repair"} @@ -236,22 +1016,137 @@ def write_frame( outputs["repaired"] = {"written": False, "status": "unavailable: no dust-repair engine registered"} else: try: - repair_result = roll_repair.repair(frame.rgb, frame.ir, resolved_repair_mode, prepass_rgbi=getattr(frame, "meter_rgbi", None)) + acquisition = ( + prepared_repair_acquisition if prepared_repair_acquisition is not None else _repair_acquisition_from_frame(frame) + ) + repair_result = roll_repair.repair( + acquisition, + resolved_repair_mode, + hybrid_runtime=self._hybrid_runtime, + progress=on_repair_progress, + cancel=self._repair_cancel, + ) + except roll_repair.RepairCancelled: + raise except Exception as error: outputs["repaired"] = {"written": False, "status": f"repair failed: {error}"} else: - entry = { - "engine": repair_result.engine, - "engine_version": repair_result.engine_version, - "mode": str(repair_result.mode), - } - if write_repaired: - repaired_rgb_path = _atomic_write_tiff(base_path + "_repaired.tif", repair_result.rgb, photometric="rgb") - repaired_ir_path = _atomic_write_tiff(base_path + "_repaired_IR.tif", frame.ir, photometric="minisblack") - entry.update(written=True, rgb_path=repaired_rgb_path, ir_path=repaired_ir_path) + transaction = _ACTIVE_OUTPUT_TRANSACTION.get() + repair_checkpoint = transaction.checkpoint() if transaction is not None else frozenset() + try: + source_rgb = np.asarray(repair_result.rgb) + rgb_snapshot = np.array( + source_rgb, + order="C", + copy=True, + ) + rgb_snapshot.setflags(write=False) + routing_snapshot = ( + dict(repair_result.routing_counts) + if isinstance(repair_result.routing_counts, dict) + else repair_result.routing_counts + ) + repair_result = dataclasses.replace( + repair_result, + rgb=rgb_snapshot, + routing_counts=routing_snapshot, + ) + _validate_repair_result_binding( + acquisition, + repair_result, + requested_mode=resolved_repair_mode, + ) + hybrid_evidence = None + if repair_result.mode_resolved is RepairMode.HYBRID: + synthesis_mask_path = _atomic_write_bytes( + base_path + "_repaired_SYNTH.png", + repair_result.storage_synthesis_mask_png, + ) + hybrid_evidence = _retain_hybrid_repair_evidence( + base_path, + acquisition, + repair_result, + ) + native_synthesis_mask_path = hybrid_evidence["native_mask"]["path"] + hybrid_receipt_path = hybrid_evidence["receipt"]["path"] + except Exception as error: + if transaction is not None: + transaction.discard_after(repair_checkpoint) + repair_result = None + synthesis_mask_path = None + native_synthesis_mask_path = None + hybrid_receipt_path = None + outputs["repaired"] = { + "written": False, + "status": f"repair evidence failed: {error}", + } + hybrid_evidence = None + if repair_result is None: + pass else: - entry.update(written=False, status="not selected (computed in memory for the positive)") - outputs["repaired"] = entry + acquisition_entry = { + "acquisition_id": acquisition.acquisition_id, + "slot": acquisition.slot, + "reservation_id": acquisition.reservation_id, + "capture_attempt_id": acquisition.capture_attempt_id, + "evidence_sha256": acquisition.evidence_sha256, + "storage_transform": acquisition.storage_transform, + "main_rgbi_sha256": acquisition.main_rgbi_sha256, + "prepass_rgbi_sha256": acquisition.prepass_rgbi_sha256, + "ir_validity_sha256": acquisition.ir_validity_sha256, + } + entry: dict[str, object] = { + "engine": repair_result.engine, + "engine_version": repair_result.engine_version, + "mode_requested": str(repair_result.mode_requested), + "mode_resolved": str(repair_result.mode_resolved), + "degraded": repair_result.degraded, + "reason": repair_result.reason, + "acquisition": acquisition_entry, + # Kept at top level for older receipt consumers. + "acquisition_id": repair_result.acquisition_id, + "slot": repair_result.slot, + "reservation_id": repair_result.reservation_id, + "evidence_sha256": repair_result.evidence_sha256, + "backend_requested": repair_result.backend_requested, + "backend_used": repair_result.backend_used, + "backend_selection_reason": (repair_result.backend_selection_reason), + "native_output_rgb_sha256": (repair_result.native_output_rgb_sha256), + "storage_output_rgb_sha256": (repair_result.storage_output_rgb_sha256), + } + if hybrid_evidence is not None: + applied_pixels = int(round(repair_result.synthesis_fraction * acquisition.ir_validity.size)) + entry["disclosure_mask"] = { + "applied_final": { + "fraction": repair_result.synthesis_fraction, + "native": hybrid_evidence["native_mask"], + "pixel_count": applied_pixels, + "storage": { + "bytes": len(repair_result.storage_synthesis_mask_png), + "path": synthesis_mask_path, + "sha256": (repair_result.storage_synthesis_mask_sha256), + "shape": list(repair_result.storage_synthesis_mask_shape), + }, + "transform": (repair_result.synthesis_mask_transform), + }, + "routed_raw": { + "native": hybrid_evidence["routed_native_mask"], + "routing_counts": (repair_result.routing_counts), + }, + } + entry["hybrid_receipt"] = { + **hybrid_evidence["receipt"], + "provenance_class": (repair_result.hybrid_provenance_class), + "verified_output_rgb_sha256": (repair_result.hybrid_receipt_output_rgb_sha256), + } + entry["hybrid_evidence_binding"] = hybrid_evidence["binding"] + if write_repaired: + repaired_rgb_path = _atomic_write_tiff(base_path + "_repaired.tif", repair_result.rgb, photometric="rgb") + repaired_ir_path = _atomic_write_tiff(base_path + "_repaired_IR.tif", frame.ir, photometric="minisblack") + entry.update(written=True, rgb_path=repaired_rgb_path, ir_path=repaired_ir_path) + else: + entry.update(written=False, status="not selected (computed in memory for the positive)") + outputs["repaired"] = entry # -- Tier 3: positive ----------------------------------------------- outputs["positive"] = {"written": False, "status": "not selected"} @@ -259,19 +1154,141 @@ def write_frame( if write_positive: if repair_result is None: tier2_status = outputs["repaired"].get("status", "unavailable") - outputs["positive"] = {"written": False, "status": f"unavailable: Tier 2 (repaired) could not be produced ({tier2_status})"} + outputs["positive"] = { + "written": False, + "status": f"unavailable: Tier 2 (repaired) could not be produced ({tier2_status})", + "color_mode": positive_mode, + } + elif positive_mode == roll_exact_color.PositiveColorMode.NIKON_EXACT.value: + transaction = _ACTIVE_OUTPUT_TRANSACTION.get() + exact_checkpoint = transaction.checkpoint() if transaction is not None else frozenset() + try: + exact_icc_profile = roll_nikon_icc.nikon_adobe_rgb_profile() + active_receipt = builder_receipt + active_builder = self._exact_color_builder + active_evaluator = self._exact_color_evaluator + if active_receipt is None: + if native_receipt_from_frame is not None: + active_receipt = native_receipt_from_frame + elif native_receipt_error is not None: + raise roll_exact_color.ExactColorUnavailable(str(native_receipt_error)) from native_receipt_error + else: + active_receipt = _build_native_receipt_from_frame(frame) + if active_builder is None: + from negpy.services.roll.portable_builder import PortableStage1Builder + + active_builder = PortableStage1Builder() + if active_evaluator is None: + from negpy.services.roll.portable_cms import PortableCMSOnEvaluator + + active_evaluator = PortableCMSOnEvaluator() + result = roll_exact_color.evaluate_exact_color( + repair_result.rgb, + builder_receipt=active_receipt, + builder=active_builder, + evaluator=active_evaluator, + ) + builder_application_receipt = result.builder_application_receipt + if builder_application_receipt is None: + raise roll_exact_color.ExactColorIntegrityError("exact-color result is missing its builder application receipt") + native_builder = type(result.builder_receipt) is roll_exact_color.NativeValidatedBuilderReceipt + positive_path = _atomic_write_tiff( + base_path + "_positive.tif", + result.rgb, + photometric="rgb", + iccprofile=exact_icc_profile, + ) + exact_tiff_artifact = _verify_exact_positive_tiff( + positive_path, + expected_rgb=result.rgb, + expected_icc=exact_icc_profile, + ) + # Retain replay evidence only after the TIFF itself has + # passed verification. If retention then fails, the + # transaction checkpoint below discards both together. + if ( + retained_native_evidence is not None + and native_receipt_from_frame is not None + and result.builder_receipt.sha256 == native_receipt_from_frame.sha256 + ): + retained_evidence = retained_native_evidence + else: + retained_evidence = _retain_builder_evidence( + base_path, + result.builder_receipt, + ) + except Exception as error: + if transaction is not None: + transaction.discard_after(exact_checkpoint) + positive_path = None + outputs["positive"] = { + "written": False, + "status": f"unavailable: exact Nikon color: {error}", + "color_mode": roll_exact_color.PositiveColorMode.NIKON_EXACT.value, + } + else: + outputs["positive"] = { + "written": True, + "rgb_path": positive_path, + "color_mode": roll_exact_color.PositiveColorMode.NIKON_EXACT.value, + "exact_nikon_color": True, + "inversion_path": ( + "native-per-acquisition-builder-and-verified-portable-cms" + if native_builder + else "stage3-evidence-replay-bridge-and-verified-portable-cms" + ), + "native_per_acquisition_builder": native_builder, + "repaired_input_rgb_sha256": result.source_rgb_sha256, + "input_rgb_sha256": result.input_rgb_sha256, + "stage1_input_rgb_sha256": result.input_rgb_sha256, + "output_rgb_sha256": result.output_rgb_sha256, + "builder_receipt": roll_exact_color.receipt_payload(result.builder_receipt), + "builder_receipt_sha256": result.builder_receipt.sha256, + "builder_validated": True, + "builder_application_receipt": roll_exact_color.receipt_payload(builder_application_receipt), + "builder_application_receipt_sha256": (builder_application_receipt.sha256), + "cms_receipt": roll_exact_color.receipt_payload(result.cms_receipt), + "cms_receipt_sha256": result.cms_receipt.sha256, + "cms_verified": True, + "icc_profile": roll_nikon_icc.profile_receipt_binding(), + "tiff_artifact": exact_tiff_artifact, + "retained_builder_evidence": retained_evidence, + "repair_engine": repair_result.engine, + "repair_engine_version": repair_result.engine_version, + "repair_mode": str(repair_result.mode_resolved), + } + if native_builder: + outputs["positive"]["native_builder_scope"] = roll_exact_color.NATIVE_BUILDER_SCOPE + else: + outputs["positive"]["replay_bridge_scope"] = roll_exact_color.STAGE3_REPLAY_SCOPE + elif positive_mode != roll_exact_color.PositiveColorMode.NEGPY_APPROXIMATE.value: + outputs["positive"] = { + "written": False, + "status": f"unavailable: unknown positive color mode {positive_mode!r}", + "color_mode": positive_mode, + } elif not roll_positive.available(): - outputs["positive"] = {"written": False, "status": "unavailable: inversion path not available"} + outputs["positive"] = { + "written": False, + "status": "unavailable: inversion path not available", + "color_mode": roll_exact_color.PositiveColorMode.NEGPY_APPROXIMATE.value, + } else: try: result = roll_positive.render_positive(repair_result.rgb, processor=self._get_image_processor()) except Exception as error: - outputs["positive"] = {"written": False, "status": f"inversion failed: {error}"} + outputs["positive"] = { + "written": False, + "status": f"inversion failed: {error}", + "color_mode": roll_exact_color.PositiveColorMode.NEGPY_APPROXIMATE.value, + } else: positive_path = _atomic_write_tiff(base_path + "_positive.tif", result.rgb, photometric="rgb") outputs["positive"] = { "written": True, "rgb_path": positive_path, + "color_mode": roll_exact_color.PositiveColorMode.NEGPY_APPROXIMATE.value, + "exact_nikon_color": False, "inversion_path": "negpy.services.rendering.image_processor.ImageProcessor.run_pipeline", "render_intent": result.render_intent, "process_mode": result.process_mode, @@ -279,11 +1296,14 @@ def write_frame( "negpy_version": result.negpy_version, "repair_engine": repair_result.engine, "repair_engine_version": repair_result.engine_version, - "repair_mode": str(repair_result.mode), + "repair_mode": str(repair_result.mode_resolved), } receipt_path = base_path + "_receipt.json" receipt_payload = dataclasses.asdict(frame.receipt) + receipt_artifacts = getattr(frame.receipt, "artifacts", None) + if receipt_artifacts is not None: + receipt_payload["artifacts"] = {name: dataclasses.asdict(artifact) for name, artifact in receipt_artifacts.items()} receipt_payload["outputs"] = outputs _atomic_write_json(receipt_path, receipt_payload) @@ -295,6 +1315,9 @@ def write_frame( repaired_ir_path=repaired_ir_path, positive_path=positive_path, receipt_path=receipt_path, + synthesis_mask_path=synthesis_mask_path, + native_synthesis_mask_path=native_synthesis_mask_path, + hybrid_receipt_path=hybrid_receipt_path, ) def _get_image_processor(self) -> ImageProcessor: @@ -314,35 +1337,1369 @@ def _require_roll(self) -> coolscanpy_roll.RollHandle: return self._roll +def _repair_acquisition_from_frame(frame: object) -> roll_repair.RepairAcquisition: + prepare = getattr(frame, "prepare_digital_ice", None) + if not callable(prepare): + raise ValueError("frame has no bound scanner-native Digital ICE acquisition") + source = prepare() + if isinstance(source, roll_repair.RepairAcquisition): + if source.slot != getattr(frame, "slot", None): + raise ValueError("Digital ICE acquisition belongs to another frame slot") + _validate_digital_ice_producer_binding(source) + return source + required = ( + "acquisition_id", + "slot", + "reservation_id", + "capture_attempt_id", + "storage_transform", + "evidence_sha256", + "main_rgbi_sha256", + "meter_rgbi_sha256", + "ir_validity_sha256", + "main_rgbi", + "meter_rgbi", + "ir_validity", + ) + missing = [name for name in required if not hasattr(source, name)] + if missing: + raise ValueError("Digital ICE acquisition producer omitted: " + ", ".join(missing)) + if source.slot != getattr(frame, "slot", None): + raise ValueError("Digital ICE acquisition belongs to another frame slot") + acquisition = roll_repair.RepairAcquisition( + acquisition_id=source.acquisition_id, + slot=source.slot, + reservation_id=source.reservation_id, + capture_attempt_id=source.capture_attempt_id, + storage_transform=source.storage_transform, + evidence_sha256=source.evidence_sha256, + main_rgbi_sha256=source.main_rgbi_sha256, + prepass_rgbi_sha256=source.meter_rgbi_sha256, + ir_validity_sha256=source.ir_validity_sha256, + main_rgbi=source.main_rgbi, + prepass_rgbi=source.meter_rgbi, + ir_validity=source.ir_validity, + ) + _validate_digital_ice_producer_binding(acquisition) + return acquisition + + +def _npy_payload(array: np.ndarray, *, dtype: np.dtype) -> bytes: + canonical = np.array(array, dtype=dtype, order="C", copy=True) + stream = io.BytesIO() + np.save(stream, canonical, allow_pickle=False) + return stream.getvalue() + + +def _derive_digital_ice_producer_binding( + *, + slot: int, + reservation_id: str, + capture_attempt_id: str, + main_rgbi: np.ndarray, + prepass_rgbi: np.ndarray, + ir_validity: np.ndarray, +) -> tuple[str, str]: + """Reproduce Coolscanpy's v1 DigitalIceAcquisitionEvidence hashes.""" + + identity_document = { + "capture_attempt_id": capture_attempt_id, + "kind": "coolscanpy.digital-ice-acquisition-identity", + "reservation_id": reservation_id, + "slot": slot, + "version": 1, + } + identity_bytes = json.dumps( + identity_document, + sort_keys=True, + separators=(",", ":"), + ensure_ascii=False, + allow_nan=False, + ).encode("utf-8") + acquisition_id = "dice-" + hashlib.sha256(identity_bytes).hexdigest() + + native_main = np.array(main_rgbi, dtype=" dict[str, object]: + canonical = np.array(array, dtype=dtype, order="C", copy=True) + payload = memoryview(canonical).cast("B") + return { + "byte_length": payload.nbytes, + "dtype": canonical.dtype.str, + "sha256": hashlib.sha256(payload).hexdigest(), + "shape": list(canonical.shape), + } + + evidence_document = { + "acquisition_id": acquisition_id, + "artifacts": { + "scanner_native_ir_validity": artifact( + native_validity, + dtype=np.dtype(np.bool_), + ), + "scanner_native_main_rgbi": artifact( + native_main, + dtype=np.dtype(" None: + expected_acquisition_id, expected_evidence_sha256 = _derive_digital_ice_producer_binding( + slot=acquisition.slot, + reservation_id=acquisition.reservation_id, + capture_attempt_id=acquisition.capture_attempt_id, + main_rgbi=acquisition.main_rgbi, + prepass_rgbi=acquisition.prepass_rgbi, + ir_validity=acquisition.ir_validity, + ) + if acquisition.acquisition_id != expected_acquisition_id: + raise ValueError("Digital ICE acquisition identity is not canonical") + if acquisition.evidence_sha256 != expected_evidence_sha256: + raise ValueError("Digital ICE producer evidence SHA-256 changed") + + +def _retain_repair_acquisition_evidence( + base_path: str, + acquisition: roll_repair.RepairAcquisition, + *, + storage_rgb: np.ndarray, + storage_ir: np.ndarray, + rgb_path: str | None, + ir_path: str | None, +) -> dict[str, object]: + """Retain the unique inputs needed to replay repair after media moves.""" + + if rgb_path is None or ir_path is None: + raise ValueError("Tier-1 RGB and IR paths are required for repair replay") + rgb = np.asarray(storage_rgb) + infrared = np.asarray(storage_ir) + expected_storage_shape = ( + acquisition.main_rgbi.shape[1], + acquisition.main_rgbi.shape[0], + ) + if ( + rgb.dtype != np.uint16 + or rgb.shape != (*expected_storage_shape, 3) + or infrared.dtype != np.uint16 + or infrared.shape != expected_storage_shape + ): + raise ValueError("Tier-1 planes do not match the Digital ICE acquisition") + storage_rgbi = np.dstack((rgb, infrared)) + reconstructed_native = np.ascontiguousarray(np.rot90(storage_rgbi, k=-1, axes=(0, 1))) + if _rgb16_sha256(reconstructed_native) != acquisition.main_rgbi_sha256: + raise ValueError("Tier-1 planes do not reconstruct the captured main RGBI") + _validate_digital_ice_producer_binding(acquisition) + + evidence_root = os.path.join( + os.path.dirname(base_path) or ".", + ".negpy-dice-acquisition", + ) + evidence_token = hashlib.sha256((acquisition.acquisition_id + "\0" + os.path.basename(base_path)).encode("utf-8")).hexdigest() + evidence_directory = os.path.join(evidence_root, evidence_token) + if any(os.path.lexists(path) and os.path.islink(path) for path in (evidence_root, evidence_directory)): + raise OSError("repair acquisition evidence path is a symlink") + _prepare_evidence_directory(evidence_directory) + + prepass_payload = _npy_payload( + acquisition.prepass_rgbi, + dtype=np.dtype(" dict[str, object]: + row: dict[str, object] = { + "dtype": dtype, + "path": path, + "raw_sha256": raw_sha256, + "relative_path": os.path.relpath(path, evidence_directory), + "shape": list(shape), + } + if payload is not None: + row.update( + bytes=len(payload), + file_sha256=hashlib.sha256(payload).hexdigest(), + ) + return row + + sources = { + "storage_ir_tiff": artifact( + path=ir_path, + payload=None, + raw_sha256=_rgb16_sha256(infrared), + shape=infrared.shape, + dtype=" np.ndarray: + before = os.lstat(path) + if stat.S_ISLNK(before.st_mode) or not stat.S_ISREG(before.st_mode): + raise ValueError(f"{label} must be a regular non-symlink file") + flags = os.O_RDONLY + if hasattr(os, "O_CLOEXEC"): + flags |= os.O_CLOEXEC + if hasattr(os, "O_NOFOLLOW"): + flags |= os.O_NOFOLLOW + if hasattr(os, "O_NONBLOCK"): + flags |= os.O_NONBLOCK + descriptor = os.open(path, flags) + + def identity(metadata: os.stat_result) -> tuple[int, int, int, int, int]: + return ( + metadata.st_dev, + metadata.st_ino, + metadata.st_size, + metadata.st_mtime_ns, + metadata.st_ctime_ns, + ) + + try: + opened = os.fstat(descriptor) + if not stat.S_ISREG(opened.st_mode) or identity(opened) != identity(before): + raise ValueError(f"{label} changed while it was opened") + try: + with os.fdopen(os.dup(descriptor), "rb") as stream: + with tifffile.TiffFile( + stream, + name=os.path.basename(path), + ) as image: + if len(image.pages) != 1: + raise ValueError(f"{label} must contain exactly one page") + page = image.pages[0] + if tuple(page.shape) != expected_shape or page.dtype != np.uint16: + raise ValueError(f"{label} geometry or dtype changed") + decoded = np.array(page.asarray(), order="C", copy=True) + except ValueError: + raise + except Exception as error: + raise ValueError(f"cannot decode {label}: {error}") from error + after_decode = os.fstat(descriptor) + finally: + os.close(descriptor) + after_path = os.lstat(path) + if identity(after_decode) != identity(opened) or identity(after_path) != identity(opened): + raise ValueError(f"{label} changed while it was decoded") + return decoded + + +def load_repair_acquisition_evidence( + binding_path: str | os.PathLike[str], +) -> roll_repair.RepairAcquisition: + """Rebuild a hash-verified RepairAcquisition from a Tier-1 archive.""" + + binding_file = os.path.abspath(os.fspath(binding_path)) + payload = _stable_regular_bytes( + binding_file, + maximum_bytes=_MAX_RECEIPT_BYTES, + label="repair acquisition binding", + ) + try: + document = _strict_json_loads(payload) + except (UnicodeDecodeError, json.JSONDecodeError) as error: + raise ValueError(f"repair acquisition binding is invalid JSON: {error}") from error + if not isinstance(document, dict): + raise ValueError("repair acquisition binding must contain an object") + canonical = ( + json.dumps( + document, + sort_keys=True, + separators=(",", ":"), + ensure_ascii=True, + allow_nan=False, + ).encode("utf-8") + + b"\n" + ) + if canonical != payload or document.get("schema") != "negpy.dice-acquisition-replay-v1": + raise ValueError("repair acquisition binding is not canonical or supported") + acquisition = document.get("acquisition") + artifacts = document.get("artifacts") + sources = document.get("sources") + expected_replay_contract = { + "authenticity": "integrity-bound-not-signed", + "complete": True, + "reconstruction": "stack-upright-storage-rgb-ir-then-rot90-k-1", + "requires": [ + "storage_rgb_tiff", + "storage_ir_tiff", + "prepass_rgbi", + "ir_validity", + "acquisition_provenance", + ], + "storage_orientation": "upright-storage", + } + if not all(isinstance(value, dict) for value in (acquisition, artifacts, sources)): + raise ValueError("repair acquisition binding sections are malformed") + if document.get("replay") != expected_replay_contract: + raise ValueError("repair acquisition replay requirements changed") + + evidence_directory = os.path.dirname(binding_file) + archive_root = os.path.realpath(os.path.dirname(os.path.dirname(evidence_directory))) + + def row_path(row: object, *, label: str) -> tuple[str, dict[str, object]]: + if not isinstance(row, dict): + raise ValueError(f"repair acquisition {label} artifact is malformed") + relative = row.get("relative_path") + if type(relative) is not str or os.path.isabs(relative): + raise ValueError(f"repair acquisition {label} relative path is invalid") + candidate = os.path.abspath(os.path.join(evidence_directory, relative)) + resolved_parent = os.path.realpath(os.path.dirname(candidate)) + resolved_candidate = os.path.join( + resolved_parent, + os.path.basename(candidate), + ) + try: + inside = os.path.commonpath((archive_root, resolved_candidate)) == archive_root + except ValueError: + inside = False + if not inside: + raise ValueError(f"repair acquisition {label} path escapes its archive") + return candidate, cast(dict[str, object], row) + + def shape(row: dict[str, object], *, label: str) -> tuple[int, ...]: + values = row.get("shape") + if ( + not isinstance(values, list) + or not values + or len(values) > 3 + or any(type(value) is not int or not 1 <= value <= 10_000 for value in values) + ): + raise ValueError(f"repair acquisition {label} shape is invalid") + result = tuple(cast(list[int], values)) + element_count = 1 + for dimension in result: + element_count *= dimension + if element_count > 150_000_000: + raise ValueError(f"repair acquisition {label} shape is too large") + return result + + def load_npy( + row: object, + *, + label: str, + expected_dtype: np.dtype, + ) -> np.ndarray: + path, artifact_row = row_path(row, label=label) + expected_shape = shape(artifact_row, label=label) + if (label == "prepass RGBI" and (len(expected_shape) != 3 or expected_shape[-1] != 4)) or ( + label == "IR validity" and len(expected_shape) != 2 + ): + raise ValueError(f"repair acquisition {label} shape is invalid") + if artifact_row.get("dtype") != expected_dtype.str: + raise ValueError(f"repair acquisition {label} dtype declaration changed") + encoded = _stable_regular_bytes( + path, + maximum_bytes=_MAX_SHARED_EVIDENCE_BYTES, + label=f"repair acquisition {label}", + ) + if artifact_row.get("file_sha256") != hashlib.sha256(encoded).hexdigest(): + raise ValueError(f"repair acquisition {label} file SHA-256 changed") + try: + stream = io.BytesIO(encoded) + version = np.lib.format.read_magic(stream) + if version == (1, 0): + header_shape, fortran_order, header_dtype = np.lib.format.read_array_header_1_0(stream) + elif version == (2, 0): + header_shape, fortran_order, header_dtype = np.lib.format.read_array_header_2_0(stream) + else: + raise ValueError(f"unsupported NPY version {version!r}") + if tuple(header_shape) != expected_shape or header_dtype != expected_dtype or fortran_order: + raise ValueError("NPY header geometry or dtype changed") + expected_bytes = int(np.prod(expected_shape, dtype=np.int64)) * expected_dtype.itemsize + if len(encoded) - stream.tell() != expected_bytes: + raise ValueError("NPY byte length changed") + stream.seek(0) + decoded = np.load(stream, allow_pickle=False) + except Exception as error: + raise ValueError(f"repair acquisition {label} NPY is invalid: {error}") from error + if not isinstance(decoded, np.ndarray) or decoded.dtype != expected_dtype or decoded.shape != expected_shape: + raise ValueError(f"repair acquisition {label} array changed") + canonical = np.array(decoded, dtype=expected_dtype, order="C", copy=True) + digest_dtype = np.dtype(np.bool_) if expected_dtype == np.dtype(np.bool_) else np.dtype(" str: + canonical = np.array(array, dtype=" np.ndarray: + try: + with Image.open(io.BytesIO(payload)) as image: + if image.format != "PNG": + raise ValueError("not a PNG") + if image.size != (expected_shape[1], expected_shape[0]): + raise ValueError("geometry changed") + decoded = np.asarray(image.convert("L")) + except Exception as error: + raise ValueError(f"{label} is not a valid PNG: {error}") from error + if not np.all(np.isin(np.unique(decoded), np.array([0, 255], dtype=np.uint8))): + raise ValueError(f"{label} is not binary") + return np.ascontiguousarray(decoded != 0) + + +def _validate_repair_result_binding( + acquisition: roll_repair.RepairAcquisition, + result: roll_repair.RepairResult, + *, + requested_mode: RepairMode, +) -> None: + """Fail closed before any Tier-2 result or evidence is published.""" + + for label, actual, expected in ( + ("acquisition ID", result.acquisition_id, acquisition.acquisition_id), + ("slot", result.slot, acquisition.slot), + ("reservation ID", result.reservation_id, acquisition.reservation_id), + ("evidence SHA-256", result.evidence_sha256, acquisition.evidence_sha256), + ("requested mode", result.mode_requested, requested_mode), + ): + if actual != expected: + raise ValueError(f"repair result {label} disagrees with its acquisition") + if requested_mode is RepairMode.EXACT and result.mode_resolved is RepairMode.HYBRID: + raise ValueError("exact repair request cannot resolve to hybrid") + if result.mode_resolved not in (RepairMode.EXACT, RepairMode.HYBRID): + raise ValueError("repair result resolved mode is invalid") + storage_shape = ( + acquisition.main_rgbi.shape[1], + acquisition.main_rgbi.shape[0], + 3, + ) + rgb = np.asarray(result.rgb) + if rgb.dtype != np.uint16 or rgb.shape != storage_shape or not rgb.flags.c_contiguous: + raise ValueError("repair result has invalid storage-oriented RGB geometry") + storage_hash = _rgb16_sha256(rgb) + if result.storage_output_rgb_sha256 != storage_hash: + raise ValueError("repair result storage RGB SHA-256 changed") + native_rgb = np.ascontiguousarray(np.rot90(rgb, k=-1, axes=(0, 1))) + native_hash = _rgb16_sha256(native_rgb) + if result.native_output_rgb_sha256 != native_hash: + raise ValueError("repair result scanner-native RGB SHA-256 changed") + + if result.mode_resolved is not RepairMode.HYBRID: + forbidden = ( + result.native_synthesis_mask_png, + result.native_synthesis_mask_sha256, + result.native_synthesis_mask_shape, + result.routed_native_synthesis_mask_png, + result.routed_native_synthesis_mask_sha256, + result.routed_native_synthesis_mask_shape, + result.storage_synthesis_mask_png, + result.storage_synthesis_mask_sha256, + result.storage_synthesis_mask_shape, + result.synthesis_mask_transform, + result.synthesis_fraction, + result.routing_counts, + result.hybrid_receipt, + result.hybrid_receipt_sha256, + result.hybrid_provenance_class, + result.hybrid_receipt_output_rgb_sha256, + ) + if any(value is not None for value in forbidden): + raise ValueError("non-hybrid repair result carries hybrid evidence") + return + + required = ( + result.native_synthesis_mask_png, + result.native_synthesis_mask_sha256, + result.native_synthesis_mask_shape, + result.routed_native_synthesis_mask_png, + result.routed_native_synthesis_mask_sha256, + result.routed_native_synthesis_mask_shape, + result.storage_synthesis_mask_png, + result.storage_synthesis_mask_sha256, + result.storage_synthesis_mask_shape, + result.synthesis_mask_transform, + result.synthesis_fraction, + result.routing_counts, + result.hybrid_receipt, + result.hybrid_receipt_sha256, + result.hybrid_receipt_output_rgb_sha256, + result.hybrid_provenance_class, + ) + if any(value is None for value in required): + raise ValueError("hybrid repair result omitted disclosure evidence") + native_png = result.native_synthesis_mask_png + routed_native_png = result.routed_native_synthesis_mask_png + storage_png = result.storage_synthesis_mask_png + receipt = result.hybrid_receipt + if not all(type(value) is bytes for value in (native_png, routed_native_png, storage_png, receipt)): + raise ValueError("hybrid disclosure evidence must be immutable bytes") + if hashlib.sha256(native_png).hexdigest() != result.native_synthesis_mask_sha256: + raise ValueError("scanner-native disclosure mask SHA-256 changed") + if hashlib.sha256(storage_png).hexdigest() != result.storage_synthesis_mask_sha256: + raise ValueError("storage disclosure mask SHA-256 changed") + if hashlib.sha256(routed_native_png).hexdigest() != result.routed_native_synthesis_mask_sha256: + raise ValueError("routed scanner-native mask SHA-256 changed") + if hashlib.sha256(receipt).hexdigest() != result.hybrid_receipt_sha256: + raise ValueError("hybrid receipt SHA-256 changed") + if len(result.hybrid_receipt_output_rgb_sha256) != 64 or any( + character not in "0123456789abcdef" for character in result.hybrid_receipt_output_rgb_sha256 + ): + raise ValueError("hybrid receipt output RGB SHA-256 is malformed") + try: + receipt_document = _strict_json_loads(receipt) + except (UnicodeDecodeError, ValueError, TypeError) as error: + raise ValueError(f"hybrid receipt JSON is invalid: {error}") from error + if not isinstance(receipt_document, dict): + raise ValueError("hybrid receipt must contain a JSON object") + try: + canonical_receipt = ( + json.dumps( + receipt_document, + sort_keys=True, + separators=(",", ":"), + ensure_ascii=True, + allow_nan=False, + ).encode("utf-8") + + b"\n" + ) + except (TypeError, ValueError, RecursionError) as error: + raise ValueError(f"hybrid receipt JSON is not canonical: {error}") from error + if canonical_receipt != receipt: + raise ValueError("hybrid receipt JSON is not canonical") + if receipt_document.get("schema") != "fauxce-hybrid-receipt-v2" or result.hybrid_provenance_class != "caller_asserted_bare_npy": + raise ValueError("hybrid receipt schema or provenance class changed") + native_mask = _decode_binary_mask( + native_png, + expected_shape=acquisition.main_rgbi.shape[:2], + label="final scanner-native disclosure mask", + ) + routed_native_mask = _decode_binary_mask( + routed_native_png, + expected_shape=acquisition.main_rgbi.shape[:2], + label="routed scanner-native disclosure mask", + ) + storage_mask = _decode_binary_mask( + storage_png, + expected_shape=( + acquisition.main_rgbi.shape[1], + acquisition.main_rgbi.shape[0], + ), + label="storage disclosure mask", + ) + if ( + tuple(native_mask.shape) != result.native_synthesis_mask_shape + or native_mask.shape != acquisition.main_rgbi.shape[:2] + or tuple(routed_native_mask.shape) != result.routed_native_synthesis_mask_shape + or routed_native_mask.shape != acquisition.main_rgbi.shape[:2] + or not np.array_equal( + native_mask, + routed_native_mask & acquisition.ir_validity, + ) + ): + raise ValueError("scanner-native disclosure mask geometry changed") + expected_storage = acquisition.storage_mask(native_mask) + if ( + tuple(storage_mask.shape) != result.storage_synthesis_mask_shape + or not np.array_equal(storage_mask, expected_storage) + or result.synthesis_mask_transform != acquisition.storage_transform + ): + raise ValueError("storage disclosure mask transform binding changed") + synthesis_pixels = int(np.count_nonzero(native_mask)) + routed_pixels = int(np.count_nonzero(routed_native_mask)) + frame_pixels = int(native_mask.size) + if result.synthesis_fraction != synthesis_pixels / frame_pixels: + raise ValueError("hybrid synthesis fraction disagrees with disclosure mask") + counts = result.routing_counts + routing = receipt_document.get("routing") + receipt_counts = routing.get("counts") if isinstance(routing, dict) else None + count_keys = ( + "final_regions", + "synthesis_pixels", + "frame_pixels", + "at_floor_pixels", + ) + if ( + not isinstance(counts, dict) + or not isinstance(receipt_counts, dict) + or any(type(counts.get(key)) is not int for key in count_keys) + or any(type(receipt_counts.get(key)) is not int for key in count_keys) + or any(counts[key] != receipt_counts[key] for key in count_keys) + or any(counts[key] < 0 for key in count_keys) + or counts.get("synthesis_pixels") != routed_pixels + or counts.get("frame_pixels") != frame_pixels + or counts["at_floor_pixels"] > frame_pixels + or counts["synthesis_pixels"] > counts["at_floor_pixels"] + or counts["final_regions"] > counts["synthesis_pixels"] + or (counts["final_regions"] == 0) != (routed_pixels == 0) + ): + raise ValueError("hybrid routing counts disagree with disclosure mask") + synthesis = receipt_document.get("synthesis") + if ( + not isinstance(synthesis, dict) + or synthesis.get("pixel_count") != routed_pixels + or synthesis.get("frame_pixel_count") != frame_pixels + or synthesis.get("fraction") != routed_pixels / frame_pixels + or synthesis.get("within_budget") is not True + ): + raise ValueError("hybrid receipt synthesis accounting changed") + artifacts = receipt_document.get("artifacts") + output_artifacts = ( + [artifact for artifact in artifacts if isinstance(artifact, dict) and artifact.get("role") == "hybrid_output_rgb16"] + if isinstance(artifacts, list) + else [] + ) + composite = receipt_document.get("composite") + if ( + len(output_artifacts) != 1 + or output_artifacts[0].get("raw_sha256") != result.hybrid_receipt_output_rgb_sha256 + or not isinstance(composite, dict) + or composite.get("hybrid_rgb16_raw_sha256") != result.hybrid_receipt_output_rgb_sha256 + ): + raise ValueError("hybrid receipt output binding changed") + mask_artifacts = ( + [artifact for artifact in artifacts if isinstance(artifact, dict) and artifact.get("role") == "synthesis_mask_png"] + if isinstance(artifacts, list) + else [] + ) + routed_u8 = np.ascontiguousarray(routed_native_mask.astype(np.uint8) * np.uint8(255)) + if ( + len(mask_artifacts) != 1 + or mask_artifacts[0].get("file_sha256") != result.routed_native_synthesis_mask_sha256 + or mask_artifacts[0].get("raw_sha256") != hashlib.sha256(memoryview(routed_u8).cast("B")).hexdigest() + or mask_artifacts[0].get("shape") != list(routed_u8.shape) + or mask_artifacts[0].get("dtype") != "|u1" + ): + raise ValueError("hybrid receipt routed mask binding changed") + + +def _retain_hybrid_repair_evidence( + base_path: str, + acquisition: roll_repair.RepairAcquisition, + result: roll_repair.RepairResult, +) -> dict[str, dict[str, object]]: + """Retain verbatim verified native evidence beside user-facing output.""" + + evidence_root = os.path.join( + os.path.dirname(base_path) or ".", + ".negpy-dice-hybrid", + ) + evidence_directory = os.path.join(evidence_root, result.hybrid_receipt_sha256) + if any(os.path.lexists(path) and os.path.islink(path) for path in (evidence_root, evidence_directory)): + raise OSError("hybrid evidence path is a symlink") + _prepare_evidence_directory(evidence_directory) + receipt_path = _atomic_write_bytes( + os.path.join(evidence_directory, "hybrid-receipt.json"), + result.hybrid_receipt, + ) + native_mask_path = _atomic_write_bytes( + os.path.join( + evidence_directory, + "synth-mask-applied-scanner-native.png", + ), + result.native_synthesis_mask_png, + ) + routed_native_mask_path = _atomic_write_bytes( + os.path.join( + evidence_directory, + "synth-mask-routed-scanner-native.png", + ), + result.routed_native_synthesis_mask_png, + ) + binding = { + "acquisition": { + "acquisition_id": acquisition.acquisition_id, + "capture_attempt_id": acquisition.capture_attempt_id, + "evidence_sha256": acquisition.evidence_sha256, + "ir_validity_sha256": acquisition.ir_validity_sha256, + "main_rgbi_sha256": acquisition.main_rgbi_sha256, + "prepass_rgbi_sha256": acquisition.prepass_rgbi_sha256, + "reservation_id": acquisition.reservation_id, + "slot": acquisition.slot, + "storage_transform": acquisition.storage_transform, + }, + "hybrid_receipt_sha256": result.hybrid_receipt_sha256, + "hybrid_receipt_output_rgb_sha256": (result.hybrid_receipt_output_rgb_sha256), + "native_output_rgb_sha256": result.native_output_rgb_sha256, + "native_synthesis_mask_sha256": result.native_synthesis_mask_sha256, + "routed_native_synthesis_mask_sha256": (result.routed_native_synthesis_mask_sha256), + "provenance_class": result.hybrid_provenance_class, + "schema": "negpy.dice-hybrid-retained-evidence-v2", + "storage_output_rgb_sha256": result.storage_output_rgb_sha256, + "storage_synthesis_mask_sha256": result.storage_synthesis_mask_sha256, + "synthesis_mask_transform": result.synthesis_mask_transform, + } + binding_bytes = json.dumps( + binding, + sort_keys=True, + separators=(",", ":"), + ensure_ascii=False, + allow_nan=False, + ).encode("utf-8") + binding_path = _atomic_write_bytes( + os.path.join(evidence_directory, "negpy-binding.json"), + binding_bytes, + ) + return { + "binding": { + "bytes": len(binding_bytes), + "path": binding_path, + "sha256": hashlib.sha256(binding_bytes).hexdigest(), + }, + "native_mask": { + "bytes": len(result.native_synthesis_mask_png), + "path": native_mask_path, + "sha256": result.native_synthesis_mask_sha256, + "shape": list(result.native_synthesis_mask_shape), + }, + "routed_native_mask": { + "bytes": len(result.routed_native_synthesis_mask_png), + "path": routed_native_mask_path, + "sha256": result.routed_native_synthesis_mask_sha256, + "shape": list(result.routed_native_synthesis_mask_shape), + }, + "receipt": { + "bytes": len(result.hybrid_receipt), + "path": receipt_path, + "sha256": result.hybrid_receipt_sha256, + }, + } + + def _ensure_uint16(array: np.ndarray) -> np.ndarray: return array if array.dtype == np.uint16 else array.astype(np.uint16) -def _atomic_write_tiff(path: str, array: np.ndarray, *, photometric: str) -> str: +def _atomic_write_tiff(path: str, array: np.ndarray, *, photometric: str, iccprofile: bytes | None = None) -> str: """Write `array` to `path` via a temp file + rename, matching the atomic-write convention `negpy.services.scanning.writer` already uses for the plain scan path.""" + final_path = path + transaction = _ACTIVE_OUTPUT_TRANSACTION.get() + if transaction is not None: + path = transaction.stage_path(path) fd, tmp_path = tempfile.mkstemp(suffix=".tif", dir=os.path.dirname(path) or ".") os.close(fd) try: - tifffile.imwrite(tmp_path, _ensure_uint16(array), photometric=photometric, compression="lzw") + if iccprofile is None: + tifffile.imwrite(tmp_path, _ensure_uint16(array), photometric=photometric, compression="lzw") + else: + tifffile.imwrite( + tmp_path, + _ensure_uint16(array), + photometric=photometric, + compression="lzw", + iccprofile=iccprofile, + ) + sync_descriptor = os.open(tmp_path, os.O_RDONLY) + try: + os.fsync(sync_descriptor) + finally: + os.close(sync_descriptor) os.replace(tmp_path, path) except Exception: if os.path.exists(tmp_path): os.unlink(tmp_path) raise - return path + return final_path def _atomic_write_json(path: str, payload: dict) -> str: + final_path = path + transaction = _ACTIVE_OUTPUT_TRANSACTION.get() + if transaction is not None: + path = transaction.stage_path(path) tmp_path = None try: with tempfile.NamedTemporaryFile("w", dir=os.path.dirname(path) or ".", delete=False, suffix=".part", encoding="utf-8") as tmp: tmp_path = tmp.name - json.dump(payload, tmp, default=str, indent=2) + json.dump(payload, tmp, indent=2, allow_nan=False) + tmp.flush() + os.fsync(tmp.fileno()) os.replace(tmp_path, path) except Exception: if tmp_path is not None and os.path.exists(tmp_path): os.unlink(tmp_path) raise - return path + return final_path + + +def _atomic_write_bytes(path: str, payload: bytes) -> str: + final_path = path + transaction = _ACTIVE_OUTPUT_TRANSACTION.get() + if transaction is not None: + path = transaction.stage_path(path) + fd, tmp_path = tempfile.mkstemp(suffix=".part", dir=os.path.dirname(path) or ".") + try: + with os.fdopen(fd, "wb") as stream: + stream.write(payload) + stream.flush() + os.fsync(stream.fileno()) + os.replace(tmp_path, path) + except Exception: + if os.path.exists(tmp_path): + os.unlink(tmp_path) + raise + return final_path + + +def _verify_exact_positive_tiff( + path: str, + *, + expected_rgb: np.ndarray, + expected_icc: bytes, +) -> dict[str, object]: + """Reopen the staged exact-positive TIFF and bind its real container.""" + + transaction = _ACTIVE_OUTPUT_TRANSACTION.get() + actual_path = transaction.staged_path(path) if transaction is not None else path + before = os.lstat(actual_path) + if stat.S_ISLNK(before.st_mode) or not stat.S_ISREG(before.st_mode): + raise roll_exact_color.ExactColorIntegrityError("exact-positive TIFF is not a regular file") + flags = os.O_RDONLY + if hasattr(os, "O_CLOEXEC"): + flags |= os.O_CLOEXEC + if hasattr(os, "O_NOFOLLOW"): + flags |= os.O_NOFOLLOW + if hasattr(os, "O_NONBLOCK"): + flags |= os.O_NONBLOCK + descriptor = os.open(actual_path, flags) + file_digest = hashlib.sha256() + byte_count = 0 + try: + opened = os.fstat(descriptor) + + def identity(metadata: os.stat_result) -> tuple[int, int, int, int, int]: + return ( + metadata.st_dev, + metadata.st_ino, + metadata.st_size, + metadata.st_mtime_ns, + metadata.st_ctime_ns, + ) + + if not stat.S_ISREG(opened.st_mode) or identity(opened) != identity(before): + raise roll_exact_color.ExactColorIntegrityError("exact-positive TIFF changed while it was opened") + while True: + chunk = os.read(descriptor, 1024 * 1024) + if not chunk: + break + file_digest.update(chunk) + byte_count += len(chunk) + after_read = os.fstat(descriptor) + if identity(after_read) != identity(opened) or byte_count != opened.st_size: + raise roll_exact_color.ExactColorIntegrityError("exact-positive TIFF size changed while it was verified") + + expected = np.asarray(expected_rgb) + expected_pixel_sha256 = roll_exact_color.rgb16_content_sha256(expected) + try: + # Decode through a duplicate of the descriptor whose bytes were + # just hashed. Reopening the pathname here would let a swap bind + # one file's hash to another file's pixels and ICC profile. + os.lseek(descriptor, 0, os.SEEK_SET) + with os.fdopen(os.dup(descriptor), "rb") as stream: + # ``fdopen`` exposes the integer descriptor as ``.name``; + # tifffile normalizes that as a path unless given a benign + # display hint. The stream remains the descriptor-pinned + # source of every decoded byte. + with tifffile.TiffFile( + stream, + name=os.path.basename(actual_path), + ) as image: + if len(image.pages) != 1: + raise roll_exact_color.ExactColorIntegrityError("exact-positive TIFF must contain exactly one page") + page = image.pages[0] + decoded = np.asarray(page.asarray()) + samples_per_pixel = int(page.samplesperpixel) + bits_value = page.tags[258].value + if isinstance(bits_value, (tuple, list)): + bits = tuple(int(value) for value in bits_value) + else: + bits = (int(bits_value),) * samples_per_pixel + photometric = int(page.photometric) + planar = int(page.planarconfig) + orientation_tag = page.tags.get(274) + orientation = 1 if orientation_tag is None else int(orientation_tag.value) + icc_tag = page.tags.get(34675) + icc = None if icc_tag is None else bytes(icc_tag.value) + except roll_exact_color.ExactColorIntegrityError: + raise + except Exception as error: + raise roll_exact_color.ExactColorIntegrityError(f"cannot verify exact-positive TIFF: {error}") from error + after_decode = os.fstat(descriptor) + finally: + os.close(descriptor) + after_path = os.lstat(actual_path) + if identity(after_decode) != identity(opened) or identity(after_path) != identity(opened) or not stat.S_ISREG(after_path.st_mode): + raise roll_exact_color.ExactColorIntegrityError("exact-positive TIFF changed while it was verified") + if ( + decoded.dtype != np.uint16 + or decoded.shape != expected.shape + or expected.shape[-1:] != (3,) + or samples_per_pixel != 3 + or photometric != 2 + or planar != 1 + or orientation != 1 + or bits != (16, 16, 16) + ): + raise roll_exact_color.ExactColorIntegrityError("exact-positive TIFF layout is not one contiguous 16-bit RGB image") + pixel_sha256 = roll_exact_color.rgb16_content_sha256(decoded) + if pixel_sha256 != expected_pixel_sha256 or not np.array_equal(decoded, expected): + raise roll_exact_color.ExactColorIntegrityError("exact-positive TIFF pixels changed during encoding") + if ( + icc is None + or len(icc) != len(expected_icc) + or icc != expected_icc + or hashlib.sha256(icc).hexdigest() != hashlib.sha256(expected_icc).hexdigest() + ): + raise roll_exact_color.ExactColorIntegrityError("exact-positive TIFF Nikon ICC tag is missing or changed") + return { + "bits_per_sample": list(bits), + "bytes": byte_count, + "dtype": str(decoded.dtype), + "file_sha256": file_digest.hexdigest(), + "icc_bytes": len(icc), + "icc_sha256": hashlib.sha256(icc).hexdigest(), + "page_count": 1, + "orientation": "top-left", + "photometric": "rgb", + "planar_config": "contiguous", + "pixel_sha256": pixel_sha256, + "samples_per_pixel": 3, + "shape": list(decoded.shape), + } + + +def _build_native_receipt_from_frame( + frame: object, +) -> roll_exact_color.NativeValidatedBuilderReceipt: + evidence = getattr(frame, "nikon_exact_builder_evidence", None) + if evidence is None: + if getattr(frame, "nikon_density_evidence", None) is not None or getattr(frame, "nikon_density_ownership", None) is not None: + raise roll_exact_color.ExactColorUnavailable("frame-bound Nikon density evidence has no native builder evidence") + raise roll_exact_color.ExactColorUnavailable( + "validated Stage-3 builder receipt is not supplied and frame has no native builder evidence" + ) + evidence = roll_native_builder.adapt_native_builder_evidence(evidence) + if evidence.slot != getattr(frame, "slot", None): + raise roll_exact_color.ExactColorUnavailable("frame native builder evidence belongs to a different slot") + public_receipt = getattr(frame, "receipt", None) + ownership = getattr(frame, "nikon_density_ownership", None) + receipt_ownership = None if public_receipt is None else getattr(public_receipt, "nikon_density_ownership", None) + if ownership is None: + raise roll_exact_color.ExactColorUnavailable("Nikon density frame ownership receipt is missing") + if receipt_ownership is None: + raise roll_exact_color.ExactColorUnavailable("public frame receipt has no Nikon density ownership") + frame_density_evidence = getattr(frame, "nikon_density_evidence", None) + if frame_density_evidence is None: + raise roll_exact_color.ExactColorUnavailable("frame-bound Nikon density evidence is missing") + ownership_payload = _canonical_component_payload(ownership, label="Nikon density frame ownership") + receipt_ownership_payload = _canonical_component_payload( + receipt_ownership, + label="public-frame Nikon density ownership", + ) + if receipt_ownership_payload != ownership_payload: + raise roll_exact_color.ExactColorUnavailable("frame and public receipt disagree on Nikon density ownership") + density_payload = _canonical_component_payload(frame_density_evidence, label="frame-bound Nikon density evidence") + if ownership_payload != evidence.frame_ownership_receipt: + raise roll_exact_color.ExactColorUnavailable("Nikon density frame ownership does not match the native builder evidence") + if density_payload != evidence.density_evidence_receipt: + raise roll_exact_color.ExactColorUnavailable("frame-bound Nikon density evidence does not match the native builder evidence") + validator = getattr(ownership, "validate_evidence", None) + if callable(validator): + try: + validator(frame_density_evidence) + except (TypeError, ValueError) as error: + raise roll_exact_color.ExactColorUnavailable(f"Nikon density frame ownership is invalid: {error}") from error + return roll_native_builder.build_native_builder_receipt(evidence) + + +def _canonical_component_payload(component: object, *, label: str) -> bytes: + if type(component) is dict: + payload = component + else: + producer = getattr(component, "to_dict", None) + if not callable(producer): + producer = getattr(component, "to_payload", None) + if callable(producer): + payload = producer() + else: + raise roll_exact_color.ExactColorUnavailable(f"{label} has no canonical receipt payload") + if type(payload) is not dict: + raise roll_exact_color.ExactColorUnavailable(f"{label} payload is malformed") + try: + return json.dumps( + payload, + sort_keys=True, + separators=(",", ":"), + ensure_ascii=False, + allow_nan=False, + ).encode("utf-8") + except (TypeError, ValueError, RecursionError) as error: + raise roll_exact_color.ExactColorUnavailable(f"{label} payload is not canonical JSON: {error}") from error + + +def _retain_builder_evidence( + base_path: str, + receipt: roll_exact_color.BuilderReceipt, +) -> dict[str, object]: + if isinstance(receipt, roll_exact_color.ValidatedBuilderReceipt): + return _retain_stage3_replay_evidence(base_path, receipt) + if isinstance(receipt, roll_exact_color.NativeValidatedBuilderReceipt): + return _retain_native_builder_evidence(base_path, receipt) + raise roll_exact_color.ExactColorIntegrityError("builder receipt has an invalid type") + + +def _retain_stage3_replay_evidence( + base_path: str, + receipt: roll_exact_color.ValidatedBuilderReceipt, +) -> dict[str, object]: + """Atomically retain the raw replay inputs consumed by exact Tier 3.""" + + roll_exact_color.builder_receipt_payload(receipt) + evidence_root = os.path.join(os.path.dirname(base_path) or ".", ".negpy-stage3-replay") + evidence_directory = os.path.join(evidence_root, receipt.sha256) + try: + if any(os.path.lexists(path) and os.path.islink(path) for path in (evidence_root, evidence_directory)): + raise OSError("evidence path is a symlink") + _prepare_evidence_directory(evidence_directory) + report_path = _atomic_write_bytes( + os.path.join(evidence_directory, "stage3-validation.json"), + receipt.stage3_receipt, + ) + except OSError as error: + raise roll_exact_color.ExactColorUnavailable(f"cannot retain Stage-3 replay evidence: {error}") from error + report_row = { + "bytes": len(receipt.stage3_receipt), + "path": report_path, + "sha256": receipt.stage3_receipt_sha256, + } + lut_rows = [] + for channel, blob, digest in zip( + ("r", "g", "b"), + receipt.pre_f_luts, + receipt.pre_f_lut_sha256, + strict=True, + ): + try: + path = _atomic_write_bytes(os.path.join(evidence_directory, f"builder-preF-{channel}.bin"), blob) + except OSError as error: + raise roll_exact_color.ExactColorUnavailable(f"cannot retain Stage-3 replay evidence: {error}") from error + lut_rows.append( + { + "bytes": len(blob), + "channel": channel, + "path": path, + "sha256": digest, + } + ) + return { + "native_per_acquisition_builder": False, + "pre_f_luts": lut_rows, + "scope": roll_exact_color.STAGE3_REPLAY_SCOPE, + "stage3_report": report_row, + } + + +def _retain_native_builder_evidence( + base_path: str, + receipt: roll_exact_color.NativeValidatedBuilderReceipt, +) -> dict[str, object]: + """Atomically retain the native evidence snapshot and derived pre-F LUTs.""" + + roll_exact_color.builder_receipt_payload(receipt) + evidence_root = os.path.join(os.path.dirname(base_path) or ".", ".negpy-native-builder") + evidence_directory = os.path.join(evidence_root, receipt.sha256) + try: + if any(os.path.lexists(path) and os.path.islink(path) for path in (evidence_root, evidence_directory)): + raise OSError("evidence path is a symlink") + _prepare_evidence_directory(evidence_directory) + builder_receipt_path = _atomic_write_bytes( + os.path.join(evidence_directory, "native-builder-receipt.json"), + receipt.payload, + ) + evidence_path = _atomic_write_bytes( + os.path.join(evidence_directory, "native-builder-evidence.json"), + receipt.evidence_payload, + ) + ownership_path = _atomic_write_bytes( + os.path.join(evidence_directory, "nikon-density-frame-ownership.json"), + receipt.frame_ownership_receipt, + ) + density_path = _atomic_write_bytes( + os.path.join(evidence_directory, "nikon-density-evidence.json"), + receipt.density_evidence_receipt, + ) + analyzer_path = _atomic_write_bytes( + os.path.join(evidence_directory, "analyzer-rgb-u16le.bin"), + receipt.analyzer_rgb, + ) + except OSError as error: + raise roll_exact_color.ExactColorUnavailable(f"cannot retain native builder evidence: {error}") from error + lut_rows = [] + for channel, blob, digest in zip(("r", "g", "b"), receipt.pre_f_luts, receipt.pre_f_lut_sha256, strict=True): + try: + path = _atomic_write_bytes(os.path.join(evidence_directory, f"builder-preF-{channel}.bin"), blob) + except OSError as error: + raise roll_exact_color.ExactColorUnavailable(f"cannot retain native builder evidence: {error}") from error + lut_rows.append({"bytes": len(blob), "channel": channel, "path": path, "sha256": digest}) + return { + "builder_receipt": { + "bytes": len(receipt.payload), + "path": builder_receipt_path, + "sha256": receipt.sha256, + }, + "analyzer_rgb": { + "bytes": len(receipt.analyzer_rgb), + "path": analyzer_path, + "sha256": receipt.analyzer_rgb_sha256, + "shape": list(receipt.analyzer_shape), + }, + "evidence_receipt": { + "bytes": len(receipt.evidence_payload), + "path": evidence_path, + "sha256": receipt.evidence_sha256, + }, + "frame_ownership_receipt": { + "bytes": len(receipt.frame_ownership_receipt), + "path": ownership_path, + "sha256": receipt.frame_ownership_receipt_sha256, + }, + "density_evidence_receipt": { + "bytes": len(receipt.density_evidence_receipt), + "path": density_path, + "sha256": receipt.density_evidence_receipt_sha256, + }, + "native_per_acquisition_builder": True, + "pre_f_luts": lut_rows, + "scope": roll_exact_color.NATIVE_BUILDER_SCOPE, + } diff --git a/pyproject.toml b/pyproject.toml index 062f0040..76ce515f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -57,8 +57,10 @@ camera = [ ] coolscan-roll = [ # Direct-USB roll scanning on a Nikon Coolscan LS-5000 with an SA-21/SA-30 feeder. + # Pin the reviewed color-negative density/DICE/direct-USB implementation + # until its first tagged release. # No SANE dependency for this workflow; see docs/COOLSCANPY_ROLL_SCANNING.md. - "coolscanpy>=0.1.2", + "coolscanpy @ git+https://github.com/rohanpandula/coolscanpy.git@733151d25b8e962e3d5cb6ac1a6989315dc4e1ed", ] fauxice = [ "portable-digital-ice @ https://github.com/rohanpandula/digital-fauxice/releases/download/v0.3.0/portable_digital_ice-0.3.0-py3-none-any.whl", @@ -77,8 +79,15 @@ Issues = "https://github.com/marcinz606/NegPy/issues" requires = ["setuptools~=80.10.2"] build-backend = "setuptools.build_meta" -[tool.setuptools] -packages = ["negpy"] +[tool.setuptools.packages.find] +include = ["negpy*"] + +[tool.setuptools.package-data] +negpy = [ + "assets/portable_builder/*.bin", + "assets/portable_cms/*.bin", + "assets/portable_cms/*.json", +] [tool.setuptools.dynamic] version = { file = "VERSION" } @@ -108,6 +117,8 @@ exclude = [ "dist", "desktop/bin", "build", + # Vendored byte-identical verified oracle; linting would change its pinned SHA. + "negpy/services/roll/portable_oracle_evaluator.py", ] [tool.coverage.run] diff --git a/tests/repair/test_fauxice_hybrid_runner.py b/tests/repair/test_fauxice_hybrid_runner.py index 915f62eb..edadd154 100644 --- a/tests/repair/test_fauxice_hybrid_runner.py +++ b/tests/repair/test_fauxice_hybrid_runner.py @@ -9,27 +9,55 @@ """ import hashlib +import io import json +import os import subprocess +import sys +import threading +import time +import types from pathlib import Path import numpy as np import pytest +from PIL import Image +from negpy.services.repair import fauxice_hybrid_runner as hybrid_runner from negpy.services.repair.fauxice_hybrid_runner import ( + HybridRunCancelled, HybridRunError, HybridRuntimeConfig, run_hybrid_repair, ) +EXPECTED_CORE_SOURCE_SHA256 = "c" * 64 +EXPECTED_HYBRID_SOURCE_SHA256 = "d" * 64 + def _runtime_config(tmp_path: Path) -> HybridRuntimeConfig: + hybrid_python = tmp_path / "hybrid-venv" / "bin" / "python" + executable = tmp_path / "hybrid-venv" / "bin" / "fauxce-hybrid" + iopaint_python = tmp_path / "iopaint-venv" / "bin" / "python" + iopaint_executable = tmp_path / "iopaint-venv" / "bin" / "iopaint" + model_dir = tmp_path / "lama-models" + model_weights = model_dir / "torch" / "hub" / "checkpoints" / "big-lama.pt" + for path in (hybrid_python, executable, iopaint_python, iopaint_executable): + path.parent.mkdir(parents=True, exist_ok=True) + path.write_bytes(b"#!/bin/sh\nexit 0\n") + path.chmod(0o700) + model_weights.parent.mkdir(parents=True, exist_ok=True) + model_weights.write_bytes(b"big-lama-weights") return HybridRuntimeConfig( - iopaint_python=tmp_path / "iopaint-venv" / "bin" / "python", - iopaint_executable=tmp_path / "iopaint-venv" / "bin" / "iopaint", + hybrid_python=hybrid_python, + executable=executable, + core_source_manifest_sha256=EXPECTED_CORE_SOURCE_SHA256, + hybrid_source_manifest_sha256=EXPECTED_HYBRID_SOURCE_SHA256, + iopaint_python=iopaint_python, + iopaint_executable=iopaint_executable, iopaint_source_manifest_sha256=hashlib.sha256(b"iopaint-source").hexdigest(), - model_dir=tmp_path / "lama-models", - model_weights=tmp_path / "lama-models" / "torch" / "hub" / "checkpoints" / "big-lama.pt", + model_dir=model_dir, + model_weights=model_weights, model_weights_sha256=hashlib.sha256(b"big-lama-weights").hexdigest(), ) @@ -39,28 +67,147 @@ def _rgbi(height: int, width: int, base: int) -> np.ndarray: def _receipt(*, requested: str = "cpu", used: str = "cpu", fraction: float = 0.01) -> dict: - return { + return {"fraction": fraction, "requested": requested, "used": used} + + +def _arg(argv: list[str], flag: str) -> str: + return argv[argv.index(flag) + 1] + + +def _raw_sha256(array: np.ndarray) -> str: + canonical = np.array(array, dtype=" None: + out_dir.mkdir(parents=True) + np.save(out_dir / "output-hybrid.rgb16.npy", hybrid_rgb16, allow_pickle=False) + main = np.load(_arg(argv, "--main"), allow_pickle=False) + prepass = np.load(_arg(argv, "--prepass"), allow_pickle=False) + manifest_bytes = Path(_arg(argv, "--acquisition-manifest")).read_bytes() + requested_fraction = float(receipt_options["fraction"]) + synthesis_pixels = min( + int(round(requested_fraction * main.shape[0] * main.shape[1])), + main.shape[0] * main.shape[1], + ) + mask = np.zeros(main.shape[:2], dtype=np.uint8) + mask.reshape(-1)[:synthesis_pixels] = 255 + mask_path = out_dir / "synth-mask.png" + Image.fromarray(mask, mode="L").save(mask_path, format="PNG") + mask_bytes = mask_path.read_bytes() + fraction = synthesis_pixels / mask.size + output_hash = _raw_sha256(hybrid_rgb16) + receipt = { + "artifacts": [ + { + "dtype": " None: - out_dir.mkdir(parents=True) - np.save(out_dir / "output-hybrid.rgb16.npy", hybrid_rgb16, allow_pickle=False) - (out_dir / "synth-mask.png").write_bytes(b"fake-png-bytes") - (out_dir / "hybrid-receipt.json").write_text(json.dumps(receipt), encoding="utf-8") +def _test_receipt_verifier(path: Path, _runtime: HybridRuntimeConfig): + document = json.loads(path.read_text(encoding="utf-8")) + out_dir = path.parent + hybrid = np.load(out_dir / "output-hybrid.rgb16.npy", allow_pickle=False) + with Image.open(out_dir / "synth-mask.png") as image: + mask = np.ascontiguousarray(np.asarray(image.convert("L")) != 0) + return types.SimpleNamespace( + document=document, + receipt_sha256=hashlib.sha256(path.read_bytes()).hexdigest(), + hybrid_output_rgb16=np.ascontiguousarray(hybrid), + synthesis_mask=mask, + model_weights_rehashed=True, + ) + + +def _attest_only(path: Path, _runtime: HybridRuntimeConfig): + return types.SimpleNamespace( + receipt_sha256=hashlib.sha256(path.read_bytes()).hexdigest(), + model_weights_rehashed=True, + ) class _RecordingRunner: @@ -77,12 +224,177 @@ def __call__(self, argv, **kwargs) -> subprocess.CompletedProcess: out_index = argv.index("--out") out_dir = Path(argv[out_index + 1]) if self._returncode == 0: - _write_success_outputs(out_dir, hybrid_rgb16=self._hybrid_rgb16, receipt=self._receipt) + _write_success_outputs( + out_dir, + hybrid_rgb16=self._hybrid_rgb16, + receipt_options=self._receipt, + argv=list(argv), + ) return subprocess.CompletedProcess(argv, 0, stdout="ok", stderr="") return subprocess.CompletedProcess(argv, self._returncode, stdout="", stderr="synthetic failure") class TestRunHybridRepairSuccess: + def test_reports_hybrid_and_verification_progress(self, tmp_path: Path) -> None: + runner = _RecordingRunner( + hybrid_rgb16=np.zeros((2, 2, 3), dtype=np.uint16), + receipt=_receipt(), + ) + seen: list[float] = [] + + run_hybrid_repair( + _rgbi(2, 2, 1000), + _rgbi(2, 2, 500), + same_frame_id="progress-bound", + backend="cpu", + runtime=_runtime_config(tmp_path), + scratch_dir=tmp_path, + runner=runner, + receipt_verifier=_test_receipt_verifier, + progress=seen.append, + ) + + assert seen == [0.0, 0.8, 0.9, 1.0] + + @pytest.mark.parametrize("boundary", [0.9, 1.0]) + def test_cancellation_at_late_progress_boundary_never_returns_a_result( + self, + tmp_path: Path, + boundary: float, + ) -> None: + runner = _RecordingRunner( + hybrid_rgb16=np.zeros((2, 2, 3), dtype=np.uint16), + receipt=_receipt(), + ) + cancel = threading.Event() + + def progress(value: float) -> None: + if value == boundary: + cancel.set() + + with pytest.raises(HybridRunCancelled, match="cancelled"): + run_hybrid_repair( + _rgbi(2, 2, 1000), + _rgbi(2, 2, 500), + same_frame_id=f"late-cancel-{boundary}", + backend="cpu", + runtime=_runtime_config(tmp_path), + scratch_dir=tmp_path, + runner=runner, + receipt_verifier=_test_receipt_verifier, + progress=progress, + cancel=cancel, + ) + + def test_subprocess_environment_ignores_python_injection_variables( + self, + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, + ) -> None: + monkeypatch.setenv("PYTHONHOME", "/attacker/python-home") + monkeypatch.setenv("PYTHONPATH", "/attacker/imports") + monkeypatch.setenv("DYLD_INSERT_LIBRARIES", "/attacker/inject.dylib") + monkeypatch.setenv("LD_PRELOAD", "/attacker/inject.so") + seen_environment: dict[str, str] = {} + hybrid_rgb16 = np.zeros((2, 2, 3), dtype=np.uint16) + + def runner(argv, **kwargs): + seen_environment.update(kwargs["env"]) + out_dir = Path(_arg(list(argv), "--out")) + _write_success_outputs( + out_dir, + hybrid_rgb16=hybrid_rgb16, + receipt_options=_receipt(), + argv=list(argv), + ) + return subprocess.CompletedProcess(argv, 0, stdout="", stderr="") + + run_hybrid_repair( + _rgbi(2, 2, 1000), + _rgbi(2, 2, 500), + same_frame_id="sanitized-environment", + backend="cpu", + runtime=_runtime_config(tmp_path), + scratch_dir=tmp_path, + runner=runner, + receipt_verifier=_test_receipt_verifier, + ) + + assert "PYTHONHOME" not in seen_environment + assert "PYTHONPATH" not in seen_environment + assert "DYLD_INSERT_LIBRARIES" not in seen_environment + assert "LD_PRELOAD" not in seen_environment + assert seen_environment["PYTHONHASHSEED"] == "0" + assert seen_environment["PYTHONNOUSERSITE"] == "1" + assert seen_environment["LC_ALL"] == "C" + + def test_resolves_scratch_to_link_free_real_path_before_cli(self, tmp_path: Path) -> None: + real_scratch = tmp_path / "real-scratch" + real_scratch.mkdir() + linked_scratch = tmp_path / "linked-scratch" + linked_scratch.symlink_to(real_scratch, target_is_directory=True) + hybrid_rgb16 = np.zeros((2, 2, 3), dtype=np.uint16) + + def runner(argv, **kwargs): + out_dir = Path(_arg(list(argv), "--out")) + assert out_dir.parent.parent == real_scratch.resolve(strict=True) + assert out_dir.parent.name.startswith("negpy-hybrid-run-") + _write_success_outputs( + out_dir, + hybrid_rgb16=hybrid_rgb16, + receipt_options=_receipt(), + argv=list(argv), + ) + return subprocess.CompletedProcess(argv, 0, stdout="", stderr="") + + run_hybrid_repair( + _rgbi(2, 2, 1000), + _rgbi(2, 2, 500), + same_frame_id="link-free-scratch", + backend="cpu", + runtime=_runtime_config(tmp_path / "runtime"), + scratch_dir=linked_scratch, + runner=runner, + receipt_verifier=_test_receipt_verifier, + ) + + def test_default_receipt_verification_runs_in_external_hybrid_python(self, tmp_path: Path) -> None: + hybrid_rgb16 = np.full((3, 4, 3), 4242, dtype=np.uint16) + runner = _RecordingRunner(hybrid_rgb16=hybrid_rgb16, receipt=_receipt()) + verification_calls: list[list[str]] = [] + + def verification_runner(argv, **kwargs): + verification_calls.append(list(argv)) + receipt_path = Path(argv[-3]) + attestation = { + "model_weights_rehashed": True, + "receipt_sha256": hashlib.sha256(receipt_path.read_bytes()).hexdigest(), + "schema": "negpy.external-fauxce-receipt-verification-v1", + } + return subprocess.CompletedProcess( + argv, + 0, + stdout=json.dumps(attestation, sort_keys=True, separators=(",", ":")), + stderr="", + ) + + runtime = _runtime_config(tmp_path) + result = run_hybrid_repair( + _rgbi(3, 4, 1000), + _rgbi(2, 2, 500), + same_frame_id="frame-external-verifier", + backend="cpu", + runtime=runtime, + scratch_dir=tmp_path, + runner=runner, + verification_runner=verification_runner, + ) + + assert result.receipt_sha256 + assert len(verification_calls) == 1 + assert verification_calls[0][0] == str(runtime.hybrid_python) + assert "fauxce_hybrid.receipts" in verification_calls[0][3] + def test_reads_hybrid_output_and_mask(self, tmp_path: Path) -> None: hybrid_rgb16 = np.full((4, 4, 3), 4242, dtype=np.uint16) runner = _RecordingRunner(hybrid_rgb16=hybrid_rgb16, receipt=_receipt()) @@ -95,58 +407,65 @@ def test_reads_hybrid_output_and_mask(self, tmp_path: Path) -> None: runtime=_runtime_config(tmp_path), scratch_dir=tmp_path, runner=runner, + receipt_verifier=_test_receipt_verifier, ) np.testing.assert_array_equal(result.hybrid_rgb16, hybrid_rgb16) - assert result.synth_mask_png == b"fake-png-bytes" - assert result.synth_mask_sha256 == hashlib.sha256(b"fake-png-bytes").hexdigest() + assert result.synth_mask_png.startswith(b"\x89PNG\r\n\x1a\n") + assert result.synth_mask_sha256 == hashlib.sha256(result.synth_mask_png).hexdigest() + assert result.receipt_sha256 == hashlib.sha256(result.receipt).hexdigest() + assert result.provenance_class == "caller_asserted_bare_npy" def test_receipt_fields_are_extracted(self, tmp_path: Path) -> None: - hybrid_rgb16 = np.zeros((2, 2, 3), dtype=np.uint16) - receipt = _receipt(requested="auto", used="cpu-fast", fraction=0.0136) + hybrid_rgb16 = np.zeros((10, 10, 3), dtype=np.uint16) + receipt = _receipt(requested="auto", used="cpu-fast", fraction=0.01) runner = _RecordingRunner(hybrid_rgb16=hybrid_rgb16, receipt=receipt) result = run_hybrid_repair( - _rgbi(2, 2, 1000), - _rgbi(2, 2, 500), + _rgbi(10, 10, 1000), + _rgbi(5, 5, 500), same_frame_id="frame-002", backend="auto", runtime=_runtime_config(tmp_path), scratch_dir=tmp_path, runner=runner, + receipt_verifier=_test_receipt_verifier, ) assert result.engine_version == "0.3.0" assert result.backend_requested == "auto" assert result.backend_used == "cpu-fast" - assert result.synthesis_fraction == 0.0136 + assert result.synthesis_fraction == 0.01 assert result.routing_counts == { - "final_regions": 3, - "synthesis_pixels": 120, - "frame_pixels": 10000, - "at_floor_pixels": 500, + "final_regions": 1, + "synthesis_pixels": 1, + "frame_pixels": 100, + "at_floor_pixels": 1, } - def test_routing_counts_none_when_receipt_omits_expected_keys(self, tmp_path: Path) -> None: + def test_rejects_receipt_that_omits_routing_counts(self, tmp_path: Path) -> None: hybrid_rgb16 = np.zeros((2, 2, 3), dtype=np.uint16) - receipt = _receipt() - del receipt["routing"]["counts"]["frame_pixels"] - runner = _RecordingRunner(hybrid_rgb16=hybrid_rgb16, receipt=receipt) + base_runner = _RecordingRunner(hybrid_rgb16=hybrid_rgb16, receipt=_receipt()) - result = run_hybrid_repair( - _rgbi(2, 2, 1000), - _rgbi(2, 2, 500), - same_frame_id="frame-003", - backend="cpu", - runtime=_runtime_config(tmp_path), - scratch_dir=tmp_path, - runner=runner, - ) + def runner(argv, **kwargs): + completed = base_runner(argv, **kwargs) + receipt_path = Path(argv[argv.index("--out") + 1]) / "hybrid-receipt.json" + document = json.loads(receipt_path.read_text(encoding="utf-8")) + del document["routing"]["counts"]["frame_pixels"] + receipt_path.write_bytes(json.dumps(document, sort_keys=True, separators=(",", ":")).encode() + b"\n") + return completed - assert result.routing_counts is None - # A partial/malformed routing block must not silently degrade the - # fields this module is confident about. - assert result.synthesis_fraction == 0.01 + with pytest.raises(HybridRunError, match="routing counts"): + run_hybrid_repair( + _rgbi(2, 2, 1000), + _rgbi(2, 2, 500), + same_frame_id="frame-003", + backend="cpu", + runtime=_runtime_config(tmp_path), + scratch_dir=tmp_path, + runner=runner, + receipt_verifier=_test_receipt_verifier, + ) def test_argv_carries_the_documented_cli_flags(self, tmp_path: Path) -> None: hybrid_rgb16 = np.zeros((2, 2, 3), dtype=np.uint16) @@ -161,11 +480,12 @@ def test_argv_carries_the_documented_cli_flags(self, tmp_path: Path) -> None: runtime=runtime, scratch_dir=tmp_path, runner=runner, + receipt_verifier=_test_receipt_verifier, ) assert len(runner.calls) == 1 argv = runner.calls[0] - assert argv[0] == "fauxce-hybrid" + assert argv[0] == str(runtime.executable) for flag, value in ( ("--same-frame-id", "frame-004"), ("--backend", "cpu"), @@ -194,7 +514,12 @@ def test_scratch_dir_out_subdirectory_does_not_preexist(self, tmp_path: Path) -> def runner(argv, **kwargs) -> subprocess.CompletedProcess: out_dir = Path(argv[argv.index("--out") + 1]) assert not out_dir.exists() - _write_success_outputs(out_dir, hybrid_rgb16=hybrid_rgb16, receipt=_receipt()) + _write_success_outputs( + out_dir, + hybrid_rgb16=hybrid_rgb16, + receipt_options=_receipt(), + argv=list(argv), + ) return subprocess.CompletedProcess(argv, 0, stdout="", stderr="") scratch = tmp_path / "scratch" @@ -207,10 +532,445 @@ def runner(argv, **kwargs) -> subprocess.CompletedProcess: runtime=_runtime_config(tmp_path), scratch_dir=scratch, runner=runner, + receipt_verifier=_test_receipt_verifier, ) class TestRunHybridRepairFailure: + @pytest.mark.skipif(not hasattr(os, "mkfifo"), reason="POSIX FIFO test") + def test_model_hash_regular_to_fifo_swap_never_blocks( + self, + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, + ) -> None: + weights = tmp_path / "weights.bin" + weights.write_bytes(b"weights") + real_open = hybrid_runner.os.open + swapped = False + + def swap_before_open(path, flags, *args): + nonlocal swapped + if not swapped and Path(path) == weights: + swapped = True + weights.unlink() + os.mkfifo(weights) + return real_open(path, flags, *args) + + monkeypatch.setattr(hybrid_runner.os, "open", swap_before_open) + started = time.monotonic() + with pytest.raises(ValueError, match="changed while it was opened"): + hybrid_runner._stable_regular_sha256(weights, label="model weights") + assert time.monotonic() - started < 1.0 + + @pytest.mark.skipif(not hasattr(os, "mkfifo"), reason="POSIX FIFO test") + def test_result_regular_to_fifo_swap_never_blocks( + self, + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, + ) -> None: + artifact = tmp_path / "artifact.bin" + artifact.write_bytes(b"artifact") + real_open = hybrid_runner.os.open + swapped = False + + def swap_before_open(path, flags, *args): + nonlocal swapped + if not swapped and Path(path) == artifact: + swapped = True + artifact.unlink() + os.mkfifo(artifact) + return real_open(path, flags, *args) + + monkeypatch.setattr(hybrid_runner.os, "open", swap_before_open) + started = time.monotonic() + with pytest.raises(HybridRunError, match="changed while being opened"): + hybrid_runner._stable_regular_bytes( + artifact, + maximum_bytes=1024, + label="hybrid artifact", + ) + assert time.monotonic() - started < 1.0 + + def test_npy_header_geometry_is_rejected_before_array_allocation( + self, + monkeypatch: pytest.MonkeyPatch, + ) -> None: + stream = io.BytesIO() + np.lib.format.write_array_header_1_0( + stream, + { + "descr": " None: + class HugeHeader: + format = "PNG" + size = (1_000_000, 1_000_000) + + def __enter__(self): + return self + + def __exit__(self, *_args): + return False + + def convert(self, _mode): + pytest.fail("oversized PNG pixels must not be decompressed") + + monkeypatch.setattr( + hybrid_runner.Image, + "open", + lambda *_args, **_kwargs: HugeHeader(), + ) + + with pytest.raises(HybridRunError, match="geometry"): + hybrid_runner._decode_mask_png( + b"synthetic-png-header", + expected_shape=(2, 2), + ) + + def test_external_process_logs_are_bounded(self, tmp_path: Path) -> None: + runtime = _runtime_config(tmp_path) + runtime.executable.write_text( + "#!/bin/sh\nhead -c 17825792 /dev/zero\n", + encoding="utf-8", + ) + runtime.executable.chmod(0o700) + + with pytest.raises(HybridRunError, match="output exceeded"): + run_hybrid_repair( + _rgbi(2, 2, 1000), + _rgbi(2, 2, 500), + same_frame_id="bounded-log", + backend="cpu", + runtime=runtime, + scratch_dir=tmp_path, + timeout_seconds=10, + ) + + def test_cancellation_terminates_blocking_external_process_group(self, tmp_path: Path) -> None: + runtime = _runtime_config(tmp_path) + runtime.executable.write_text("#!/bin/sh\nsleep 30\n", encoding="utf-8") + runtime.executable.chmod(0o700) + cancel = threading.Event() + timer = threading.Timer(0.2, cancel.set) + started = time.monotonic() + timer.start() + try: + with pytest.raises(HybridRunCancelled, match="cancelled"): + run_hybrid_repair( + _rgbi(2, 2, 1000), + _rgbi(2, 2, 500), + same_frame_id="cancel-blocking-process", + backend="cpu", + runtime=runtime, + scratch_dir=tmp_path, + cancel=cancel, + ) + finally: + timer.cancel() + + assert time.monotonic() - started < 5.0 + assert not (tmp_path / "out" / "hybrid-receipt.json").exists() + + def test_cancellation_kills_child_that_ignores_term_after_leader_exits( + self, + tmp_path: Path, + ) -> None: + runtime = _runtime_config(tmp_path) + child_pid_path = tmp_path / "child.pid" + child_program = tmp_path / "ignore-term-child.py" + child_program.write_text( + "\n".join( + ( + "import os", + "from pathlib import Path", + "import signal", + "import sys", + "import time", + "signal.signal(signal.SIGTERM, signal.SIG_IGN)", + "Path(sys.argv[1]).write_text(str(os.getpid()), encoding='ascii')", + "while True:", + " time.sleep(0.1)", + ) + ) + + "\n", + encoding="utf-8", + ) + runtime.executable.write_text( + "\n".join( + ( + "#!/bin/sh", + f"{sys.executable!s} {child_program!s} {child_pid_path!s} &", + "trap 'exit 0' TERM", + "while :; do sleep 1; done", + ) + ) + + "\n", + encoding="utf-8", + ) + runtime.executable.chmod(0o700) + cancel = threading.Event() + + def cancel_after_child_starts() -> None: + deadline = time.monotonic() + 5.0 + while not child_pid_path.exists() and time.monotonic() < deadline: + time.sleep(0.01) + cancel.set() + + canceller = threading.Thread(target=cancel_after_child_starts, daemon=True) + canceller.start() + with pytest.raises(HybridRunCancelled, match="cancelled"): + run_hybrid_repair( + _rgbi(2, 2, 1000), + _rgbi(2, 2, 500), + same_frame_id="cancel-orphan-process", + backend="cpu", + runtime=runtime, + scratch_dir=tmp_path, + cancel=cancel, + ) + canceller.join(timeout=1.0) + assert child_pid_path.exists() + child_pid = int(child_pid_path.read_text(encoding="ascii")) + deadline = time.monotonic() + 3.0 + while time.monotonic() < deadline: + try: + os.kill(child_pid, 0) + except ProcessLookupError: + break + time.sleep(0.05) + else: + pytest.fail(f"hybrid child process {child_pid} survived cancellation") + + @pytest.mark.parametrize( + ("section", "field", "match"), + [ + ("core", "source_manifest_sha256", "core source manifest"), + ( + "generation", + "hybrid_source_manifest_sha256", + "hybrid source manifest", + ), + ], + ) + def test_rejects_independent_source_manifest_mismatch( + self, + tmp_path: Path, + section: str, + field: str, + match: str, + ) -> None: + base = _RecordingRunner( + hybrid_rgb16=np.zeros((2, 2, 3), dtype=np.uint16), + receipt=_receipt(), + ) + + def runner(argv, **kwargs): + completed = base(argv, **kwargs) + receipt_path = Path(_arg(list(argv), "--out")) / "hybrid-receipt.json" + document = json.loads(receipt_path.read_bytes()) + document[section][field] = "0" * 64 + receipt_path.write_bytes(json.dumps(document, sort_keys=True, separators=(",", ":")).encode() + b"\n") + return completed + + with pytest.raises(HybridRunError, match=match): + run_hybrid_repair( + _rgbi(2, 2, 1000), + _rgbi(2, 2, 500), + same_frame_id="source-manifest-mismatch", + backend="cpu", + runtime=_runtime_config(tmp_path), + scratch_dir=tmp_path, + runner=runner, + receipt_verifier=_attest_only, + ) + + def test_rejects_malformed_receipt_json(self, tmp_path: Path) -> None: + base = _RecordingRunner( + hybrid_rgb16=np.zeros((2, 2, 3), dtype=np.uint16), + receipt=_receipt(), + ) + + def runner(argv, **kwargs): + completed = base(argv, **kwargs) + receipt_path = Path(_arg(list(argv), "--out")) / "hybrid-receipt.json" + receipt_path.write_bytes(b"{") + return completed + + with pytest.raises(HybridRunError, match="receipt JSON is invalid"): + run_hybrid_repair( + _rgbi(2, 2, 1000), + _rgbi(2, 2, 500), + same_frame_id="malformed-receipt", + backend="cpu", + runtime=_runtime_config(tmp_path), + scratch_dir=tmp_path, + runner=runner, + receipt_verifier=_attest_only, + ) + + def test_rejects_receipt_input_hash_swap(self, tmp_path: Path) -> None: + base = _RecordingRunner( + hybrid_rgb16=np.zeros((2, 2, 3), dtype=np.uint16), + receipt=_receipt(), + ) + + def runner(argv, **kwargs): + completed = base(argv, **kwargs) + receipt_path = Path(_arg(list(argv), "--out")) / "hybrid-receipt.json" + document = json.loads(receipt_path.read_bytes()) + document["inputs"]["main"]["raw_sha256"] = "0" * 64 + receipt_path.write_bytes(json.dumps(document, sort_keys=True, separators=(",", ":")).encode() + b"\n") + return completed + + with pytest.raises(HybridRunError, match="main SHA-256 changed"): + run_hybrid_repair( + _rgbi(2, 2, 1000), + _rgbi(2, 2, 500), + same_frame_id="input-hash-swap", + backend="cpu", + runtime=_runtime_config(tmp_path), + scratch_dir=tmp_path, + runner=runner, + receipt_verifier=_attest_only, + ) + + def test_rejects_invalid_disclosure_png_even_when_file_hash_matches_receipt(self, tmp_path: Path) -> None: + base = _RecordingRunner( + hybrid_rgb16=np.zeros((2, 2, 3), dtype=np.uint16), + receipt=_receipt(), + ) + + def runner(argv, **kwargs): + completed = base(argv, **kwargs) + out_dir = Path(_arg(list(argv), "--out")) + bad_png = b"not-a-png" + (out_dir / "synth-mask.png").write_bytes(bad_png) + receipt_path = out_dir / "hybrid-receipt.json" + document = json.loads(receipt_path.read_bytes()) + mask_artifact = next(row for row in document["artifacts"] if row["role"] == "synthesis_mask_png") + mask_artifact["file_sha256"] = hashlib.sha256(bad_png).hexdigest() + receipt_path.write_bytes(json.dumps(document, sort_keys=True, separators=(",", ":")).encode() + b"\n") + return completed + + with pytest.raises(HybridRunError, match="mask PNG is invalid"): + run_hybrid_repair( + _rgbi(2, 2, 1000), + _rgbi(2, 2, 500), + same_frame_id="bad-mask-png", + backend="cpu", + runtime=_runtime_config(tmp_path), + scratch_dir=tmp_path, + runner=runner, + receipt_verifier=_attest_only, + ) + + def test_rejects_symlinked_result_artifact(self, tmp_path: Path) -> None: + base = _RecordingRunner( + hybrid_rgb16=np.zeros((2, 2, 3), dtype=np.uint16), + receipt=_receipt(), + ) + + def runner(argv, **kwargs): + completed = base(argv, **kwargs) + out_dir = Path(_arg(list(argv), "--out")) + mask_path = out_dir / "synth-mask.png" + target = out_dir / "attacker-mask.png" + target.write_bytes(mask_path.read_bytes()) + mask_path.unlink() + mask_path.symlink_to(target) + return completed + + with pytest.raises(HybridRunError, match="non-symlink"): + run_hybrid_repair( + _rgbi(2, 2, 1000), + _rgbi(2, 2, 500), + same_frame_id="symlink-mask", + backend="cpu", + runtime=_runtime_config(tmp_path), + scratch_dir=tmp_path, + runner=runner, + receipt_verifier=_attest_only, + ) + + def test_rejects_external_verifier_receipt_hash_mismatch(self, tmp_path: Path) -> None: + runner = _RecordingRunner( + hybrid_rgb16=np.zeros((2, 2, 3), dtype=np.uint16), + receipt=_receipt(), + ) + + def verifier(_path: Path, _runtime: HybridRuntimeConfig): + return types.SimpleNamespace( + receipt_sha256="0" * 64, + model_weights_rehashed=True, + ) + + with pytest.raises(HybridRunError, match="changed after verification"): + run_hybrid_repair( + _rgbi(2, 2, 1000), + _rgbi(2, 2, 500), + same_frame_id="receipt-hash-mismatch", + backend="cpu", + runtime=_runtime_config(tmp_path), + scratch_dir=tmp_path, + runner=runner, + receipt_verifier=verifier, + ) + + def test_rejects_a_verifier_result_with_wrong_output_dtype_and_geometry(self, tmp_path: Path) -> None: + main = _rgbi(4, 5, 1000) + prepass = _rgbi(2, 3, 500) + + def runner(argv, **kwargs) -> subprocess.CompletedProcess: + out_dir = Path(argv[argv.index("--out") + 1]) + out_dir.mkdir(parents=True) + np.save( + out_dir / "output-hybrid.rgb16.npy", + np.zeros((1, 2), dtype=np.float32), + allow_pickle=False, + ) + (out_dir / "synth-mask.png").write_bytes(b"not-a-png") + (out_dir / "hybrid-receipt.json").write_text("{}\n", encoding="utf-8") + return subprocess.CompletedProcess(argv, 0, stdout="", stderr="") + + def verifier(_path: Path, _runtime: HybridRuntimeConfig): + return types.SimpleNamespace( + document={}, + receipt_sha256=hashlib.sha256(_path.read_bytes()).hexdigest(), + hybrid_output_rgb16=np.zeros((1, 2), dtype=np.float32), + synthesis_mask=np.zeros((1, 2), dtype=np.bool_), + model_weights_rehashed=True, + ) + + with pytest.raises(HybridRunError, match="HxWx3 uint16"): + run_hybrid_repair( + main, + prepass, + same_frame_id="frame-malformed-output", + backend="cpu", + runtime=_runtime_config(tmp_path), + scratch_dir=tmp_path, + runner=runner, + receipt_verifier=verifier, + ) + def test_nonzero_exit_raises_hybrid_run_error(self, tmp_path: Path) -> None: runner = _RecordingRunner( hybrid_rgb16=np.zeros((2, 2, 3), dtype=np.uint16), @@ -248,17 +1008,34 @@ def runner(argv, **kwargs) -> subprocess.CompletedProcess: runner=runner, ) - def test_launch_oserror_raises_hybrid_run_error(self, tmp_path: Path) -> None: - def runner(argv, **kwargs): - raise OSError("fauxce-hybrid executable not found") - with pytest.raises(HybridRunError, match="could not launch"): - run_hybrid_repair( - _rgbi(2, 2, 1000), - _rgbi(2, 2, 500), - same_frame_id="frame-008", - backend="cpu", - runtime=_runtime_config(tmp_path), - scratch_dir=tmp_path, - runner=runner, - ) +def test_runtime_config_rejects_relative_process_or_artifact_paths() -> None: + with pytest.raises(ValueError, match="absolute"): + HybridRuntimeConfig( + hybrid_python=Path("hybrid/bin/python"), + executable=Path("hybrid/bin/fauxce-hybrid"), + core_source_manifest_sha256=EXPECTED_CORE_SOURCE_SHA256, + hybrid_source_manifest_sha256=EXPECTED_HYBRID_SOURCE_SHA256, + iopaint_python=Path("iopaint/bin/python"), + iopaint_executable=Path("iopaint/bin/iopaint"), + iopaint_source_manifest_sha256="a" * 64, + model_dir=Path("models"), + model_weights=Path("models/big-lama.pt"), + model_weights_sha256="b" * 64, + ) + + +def test_launch_oserror_raises_hybrid_run_error(tmp_path: Path) -> None: + def runner(argv, **kwargs): + raise OSError("fauxce-hybrid executable not found") + + with pytest.raises(HybridRunError, match="could not launch"): + run_hybrid_repair( + _rgbi(2, 2, 1000), + _rgbi(2, 2, 500), + same_frame_id="frame-008", + backend="cpu", + runtime=_runtime_config(tmp_path), + scratch_dir=tmp_path, + runner=runner, + ) diff --git a/tests/repair/test_fauxice_ir_repair.py b/tests/repair/test_fauxice_ir_repair.py index ad3dffc2..e80d8e9f 100644 --- a/tests/repair/test_fauxice_ir_repair.py +++ b/tests/repair/test_fauxice_ir_repair.py @@ -18,6 +18,7 @@ from __future__ import annotations import importlib.machinery +import hashlib import json import subprocess import sys @@ -31,10 +32,14 @@ import pytest import tifffile -from negpy.services.repair.fauxice_hybrid_runner import HybridRuntimeConfig +from negpy.services.repair.fauxice_hybrid_runner import ( + HybridRunResult, + HybridRuntimeConfig, +) from negpy.services.repair.fauxice_ir_repair import ( ENGINE_IMPORT_NAME, HYBRID_IMPORT_NAME, + FauxiceRepairCancelled, FauxiceRepairConfig, RepairMode, RepairStatus, @@ -192,13 +197,29 @@ def _prepass(height: int, width: int) -> np.ndarray: def _hybrid_runtime(tmp_path: Path) -> HybridRuntimeConfig: + hybrid_python = tmp_path / "hybrid" / "bin" / "python" + executable = tmp_path / "hybrid" / "bin" / "fauxce-hybrid" + iopaint_python = tmp_path / "iopaint" / "bin" / "python" + iopaint_executable = tmp_path / "iopaint" / "bin" / "iopaint" + model_dir = tmp_path / "models" + model_weights = model_dir / "torch" / "hub" / "checkpoints" / "big-lama.pt" + for path in (hybrid_python, executable, iopaint_python, iopaint_executable): + path.parent.mkdir(parents=True, exist_ok=True) + path.write_bytes(b"#!/bin/sh\nexit 0\n") + path.chmod(0o700) + model_weights.parent.mkdir(parents=True, exist_ok=True) + model_weights.write_bytes(b"stub-model") return HybridRuntimeConfig( - iopaint_python=tmp_path / "iopaint" / "bin" / "python", - iopaint_executable=tmp_path / "iopaint" / "bin" / "iopaint", + hybrid_python=hybrid_python, + executable=executable, + core_source_manifest_sha256="c" * 64, + hybrid_source_manifest_sha256="d" * 64, + iopaint_python=iopaint_python, + iopaint_executable=iopaint_executable, iopaint_source_manifest_sha256="a" * 64, - model_dir=tmp_path / "models", - model_weights=tmp_path / "models" / "torch" / "hub" / "checkpoints" / "big-lama.pt", - model_weights_sha256="b" * 64, + model_dir=model_dir, + model_weights=model_weights, + model_weights_sha256=hashlib.sha256(b"stub-model").hexdigest(), ) @@ -232,14 +253,47 @@ def runner(argv, **kwargs) -> subprocess.CompletedProcess: return runner +def _hybrid_outcome(hybrid_rgb16: np.ndarray) -> HybridRunResult: + mask = np.zeros(hybrid_rgb16.shape[:2], dtype=np.bool_) + receipt = b'{"schema":"fauxce-hybrid-receipt-v2"}' + mask_bytes = b"mask-bytes" + return HybridRunResult( + hybrid_rgb16=np.ascontiguousarray(hybrid_rgb16), + synth_mask_png=mask_bytes, + synth_mask_sha256=hashlib.sha256(mask_bytes).hexdigest(), + synth_mask=mask, + receipt=receipt, + receipt_sha256=hashlib.sha256(receipt).hexdigest(), + acquisition_manifest_sha256="1" * 64, + main_rgbi_sha256="2" * 64, + prepass_rgbi_sha256="3" * 64, + output_rgb16_sha256=hashlib.sha256(hybrid_rgb16.astype(" None: - # Neither optional package is installed in this test environment. + def test_engine_unavailable_when_distribution_is_absent(self, monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setattr( + "negpy.services.repair.fauxice_ir_repair.importlib.util.find_spec", + lambda _name: None, + ) assert engine_available() is False def test_hybrid_unavailable_by_default(self) -> None: @@ -249,9 +303,8 @@ def test_engine_available_with_stub_installed(self, monkeypatch: pytest.MonkeyPa _install_stub_engine(monkeypatch) assert engine_available() is True - def test_hybrid_available_with_stub_installed(self, monkeypatch: pytest.MonkeyPatch) -> None: - _install_stub_hybrid(monkeypatch) - assert hybrid_available() is True + def test_hybrid_available_with_explicit_external_runtime(self, tmp_path: Path) -> None: + assert hybrid_available(_hybrid_runtime(tmp_path)) is True def test_engine_and_hybrid_availability_are_independent(self, monkeypatch: pytest.MonkeyPatch) -> None: _install_stub_engine(monkeypatch) @@ -271,7 +324,11 @@ def test_disabled_config_skips_without_checking_engine(self) -> None: assert result.status is RepairStatus.SKIPPED assert "disabled" in result.reason - def test_engine_unavailable_reports_unavailable_status(self) -> None: + def test_engine_unavailable_reports_unavailable_status(self, monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setattr( + "negpy.services.repair.fauxice_ir_repair.importlib.util.find_spec", + lambda _name: None, + ) config = FauxiceRepairConfig(enabled=True) result = repair_ir_dust(_rgb(4, 4, 1000), _ir(4, 4, 500), same_frame_id="f1", config=config, prepass_rgbi=_prepass(4, 4)) assert result.status is RepairStatus.UNAVAILABLE @@ -436,17 +493,16 @@ def test_cancel_before_start_skips_without_calling_engine(self, monkeypatch: pyt cancel = threading.Event() cancel.set() - result = repair_ir_dust( - _rgb(4, 4, 1000), - _ir(4, 4, 500), - same_frame_id="f1", - config=config, - prepass_rgbi=_prepass(4, 4), - cancel=cancel, - ) + with pytest.raises(FauxiceRepairCancelled, match="cancelled"): + repair_ir_dust( + _rgb(4, 4, 1000), + _ir(4, 4, 500), + same_frame_id="f1", + config=config, + prepass_rgbi=_prepass(4, 4), + cancel=cancel, + ) - assert result.status is RepairStatus.SKIPPED - assert "cancelled" in result.reason assert calls == [] # the engine was never invoked def test_cancel_during_exact_run_skips_with_clear_reason(self, monkeypatch: pytest.MonkeyPatch) -> None: @@ -456,17 +512,18 @@ def test_cancel_during_exact_run_skips_with_clear_reason(self, monkeypatch: pyte # engine polls it mid-run. cancel = _FlippingCancelEvent(set_after_calls=1) - result = repair_ir_dust( - _rgb(4, 4, 1000), - _ir(4, 4, 500), - same_frame_id="f1", - config=config, - prepass_rgbi=_prepass(4, 4), - cancel=cancel, - ) - - assert result.status is RepairStatus.SKIPPED - assert "cancelled before completion" in result.reason + with pytest.raises( + FauxiceRepairCancelled, + match="cancelled before completion", + ): + repair_ir_dust( + _rgb(4, 4, 1000), + _ir(4, 4, 500), + same_frame_id="f1", + config=config, + prepass_rgbi=_prepass(4, 4), + cancel=cancel, + ) # --------------------------------------------------------------------------- @@ -480,6 +537,10 @@ def test_hybrid_mode_applies_via_hybrid_runner(self, tmp_path: Path, monkeypatch _install_stub_hybrid(monkeypatch) config = FauxiceRepairConfig(enabled=True, mode=RepairMode.HYBRID) hybrid_output = np.full((4, 4, 3), 9999, dtype=np.uint16) + monkeypatch.setattr( + "negpy.services.repair.fauxice_ir_repair.run_hybrid_repair", + lambda *args, **kwargs: _hybrid_outcome(hybrid_output), + ) result = repair_ir_dust( _rgb(4, 4, 1000), @@ -504,29 +565,45 @@ def test_hybrid_mode_applies_via_hybrid_runner(self, tmp_path: Path, monkeypatch } assert "2 region(s) routed" in result.reason - def test_hybrid_mode_without_hybrid_package_degrades_to_exact(self, monkeypatch: pytest.MonkeyPatch) -> None: + def test_hybrid_validity_composite_has_its_own_final_output_hash(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: _install_stub_engine(monkeypatch) - # fauxce_hybrid deliberately not installed. - config = FauxiceRepairConfig(enabled=True, mode=RepairMode.HYBRID) + hybrid_output = np.full((4, 4, 3), 9999, dtype=np.uint16) + outcome = _hybrid_outcome(hybrid_output) + monkeypatch.setattr( + "negpy.services.repair.fauxice_ir_repair.run_hybrid_repair", + lambda *args, **kwargs: outcome, + ) + source = _rgb(4, 4, 1000) + validity = np.ones((4, 4), dtype=np.bool_) + validity[1, 2] = False - result = repair_ir_dust(_rgb(4, 4, 1000), _ir(4, 4, 500), same_frame_id="f1", config=config, prepass_rgbi=_prepass(4, 4)) + result = repair_ir_dust( + source, + _ir(4, 4, 500), + same_frame_id="validity-bound", + config=FauxiceRepairConfig(enabled=True, mode=RepairMode.HYBRID), + prepass_rgbi=_prepass(4, 4), + validity_mask=validity, + hybrid_runtime=_hybrid_runtime(tmp_path), + ) - assert result.status is RepairStatus.APPLIED - assert result.mode_requested is RepairMode.HYBRID - assert result.mode_resolved is RepairMode.EXACT - assert "not installed" in result.reason - assert "degraded to exact" in result.reason + expected = hybrid_output.copy() + expected[1, 2] = source[1, 2] + np.testing.assert_array_equal(result.repaired_rgb16, expected) + assert result.native_output_rgb_sha256 == hashlib.sha256(expected.astype(" None: + def test_hybrid_mode_without_runtime_degrades_to_exact_truthfully(self, monkeypatch: pytest.MonkeyPatch) -> None: _install_stub_engine(monkeypatch) - _install_stub_hybrid(monkeypatch) config = FauxiceRepairConfig(enabled=True, mode=RepairMode.HYBRID) result = repair_ir_dust(_rgb(4, 4, 1000), _ir(4, 4, 500), same_frame_id="f1", config=config, prepass_rgbi=_prepass(4, 4)) assert result.status is RepairStatus.APPLIED + assert result.mode_requested is RepairMode.HYBRID assert result.mode_resolved is RepairMode.EXACT assert "no hybrid runtime is configured" in result.reason + assert "degraded to exact" in result.reason def test_hybrid_run_failure_degrades_to_exact_never_fails(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: _install_stub_engine(monkeypatch) @@ -565,19 +642,20 @@ def unreachable_runner(argv, **kwargs): # before the mode is even inspected, so a cancellation requested up # front skips the whole call, hybrid included, without touching the # subprocess runner. - result = repair_ir_dust( - _rgb(4, 4, 1000), - _ir(4, 4, 500), - same_frame_id="f1", - config=config, - prepass_rgbi=_prepass(4, 4), - hybrid_runtime=_hybrid_runtime(tmp_path), - hybrid_subprocess_runner=unreachable_runner, - cancel=cancel, - ) - - assert result.status is RepairStatus.SKIPPED - assert "cancelled by caller before repair started" in result.reason + with pytest.raises( + FauxiceRepairCancelled, + match="cancelled by caller before repair started", + ): + repair_ir_dust( + _rgb(4, 4, 1000), + _ir(4, 4, 500), + same_frame_id="f1", + config=config, + prepass_rgbi=_prepass(4, 4), + hybrid_runtime=_hybrid_runtime(tmp_path), + hybrid_subprocess_runner=unreachable_runner, + cancel=cancel, + ) def test_hybrid_mode_cancelled_between_preflight_and_hybrid_start(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: """Covers the hybrid-specific cancel check, distinct from the top-level one. @@ -599,22 +677,20 @@ def test_hybrid_mode_cancelled_between_preflight_and_hybrid_start(self, tmp_path def unreachable_runner(argv, **kwargs): raise AssertionError("hybrid subprocess must not run once cancelled") - result = repair_ir_dust( - _rgb(4, 4, 1000), - _ir(4, 4, 500), - same_frame_id="f1", - config=config, - prepass_rgbi=_prepass(4, 4), - hybrid_runtime=_hybrid_runtime(tmp_path), - hybrid_subprocess_runner=unreachable_runner, - cancel=cancel, - ) - - # Hybrid degrades to the exact path (never invoking the subprocess), - # and the same cancellation then cancels that exact fallback too. - assert result.status is RepairStatus.SKIPPED - assert "cancelled before the hybrid run started" in result.reason - assert "cancelled before completion" in result.reason + with pytest.raises( + FauxiceRepairCancelled, + match="cancelled before the hybrid run started", + ): + repair_ir_dust( + _rgb(4, 4, 1000), + _ir(4, 4, 500), + same_frame_id="f1", + config=config, + prepass_rgbi=_prepass(4, 4), + hybrid_runtime=_hybrid_runtime(tmp_path), + hybrid_subprocess_runner=unreachable_runner, + cancel=cancel, + ) # --------------------------------------------------------------------------- @@ -703,8 +779,11 @@ def test_ir_array_not_mutated_in_memory(self, monkeypatch: pytest.MonkeyPatch) - assert result.status is RepairStatus.APPLIED np.testing.assert_array_equal(ir, ir_before) - def test_provenance_sidecar_written_even_when_unavailable(self, tmp_path: Path) -> None: - # No stub engine installed: engine_available() is False. + def test_provenance_sidecar_written_even_when_unavailable(self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setattr( + "negpy.services.repair.fauxice_ir_repair.importlib.util.find_spec", + lambda _name: None, + ) rgb_path, _ = self._write_frame(tmp_path) config = FauxiceRepairConfig(enabled=True) @@ -724,6 +803,10 @@ def test_hybrid_provenance_sidecar_records_disclosure_mask_and_routing(self, tmp np.save(prepass_path, _prepass(4, 4), allow_pickle=False) config = FauxiceRepairConfig(enabled=True, mode=RepairMode.HYBRID) hybrid_output = np.full((4, 4, 3), 9999, dtype=np.uint16) + monkeypatch.setattr( + "negpy.services.repair.fauxice_ir_repair.run_hybrid_repair", + lambda *args, **kwargs: _hybrid_outcome(hybrid_output), + ) result = repair_frame_files( rgb_path, diff --git a/tests/repair/test_hybrid_runtime_manifest.py b/tests/repair/test_hybrid_runtime_manifest.py new file mode 100644 index 00000000..a610ea38 --- /dev/null +++ b/tests/repair/test_hybrid_runtime_manifest.py @@ -0,0 +1,134 @@ +from __future__ import annotations + +import hashlib +import json +import os +from pathlib import Path + +import pytest + +from negpy.services.repair import hybrid_runtime_manifest as runtime_manifest +from negpy.services.repair.hybrid_runtime_manifest import ( + HybridRuntimeManifestError, + RUNTIME_MANIFEST_SCHEMA, + canonical_runtime_manifest_bytes, + load_default_hybrid_runtime_manifest, + load_hybrid_runtime_manifest, + runtime_manifest_pin_path, +) + + +def _document(root: Path) -> dict[str, object]: + return { + "core_source_manifest_sha256": "1" * 64, + "executable": str(root / "hybrid" / "bin" / "fauxce-hybrid"), + "hybrid_python": str(root / "hybrid" / "bin" / "python"), + "hybrid_source_manifest_sha256": "2" * 64, + "inpaint_device": "cpu", + "inpaint_seed": 0, + "inpaint_threads": 1, + "iopaint_executable": str(root / "iopaint" / "bin" / "iopaint"), + "iopaint_python": str(root / "iopaint" / "bin" / "python"), + "iopaint_source_manifest_sha256": "3" * 64, + "model_dir": str(root / "models"), + "model_weights": str(root / "models" / "torch" / "hub" / "checkpoints" / "big-lama.pt"), + "model_weights_sha256": "4" * 64, + "schema": RUNTIME_MANIFEST_SCHEMA, + } + + +def _write_pair(path: Path, document: dict[str, object]) -> str: + payload = canonical_runtime_manifest_bytes(document) + path.write_bytes(payload) + digest = hashlib.sha256(payload).hexdigest() + runtime_manifest_pin_path(path).write_text(digest + "\n", encoding="ascii") + return digest + + +def test_loads_canonical_hash_pinned_runtime(tmp_path: Path) -> None: + path = tmp_path / "fauxce-hybrid-runtime.json" + digest = _write_pair(path, _document(tmp_path)) + + runtime = load_hybrid_runtime_manifest(path, expected_sha256=digest) + + assert runtime.hybrid_python == tmp_path / "hybrid" / "bin" / "python" + assert runtime.core_source_manifest_sha256 == "1" * 64 + assert runtime.hybrid_source_manifest_sha256 == "2" * 64 + + +def test_default_loader_requires_manifest_and_independent_pin(tmp_path: Path) -> None: + path = tmp_path / "fauxce-hybrid-runtime.json" + assert load_default_hybrid_runtime_manifest(path) is None + + path.write_bytes(canonical_runtime_manifest_bytes(_document(tmp_path))) + with pytest.raises(HybridRuntimeManifestError, match="must both exist"): + load_default_hybrid_runtime_manifest(path) + + +def test_default_loader_rejects_tampering_after_pin(tmp_path: Path) -> None: + path = tmp_path / "fauxce-hybrid-runtime.json" + document = _document(tmp_path) + _write_pair(path, document) + document["inpaint_threads"] = 2 + path.write_bytes(canonical_runtime_manifest_bytes(document)) + + with pytest.raises(HybridRuntimeManifestError, match="SHA-256 mismatch"): + load_default_hybrid_runtime_manifest(path) + + +def test_loader_rejects_noncanonical_or_extended_manifest(tmp_path: Path) -> None: + path = tmp_path / "fauxce-hybrid-runtime.json" + document = _document(tmp_path) + document["unexpected"] = True + payload = (json.dumps(document, indent=2) + "\n").encode() + path.write_bytes(payload) + + with pytest.raises(HybridRuntimeManifestError, match="not canonical"): + load_hybrid_runtime_manifest( + path, + expected_sha256=hashlib.sha256(payload).hexdigest(), + ) + + +def test_loader_rejects_symlinked_manifest(tmp_path: Path) -> None: + target = tmp_path / "target.json" + payload = canonical_runtime_manifest_bytes(_document(tmp_path)) + target.write_bytes(payload) + path = tmp_path / "fauxce-hybrid-runtime.json" + path.symlink_to(target) + + with pytest.raises(HybridRuntimeManifestError, match="non-symlink"): + load_hybrid_runtime_manifest( + path, + expected_sha256=hashlib.sha256(payload).hexdigest(), + ) + + +def test_loader_does_not_block_if_manifest_is_swapped_to_fifo( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + path = tmp_path / "fauxce-hybrid-runtime.json" + payload = canonical_runtime_manifest_bytes(_document(tmp_path)) + path.write_bytes(payload) + real_open = runtime_manifest.os.open + swapped = False + + def swap_before_open(candidate, flags, *args, **kwargs): + nonlocal swapped + if os.fspath(candidate) == os.fspath(path) and not swapped: + swapped = True + assert flags & os.O_NONBLOCK + path.unlink() + os.mkfifo(path) + return real_open(candidate, flags, *args, **kwargs) + + monkeypatch.setattr(runtime_manifest.os, "open", swap_before_open) + + with pytest.raises(HybridRuntimeManifestError, match="changed while opening"): + load_hybrid_runtime_manifest( + path, + expected_sha256=hashlib.sha256(payload).hexdigest(), + ) + + assert swapped is True diff --git a/tests/roll/_exact_fixtures.py b/tests/roll/_exact_fixtures.py new file mode 100644 index 00000000..5d0739ce --- /dev/null +++ b/tests/roll/_exact_fixtures.py @@ -0,0 +1,143 @@ +"""Explicit test-only files for the production exact-color trust boundary.""" + +from __future__ import annotations + +import json +import tempfile +from hashlib import sha256 +from pathlib import Path + +import numpy as np + +from negpy.services.roll import exact_color + + +CHANNELS = ("r", "g", "b") +PRE_F_BYTES = 65_536 * 2 +FIXED_ARTIFACT_BYTES = { + "analyzer-desc.bin": 96, + "analyzer-pixels.bin": 281 * 425 * 3 * 2, + "builder-args.bin": 204, + **{f"builder-control-{axis}-{channel}.bin": 256 for channel in CHANNELS for axis in ("x", "y")}, +} + + +def write_stage3_replay_fixture( + root: Path, + pre_f_arrays: tuple[np.ndarray, np.ndarray, np.ndarray], +) -> Path: + """Write a complete real-shaped PASS JSON plus the three consumed LUTs.""" + + root.mkdir(parents=True, exist_ok=True) + pre_f_luts = tuple(np.asarray(array, dtype=" exact_color.ValidatedBuilderReceipt: + with tempfile.TemporaryDirectory(prefix="negpy-stage3-test-") as directory: + report_path = write_stage3_replay_fixture(Path(directory), pre_f_arrays) + return exact_color.load_stage3_replay_builder_receipt(report_path) + + +def production_cms_payload( + *, + builder_receipt_sha256: str, + input_rgb_sha256: str, + output_rgb_sha256: str, + chunk_pixels: int = 17, +) -> dict[str, object]: + return { + "algorithm": exact_color.CMS_ALGORITHM_ID, + "assets": dict(exact_color.CMS_ASSET_SHA256), + "builder_receipt_sha256": builder_receipt_sha256, + "chunk_pixels": chunk_pixels, + "dll_free": True, + "input_rgb_sha256": input_rgb_sha256, + "kind": exact_color.CMS_RECEIPT_KIND, + "oracle_source": { + "path": "portable_oracle_evaluator.py", + "sha256": exact_color.CMS_ORACLE_SOURCE_SHA256, + }, + "output_rgb_sha256": output_rgb_sha256, + "scope": exact_color.CMS_SCOPE, + "stage_order": ["stage1", "stage2"], + "upstream_builder_included": False, + "validation": { + "events": 12, + "full_payload_mismatched_bytes": 0, + "full_payload_total_bytes": 698_880, + "mismatched_u16": 0, + "receipt_sha256": exact_color.CMS_VALIDATION_RECEIPT_SHA256, + "total_u16": 265_440, + }, + "version": exact_color.CMS_RECEIPT_VERSION, + } diff --git a/tests/roll/conftest.py b/tests/roll/conftest.py index e8db2a68..2c9b9ff2 100644 --- a/tests/roll/conftest.py +++ b/tests/roll/conftest.py @@ -10,11 +10,13 @@ from __future__ import annotations import dataclasses +import hashlib import importlib.machinery import sys import types from typing import Any +import numpy as np import pytest from negpy.infrastructure.roll import repair as roll_repair @@ -73,6 +75,13 @@ class FakeFrame: ir: Any ir_validity: Any receipt: Any + meter_rgbi: Any = None + digital_ice_acquisition: Any = None + + def prepare_digital_ice(self): + if self.digital_ice_acquisition is None: + raise ValueError("frame has no bound Digital ICE acquisition") + return self.digital_ice_acquisition @dataclasses.dataclass(frozen=True) @@ -95,6 +104,7 @@ def __init__(self, thumbnails=None, frames=None, raise_on=None) -> None: self._frames = {frame.slot: frame for frame in (frames or [])} self._raise_on = raise_on or {} self.approved: list[int] = [] + self.restore_preview_session_calls: list[tuple[str, tuple[int, ...] | None]] = [] self.spacing_offsets: dict[int, int] = {} self.safe_stop_called = False self.closed = False @@ -103,13 +113,27 @@ def preview(self, slots=None, *, on_progress=None): if "preview" in self._raise_on: raise self._raise_on["preview"] if on_progress is not None: - on_progress(FakeProgress(stage="preview", slot=None, index=0, total=1, fraction=0.0, message="reading whole-roll transport index")) + on_progress( + FakeProgress(stage="preview", slot=None, index=0, total=1, fraction=0.0, message="reading whole-roll transport index") + ) wanted = None if slots is None else set(slots) result = [t for t in self._thumbnails if wanted is None or t.slot in wanted] if on_progress is not None: on_progress(FakeProgress(stage="preview", slot=None, index=1, total=1, fraction=1.0, message="preview complete")) return result + def restore_preview_session(self, payload, slots=None): + if "restore_preview_session" in self._raise_on: + raise self._raise_on["restore_preview_session"] + requested = None if slots is None else tuple(slots) + self.restore_preview_session_calls.append((payload, requested)) + wanted = None if requested is None else set(requested) + return [ + thumbnail + for thumbnail in self._thumbnails + if wanted is None or thumbnail.slot in wanted + ] + def set_spacing_offset(self, slot, offset_rows) -> None: self.spacing_offsets[slot] = offset_rows @@ -128,7 +152,9 @@ def scan_many(self, slots, *, on_progress=None): if error is not None: raise error if on_progress is not None: - on_progress(FakeProgress(stage="fine-scan", slot=slot, index=i, total=len(ordered), fraction=1.0, message=f"slot {slot} complete")) + on_progress( + FakeProgress(stage="fine-scan", slot=slot, index=i, total=len(ordered), fraction=1.0, message=f"slot {slot} complete") + ) yield self._frames[slot] def safe_stop(self) -> None: @@ -204,7 +230,8 @@ class FakeRepairEngine: tagged with this engine's name/version/mode) -- set `.transform` to a function to script a detectable change, or `.raise_error` to an exception instance to script a failure. `.calls` records every `(rgb, ir, mode)` - the service passed in, so a test can assert *what* was repaired (e.g. + and `.prepasses` the matching prepass the service passed in, so a test + can assert *what* was repaired (e.g. the Tier-1 array, not something already touched) without needing the repair to actually alter pixels. """ @@ -214,13 +241,45 @@ def __init__(self, *, engine: str = "test-repair-engine", engine_version: str = self.engine_version = engine_version self.transform = lambda rgb: rgb self.raise_error: Exception | None = None - self.calls: list[tuple[Any, Any, roll_repair.RepairMode]] = [] - - def repair(self, rgb: Any, ir: Any, mode: roll_repair.RepairMode) -> roll_repair.RepairResult: - self.calls.append((rgb, ir, mode)) + self.calls: list[tuple[Any, roll_repair.RepairMode, Any]] = [] + self.prepasses: list[Any] = [] + + def repair( + self, + acquisition: Any, + mode: roll_repair.RepairMode, + *, + hybrid_runtime: Any = None, + progress: Any = None, + cancel: Any = None, + ) -> roll_repair.RepairResult: + self.calls.append((acquisition, mode, hybrid_runtime)) + self.prepasses.append(acquisition.prepass_rgbi) + if progress is not None: + progress(1.0) if self.raise_error is not None: raise self.raise_error - return roll_repair.RepairResult(rgb=self.transform(rgb), engine=self.engine, engine_version=self.engine_version, mode=mode) + native_rgb = self.transform(acquisition.main_rgbi[..., :3]) + storage_rgb = acquisition.storage_rgb(native_rgb) + resolved_mode = roll_repair.RepairMode.EXACT if mode is roll_repair.RepairMode.HYBRID and hybrid_runtime is None else mode + return roll_repair.RepairResult( + rgb=storage_rgb, + engine=self.engine, + engine_version=self.engine_version, + mode_requested=mode, + mode_resolved=resolved_mode, + reason=( + "hybrid mode requested but no hybrid runtime is configured; degraded to exact repair" + if resolved_mode is not mode + else "test repair applied" + ), + acquisition_id=acquisition.acquisition_id, + slot=acquisition.slot, + reservation_id=acquisition.reservation_id, + evidence_sha256=acquisition.evidence_sha256, + native_output_rgb_sha256=hashlib.sha256(np.asarray(native_rgb, dtype=" None: monkeypatch.delitem(sys.modules, "coolscanpy", raising=False) - # This environment genuinely doesn't have coolscanpy installed (it's - # an optional dependency-group); if that ever changes, find_spec - # itself would need monkeypatching instead of just sys.modules. - assert importlib.util.find_spec("coolscanpy") is None + # The optional group may intentionally be installed by this test run, + # so model an absent package at the availability seam rather than + # assuming anything about the interpreter's installed extras. + monkeypatch.setattr(coolscanpy_roll.importlib.util, "find_spec", lambda _name: None) assert coolscanpy_roll.available() is False def test_true_when_fake_module_present(self, fake_coolscanpy) -> None: @@ -109,9 +108,65 @@ def test_preview_exception_translated(self, fake_coolscanpy) -> None: with pytest.raises(RuntimeError, match="fingerprint mismatch"): handle.preview() - def test_approve_records_slot(self, fake_coolscanpy) -> None: - handle, roll, _device = self._handle(fake_coolscanpy) - handle.approve(3) + def test_restore_preview_session_passthrough(self, fake_coolscanpy) -> None: + thumbnails = [ + fake_coolscanpy.Thumbnail( + slot=slot, + image=np.zeros((2, 2, 3)), + boundary_rows=(0, 2), + spacing_offset=0, + needs_approval=False, + ) + for slot in (1, 2, 3) + ] + handle, roll, _device = self._handle( + fake_coolscanpy, + fake_coolscanpy.Roll(thumbnails=thumbnails), + ) + + result = handle.restore_preview_session("saved-session", [1, 3]) + + assert [thumbnail.slot for thumbnail in result] == [1, 3] + assert roll.restore_preview_session_calls == [ + ("saved-session", (1, 3)) + ] + + def test_restore_preview_session_exception_translated( + self, fake_coolscanpy + ) -> None: + error = fake_coolscanpy.module.PyCoolscanError( + "saved preview hash mismatch" + ) + handle, _roll, _device = self._handle( + fake_coolscanpy, + fake_coolscanpy.Roll( + raise_on={"restore_preview_session": error} + ), + ) + + with pytest.raises(RuntimeError, match="saved preview hash mismatch") as excinfo: + handle.restore_preview_session("saved-session") + + assert excinfo.value.__cause__ is error + + def test_approve_returns_underlying_content_bound_receipt( + self, fake_coolscanpy + ) -> None: + approval = object() + + class ReturningApprovalRoll(fake_coolscanpy.Roll): + def approve(self, slot): + super().approve(slot) + return approval + + handle, roll, _device = self._handle( + fake_coolscanpy, + ReturningApprovalRoll(), + ) + + result = handle.approve(3) + + assert result is approval assert roll.approved == [3] def test_manual_review_required_translated(self, fake_coolscanpy) -> None: @@ -154,6 +209,23 @@ def test_close_releases_roll_then_device(self, fake_coolscanpy) -> None: assert roll.closed is True assert device.closed is True + def test_close_retains_device_when_roll_ownership_is_uncertain( + self, fake_coolscanpy + ) -> None: + ownership_error = RuntimeError("USB ownership is retained") + + class UncertainRoll(fake_coolscanpy.Roll): + def close(self) -> None: + raise ownership_error + + handle, _roll, device = self._handle(fake_coolscanpy, UncertainRoll()) + + with pytest.raises(RuntimeError) as raised: + handle.close() + + assert raised.value is ownership_error + assert device.closed is False + def test_context_manager_closes_on_exit(self, fake_coolscanpy) -> None: roll = fake_coolscanpy.Roll() device = fake_coolscanpy.Device(roll) diff --git a/tests/roll/test_deep_acceptance.py b/tests/roll/test_deep_acceptance.py new file mode 100644 index 00000000..2cfad360 --- /dev/null +++ b/tests/roll/test_deep_acceptance.py @@ -0,0 +1,1143 @@ +from __future__ import annotations + +import hashlib +import json +import os +import fcntl +from dataclasses import replace +from pathlib import Path +from types import SimpleNamespace + +import numpy as np +import pytest + +from coolscanpy.protocol.ls5000_single_pass.density import ( + NikonDensityFrameOwnershipReceipt, +) +from coolscanpy.types import ( + ArtifactEvidence, + ClippingTelemetry, + ExposureVector, + FocusDetailTelemetry, + Receipt, + TransportSmearAssessment, +) + +from negpy.infrastructure.roll import repair as roll_repair +from negpy.services.roll import deep_acceptance +from negpy.services.roll import service as roll_service +from negpy.services.roll.service import RollFrameOutput, RollScanningService +from tests.roll.test_native_builder import ( + FRESH_FINGERPRINT_SHA256, + RESERVATION_ID, + REVIEWED_FINGERPRINT_SHA256, + _PayloadReceipt, + _density_document, + _ownership_document, + _synthetic_native_evidence, +) +from tests.roll.test_service import _valid_hybrid_result + + +class _Runtime: + core_source_manifest_sha256 = "1" * 64 + hybrid_source_manifest_sha256 = "2" * 64 + iopaint_source_manifest_sha256 = "3" * 64 + model_weights_sha256 = "4" * 64 + inpaint_device = "cpu" + inpaint_threads = 1 + inpaint_seed = 0 + + def __init__(self) -> None: + self.validated = False + + def validate_files(self) -> None: + self.validated = True + + +def _runtime_bound_hybrid_result( + acquisition: roll_repair.RepairAcquisition, + runtime: _Runtime, +) -> roll_repair.RepairResult: + base = _valid_hybrid_result(acquisition) + document = json.loads(base.hybrid_receipt) + assertion = { + "assertions": { + "focus_exposure_locked": True, + "same_frame_id": acquisition.acquisition_id, + }, + "inputs": { + "main": {"raw_sha256": acquisition.main_rgbi_sha256}, + "prepass": {"raw_sha256": acquisition.prepass_rgbi_sha256}, + }, + "provenance_class": "caller_asserted_bare_npy", + "schema": "negpy.fauxce-hybrid-acquisition-assertion-v1", + } + document.update( + core={ + "backend": { + "reason": base.backend_selection_reason, + "requested": base.backend_requested, + "used": base.backend_used, + }, + "source_manifest_sha256": runtime.core_source_manifest_sha256, + "version": base.engine_version, + }, + generation={"hybrid_source_manifest_sha256": (runtime.hybrid_source_manifest_sha256)}, + inpainting={"invoked": False}, + inputs={ + "geometry": { + "mask_shape": list(acquisition.main_rgbi.shape[:2]), + "output_shape": [*acquisition.main_rgbi.shape[:2], 3], + }, + "main": { + "canonical_encoding": "uint16_little_endian_c_order", + "raw_sha256": acquisition.main_rgbi_sha256, + "shape": list(acquisition.main_rgbi.shape), + }, + "prepass": { + "canonical_encoding": "uint16_little_endian_c_order", + "raw_sha256": acquisition.prepass_rgbi_sha256, + "shape": list(acquisition.prepass_rgbi.shape), + }, + "provenance": { + "basis": "caller_asserted", + "source_manifest_sha256": hashlib.sha256(deep_acceptance._canonical_json(assertion)).hexdigest(), + }, + }, + ) + payload = ( + json.dumps( + document, + sort_keys=True, + separators=(",", ":"), + ensure_ascii=True, + allow_nan=False, + ).encode() + + b"\n" + ) + return replace( + base, + hybrid_receipt=payload, + hybrid_receipt_sha256=hashlib.sha256(payload).hexdigest(), + ) + + +def _production_frame() -> tuple[SimpleNamespace, roll_repair.RepairAcquisition]: + slot = 5 + storage_rgb = np.arange(4 * 6 * 3, dtype=np.uint16).reshape(4, 6, 3) + storage_rgb += np.uint16(10_000) + storage_ir = np.arange(4 * 6, dtype=np.uint16).reshape(4, 6) + storage_ir += np.uint16(2_000) + storage_validity = np.ones((4, 6), dtype=np.bool_) + meter = np.arange(2 * 3 * 4, dtype=np.uint16).reshape(2, 3, 4) + native_rgbi = np.ascontiguousarray( + np.rot90( + np.dstack((storage_rgb, storage_ir)), + k=-1, + axes=(0, 1), + ) + ) + native_validity = np.ascontiguousarray(np.rot90(storage_validity, k=-1, axes=(0, 1))) + acquisition_id, evidence_sha256 = roll_service._derive_digital_ice_producer_binding( + slot=slot, + reservation_id=RESERVATION_ID, + capture_attempt_id="capture-d", + main_rgbi=native_rgbi, + prepass_rgbi=meter, + ir_validity=native_validity, + ) + acquisition = roll_repair.RepairAcquisition.from_arrays( + acquisition_id=acquisition_id, + slot=slot, + reservation_id=RESERVATION_ID, + capture_attempt_id="capture-d", + storage_transform=roll_repair.DIGITAL_ICE_STORAGE_TRANSFORM, + evidence_sha256=evidence_sha256, + main_rgbi=native_rgbi, + prepass_rgbi=meter, + ir_validity=native_validity, + ) + + def artifact(array: np.ndarray) -> ArtifactEvidence: + contiguous = np.ascontiguousarray(array) + return ArtifactEvidence( + sha256=hashlib.sha256(memoryview(contiguous).cast("B")).hexdigest(), + byte_length=contiguous.nbytes, + shape=contiguous.shape, + dtype=str(contiguous.dtype), + ) + + ownership_document = _ownership_document() + ownership = NikonDensityFrameOwnershipReceipt.from_dict(ownership_document) + receipt = Receipt( + version=1, + slot=slot, + spacing_offset=0, + dpi=4_000, + depth=16, + device_id="usb:2:7", + device_model="Nikon LS-5000 ED 1.03", + reviewed_fingerprint_sha256=REVIEWED_FINGERPRINT_SHA256, + fresh_fingerprint_sha256=FRESH_FINGERPRINT_SHA256, + manual_approval=None, + exposure=ExposureVector(1, 1.0, 1.0, 1.0, 1.0), + split_alignment=None, + clipping=ClippingTelemetry((0.0, 0.0, 0.0), 65_535.0, 0.01, False), + focus_detail=FocusDetailTelemetry("laplacian", "measured", 1.0, 1.0), + transport_smear=TransportSmearAssessment( + "clean", + None, + 0, + 0, + None, + None, + None, + None, + "clean", + ), + artifacts={"rgb": artifact(storage_rgb), "ir": artifact(storage_ir)}, + nikon_density_ownership=ownership, + ) + frame = SimpleNamespace( + slot=slot, + rgb=storage_rgb, + ir=storage_ir, + ir_validity=storage_validity, + meter_rgbi=meter, + receipt=receipt, + nikon_density_evidence=_PayloadReceipt(_density_document()), + nikon_density_ownership=_PayloadReceipt(ownership_document), + nikon_exact_builder_evidence=_synthetic_native_evidence(), + prepare_digital_ice=lambda: acquisition, + ) + return frame, acquisition + + +def _digest(text: str) -> str: + return hashlib.sha256(text.encode()).hexdigest() + + +def _output(root: Path, slot: int) -> RollFrameOutput: + base = root / f"acceptance_slot{slot:02d}" + return RollFrameOutput( + slot=slot, + rgb_path=str(base.with_suffix(".tif")), + ir_path=str(base.with_name(base.name + "_IR.tif")), + repaired_rgb_path=str(base.with_name(base.name + "_repaired.tif")), + repaired_ir_path=str(base.with_name(base.name + "_repaired_IR.tif")), + positive_path=str(base.with_name(base.name + "_positive.tif")), + receipt_path=str(base.with_name(base.name + "_receipt.json")), + synthesis_mask_path=str(base.with_name(base.name + "_repaired_SYNTH.png")), + native_synthesis_mask_path=str(root / ".negpy-dice-hybrid" / f"slot-{slot}" / "native.png"), + hybrid_receipt_path=str(root / ".negpy-dice-hybrid" / f"slot-{slot}" / "hybrid-receipt.json"), + ) + + +def _completed_receipt_fixture( + root: Path, + *, + slot: int = 1, +) -> tuple[RollFrameOutput, dict[str, object]]: + output = _output(root, slot) + + def touch(path: Path) -> str: + path.parent.mkdir(parents=True, exist_ok=True) + path.write_bytes(f"slot-{slot}:{path.name}".encode()) + return str(path) + + for value in ( + output.rgb_path, + output.ir_path, + output.repaired_rgb_path, + output.repaired_ir_path, + output.positive_path, + output.synthesis_mask_path, + output.native_synthesis_mask_path, + output.hybrid_receipt_path, + ): + touch(Path(value)) + + dice_dir = root / ".negpy-dice-acquisition" / f"slot-{slot}" + dice_binding = dice_dir / "acquisition-binding.json" + dice_prepass = dice_dir / "prepass.rgbi16.npy" + dice_validity = dice_dir / "ir-validity.npy" + for path in (dice_binding, dice_prepass, dice_validity): + touch(path) + + hybrid_dir = Path(output.hybrid_receipt_path).parent + routed_mask = hybrid_dir / "routed.png" + hybrid_binding = hybrid_dir / "binding.json" + for path in (routed_mask, hybrid_binding): + touch(path) + + native_dir = root / ".negpy-native-builder" / f"slot-{slot}" + native_paths = { + "builder_receipt": native_dir / "native-builder-receipt.json", + "analyzer_rgb": native_dir / "analyzer-rgb-u16le.bin", + "evidence_receipt": native_dir / "native-builder-evidence.json", + "frame_ownership_receipt": (native_dir / "nikon-density-frame-ownership.json"), + "density_evidence_receipt": native_dir / "nikon-density-evidence.json", + } + lut_paths = [native_dir / f"builder-preF-{channel}.bin" for channel in "rgb"] + for path in (*native_paths.values(), *lut_paths): + touch(path) + + retained = { + **{key: {"path": str(path)} for key, path in native_paths.items()}, + "native_per_acquisition_builder": True, + "pre_f_luts": [{"path": str(path)} for path in lut_paths], + } + document: dict[str, object] = { + "artifacts": {"ir": {}, "rgb": {}}, + "depth": 16, + "device_id": "usb:2:7", + "device_model": "Nikon LS-5000 ED 1.03", + "dpi": 4_000, + "outputs": { + "native_color_evidence": { + "native_per_acquisition_builder": True, + "retained": True, + "retained_builder_evidence": retained, + }, + "positive": { + "builder_validated": True, + "cms_verified": True, + "color_mode": "nikon-exact", + "exact_nikon_color": True, + "native_per_acquisition_builder": True, + "retained_builder_evidence": retained, + "rgb_path": output.positive_path, + "written": True, + }, + "repair_acquisition_evidence": { + "artifacts": { + "ir_validity": {"path": str(dice_validity)}, + "prepass_rgbi": {"path": str(dice_prepass)}, + }, + "binding": {"path": str(dice_binding)}, + "replayable": True, + "retained": True, + "sources": { + "storage_ir_tiff": {"path": output.ir_path}, + "storage_rgb_tiff": {"path": output.rgb_path}, + }, + }, + "repaired": { + "degraded": False, + "disclosure_mask": { + "applied_final": { + "native": {"path": output.native_synthesis_mask_path}, + "storage": {"path": output.synthesis_mask_path}, + }, + "routed_raw": {"native": {"path": str(routed_mask)}}, + }, + "hybrid_evidence_binding": {"path": str(hybrid_binding)}, + "hybrid_receipt": {"path": output.hybrid_receipt_path}, + "ir_path": output.repaired_ir_path, + "mode_requested": "hybrid", + "mode_resolved": "hybrid", + "rgb_path": output.repaired_rgb_path, + "written": True, + }, + "unrepaired": { + "ir_path": output.ir_path, + "rgb_path": output.rgb_path, + "written": True, + }, + }, + "slot": slot, + "version": 1, + } + receipt_path = Path(output.receipt_path) + receipt_path.write_text( + json.dumps(document, indent=2, ensure_ascii=True, allow_nan=False), + encoding="utf-8", + ) + lock_dir = root / ".negpy-locks" + lock_dir.mkdir() + lock_path = lock_dir / (hashlib.sha256(str(receipt_path).encode()).hexdigest() + ".lock") + lock_path.write_bytes(b"") + return output, document + + +def _approval(slot: int, reviewed: str) -> dict[str, object]: + return { + "reviewed_fingerprint_sha256": reviewed, + "slot": slot, + "spacing_offset": 0, + "thumbnail_sha256": _digest(f"thumbnail-{slot}"), + "reviewed_lookup_row": (slot - 1) * 143, + "reviewed_native_origin": (slot - 1) * 5_959, + "review_reasons": ["strip boundary requires review"], + } + + +def _fake_audits(root: Path) -> tuple[list[RollFrameOutput], list[deep_acceptance._FrameAudit]]: + reservation = "reservation-six-frame" + preview = _digest("preview") + preview_identity = _digest("preview-identity") + transport_table = _digest("transport-table") + reviewed = _digest("reviewed") + fresh = _digest("fresh") + transport_material = { + "reservation_id": reservation, + "batch_session_id": reservation, + "preview_sha256": preview, + "preview_identity_sha256": preview_identity, + "transport_table_sha256": transport_table, + "reviewed_fingerprint_sha256": reviewed, + "fresh_fingerprint_sha256": fresh, + "selected_slots": list(deep_acceptance.SLOTS), + } + transport_identity = hashlib.sha256( + json.dumps( + transport_material, + sort_keys=True, + separators=(",", ":"), + ).encode() + ).hexdigest() + outputs: list[RollFrameOutput] = [] + audits: list[deep_acceptance._FrameAudit] = [] + for slot in deep_acceptance.SLOTS: + output = _output(root, slot) + outputs.append(output) + visible = { + Path(output.rgb_path), + Path(output.ir_path), + Path(output.repaired_rgb_path), + Path(output.repaired_ir_path), + Path(output.positive_path), + Path(output.receipt_path), + Path(output.synthesis_mask_path), + } + for path in visible: + path.parent.mkdir(parents=True, exist_ok=True) + path.write_bytes(f"slot-{slot}:{path.name}".encode()) + ownership = SimpleNamespace( + reservation_id=reservation, + batch_session_id=reservation, + preview_sha256=preview, + preview_identity_sha256=preview_identity, + transport_table_sha256=transport_table, + transport_identity_sha256=transport_identity, + reviewed_fingerprint_sha256=reviewed, + fresh_fingerprint_sha256=fresh, + frame_capture_attempt_id=f"attempt-{slot}", + frame_index=slot, + frame_total=6, + selected_slots=deep_acceptance.SLOTS, + selected_slot=slot, + ) + receipt = { + "device_id": "usb:2:7", + "device_model": "Nikon LS-5000 ED 1.03", + "spacing_offset": 0, + "manual_approval": (_approval(slot, reviewed) if slot in deep_acceptance.APPROVED_SLOTS else None), + } + receipt_path = Path(output.receipt_path) + receipt_sha = hashlib.sha256(receipt_path.read_bytes()).hexdigest() + lock_dir = root / ".negpy-locks" + lock_dir.mkdir(exist_ok=True) + lock_path = lock_dir / (hashlib.sha256(str(receipt_path).encode()).hexdigest() + ".lock") + lock_path.write_bytes(b"") + visible.add(lock_path) + audits.append( + deep_acceptance._FrameAudit( + summary={ + "slot": slot, + "frame_receipt": { + "path": str(receipt_path), + "bytes": receipt_path.stat().st_size, + "sha256": receipt_sha, + }, + "referenced_file_count": len(visible), + "referenced_files": sorted(str(path) for path in visible), + }, + referenced_files=frozenset(visible), + receipt_path=receipt_path, + receipt=receipt, + ownership=ownership, + builder_receipt=SimpleNamespace(density_evidence_receipt_sha256=_digest("density")), + output_artifacts={field: getattr(output, field) for field in deep_acceptance._OUTPUT_FIELDS}, + ) + ) + return outputs, audits + + +def test_six_frame_batch_closes_cross_frame_and_exact_inventory( + monkeypatch: pytest.MonkeyPatch, + tmp_path: Path, +) -> None: + outputs, audits = _fake_audits(tmp_path) + runtime = _Runtime() + iterator = iter(audits) + + def validate_locked_frame(*args: object, **kwargs: object): + del args, kwargs + for audit in audits: + lock_path = deep_acceptance._receipt_lock_path(audit.receipt_path) + descriptor = os.open(lock_path, os.O_RDWR) + try: + with pytest.raises(BlockingIOError): + fcntl.flock(descriptor, fcntl.LOCK_EX | fcntl.LOCK_NB) + finally: + os.close(descriptor) + return next(iterator) + + monkeypatch.setattr( + deep_acceptance, + "_validate_frame", + validate_locked_frame, + ) + audit_lock = tmp_path / "deep-audit.lock" + audit_lock.write_bytes(b"") + + result = deep_acceptance.validate_six_frame_batch( + outputs, + output_dir=tmp_path, + allowed_output_lock_name=audit_lock.name, + builder=object(), + evaluator=object(), + hybrid_runtime=runtime, + ) + + assert result["status"] == "passed" + assert result["slots"] == [1, 2, 3, 4, 5, 6] + assert result["approved_slots"] == [1, 6] + assert result["device_id"] == "usb:2:7" + assert result["device_model"] == "Nikon LS-5000 ED 1.03" + assert result["reviewed_fingerprint_sha256"] == audits[0].ownership.reviewed_fingerprint_sha256 + assert result["manual_approval_bindings"] == [ + { + "slot": slot, + "binding_sha256": deep_acceptance._validate_manual_approval( + audits[slot - 1], + expected=True, + )["binding_sha256"], + } + for slot in sorted(deep_acceptance.APPROVED_SLOTS) + ] + assert result["inventory"]["visible_file_count"] == 42 + assert result["inventory"]["exact"] is True + assert result["inventory"]["allowed_output_lock_path"] == str(audit_lock) + assert len(result["referenced_files"]) == 48 + assert str(audit_lock) not in result["referenced_files"] + assert all(Path(path).is_absolute() for path in result["referenced_files"]) + assert runtime.validated is True + json.dumps(result, allow_nan=False) + + +def test_completed_frame_reports_deterministic_absolute_references( + monkeypatch: pytest.MonkeyPatch, + tmp_path: Path, +) -> None: + outputs, audits = _fake_audits(tmp_path) + audit = audits[0] + monkeypatch.setattr( + deep_acceptance, + "_validate_frame", + lambda *args, **kwargs: audit, + ) + + result = deep_acceptance.validate_completed_frame( + outputs[0], + output_dir=tmp_path, + builder=object(), + evaluator=object(), + hybrid_runtime=_Runtime(), + ) + + assert result["referenced_files"] == sorted(result["referenced_files"]) + assert result["referenced_file_count"] == len(result["referenced_files"]) + assert all(Path(path).is_absolute() for path in result["referenced_files"]) + assert any("/.negpy-locks/" in path for path in result["referenced_files"]) + + +def test_manual_approval_rejects_string_review_reasons(tmp_path: Path) -> None: + _, audits = _fake_audits(tmp_path) + approval = audits[0].receipt["manual_approval"] + assert isinstance(approval, dict) + approval["review_reasons"] = "not-a-json-list" + + with pytest.raises( + deep_acceptance.DeepAcceptanceError, + match="approval reasons are malformed", + ): + deep_acceptance._validate_manual_approval(audits[0], expected=True) + + +def test_collect_completed_frame_files_returns_exact_cheap_inventory( + tmp_path: Path, +) -> None: + output, _ = _completed_receipt_fixture(tmp_path) + + files = deep_acceptance.collect_completed_frame_files( + output, + output_dir=tmp_path, + expected_slot=1, + ) + + assert files == sorted(files) + assert len(files) == 23 + assert len(set(files)) == 23 + assert all(Path(path).is_absolute() and Path(path).is_file() for path in files) + assert output.receipt_path in files + assert any("/.negpy-locks/" in path for path in files) + + +def test_collect_completed_frame_files_rejects_extra_receipt_path( + tmp_path: Path, +) -> None: + output, document = _completed_receipt_fixture(tmp_path) + extra = tmp_path / "extra.bin" + extra.write_bytes(b"extra") + outputs = document["outputs"] + assert isinstance(outputs, dict) + positive = outputs["positive"] + assert isinstance(positive, dict) + positive["unexpected"] = {"path": str(extra)} + Path(output.receipt_path).write_text( + json.dumps(document, indent=2, ensure_ascii=True, allow_nan=False), + encoding="utf-8", + ) + + with pytest.raises( + deep_acceptance.DeepAcceptanceError, + match="receipt path inventory changed", + ): + deep_acceptance.collect_completed_frame_files( + output, + output_dir=tmp_path, + expected_slot=1, + ) + + +def test_collect_completed_frame_files_rejects_noncanonical_or_mismatched_output( + tmp_path: Path, +) -> None: + output, document = _completed_receipt_fixture(tmp_path) + Path(output.receipt_path).write_text(json.dumps(document), encoding="utf-8") + with pytest.raises( + deep_acceptance.DeepAcceptanceError, + match="canonical frame-receipt encoding", + ): + deep_acceptance.collect_completed_frame_files( + output, + output_dir=tmp_path, + expected_slot=1, + ) + + Path(output.receipt_path).write_text( + json.dumps(document, indent=2, ensure_ascii=True, allow_nan=False), + encoding="utf-8", + ) + wrong_positive = tmp_path / "wrong-positive.tif" + wrong_positive.write_bytes(b"wrong") + mismatched = replace(output, positive_path=str(wrong_positive)) + with pytest.raises( + deep_acceptance.DeepAcceptanceError, + match="positive_path differs from its receipt", + ): + deep_acceptance.collect_completed_frame_files( + mismatched, + output_dir=tmp_path, + expected_slot=1, + ) + + +def test_collect_completed_frame_files_rejects_nonabsolute_required_path( + tmp_path: Path, +) -> None: + output, document = _completed_receipt_fixture(tmp_path) + outputs = document["outputs"] + assert isinstance(outputs, dict) + unrepaired = outputs["unrepaired"] + assert isinstance(unrepaired, dict) + unrepaired["rgb_path"] = "relative-frame.tif" + Path(output.receipt_path).write_text( + json.dumps(document, indent=2, ensure_ascii=True, allow_nan=False), + encoding="utf-8", + ) + + with pytest.raises( + deep_acceptance.DeepAcceptanceError, + match="absolute artifact path", + ): + deep_acceptance.collect_completed_frame_files( + output, + output_dir=tmp_path, + expected_slot=1, + ) + + +def test_collect_completed_frame_files_refuses_a_busy_production_lock( + tmp_path: Path, +) -> None: + output, _ = _completed_receipt_fixture(tmp_path) + receipt_path = Path(output.receipt_path) + lock_path = deep_acceptance._receipt_lock_path(receipt_path) + descriptor = os.open(lock_path, os.O_RDWR) + try: + fcntl.flock(descriptor, fcntl.LOCK_EX | fcntl.LOCK_NB) + with pytest.raises( + deep_acceptance.DeepAcceptanceError, + match="busy in another process", + ): + deep_acceptance.collect_completed_frame_files( + output, + output_dir=tmp_path, + expected_slot=1, + ) + finally: + fcntl.flock(descriptor, fcntl.LOCK_UN) + os.close(descriptor) + + +def test_completed_frame_runs_full_production_replay_end_to_end( + fake_repair_engine: object, + tmp_path: Path, +) -> None: + del fake_repair_engine + runtime = _Runtime() + frame, expected_acquisition = _production_frame() + + class Engine: + def repair( + self, + acquisition: roll_repair.RepairAcquisition, + mode: roll_repair.RepairMode, + **kwargs: object, + ) -> roll_repair.RepairResult: + del kwargs + assert mode is roll_repair.RepairMode.HYBRID + assert acquisition.acquisition_id == expected_acquisition.acquisition_id + return _runtime_bound_hybrid_result(acquisition, runtime) + + roll_repair.register_engine(Engine()) + output = RollScanningService(hybrid_runtime=runtime).write_frame( + frame, + str(tmp_path), + 'acceptance_slot{{ "%02d" % seq }}', + write_unrepaired=True, + write_repaired=True, + write_positive=True, + repair_mode="hybrid", + positive_mode="nikon-exact", + ) + + cheap_files = deep_acceptance.collect_completed_frame_files( + output, + output_dir=tmp_path, + expected_slot=5, + ) + result = deep_acceptance.validate_completed_frame( + output, + output_dir=tmp_path, + expected_slot=5, + hybrid_runtime=runtime, + ) + + assert result["status"] == "passed" + assert result["slot"] == 5 + assert result["referenced_file_count"] == 23 + assert result["referenced_files"] == cheap_files + assert result["acquisition_id"] == expected_acquisition.acquisition_id + assert runtime.validated is True + + +def test_six_frame_batch_rejects_one_unreferenced_file( + monkeypatch: pytest.MonkeyPatch, + tmp_path: Path, +) -> None: + outputs, audits = _fake_audits(tmp_path) + iterator = iter(audits) + monkeypatch.setattr( + deep_acceptance, + "_validate_frame", + lambda *args, **kwargs: next(iterator), + ) + (tmp_path / "unexpected.txt").write_text("not part of the batch") + + with pytest.raises( + deep_acceptance.DeepAcceptanceError, + match="inventory file set changed", + ): + deep_acceptance.validate_six_frame_batch( + outputs, + output_dir=tmp_path, + builder=object(), + evaluator=object(), + hybrid_runtime=_Runtime(), + ) + + +def test_six_frame_batch_rejects_cross_frame_fingerprint_drift( + monkeypatch: pytest.MonkeyPatch, + tmp_path: Path, +) -> None: + outputs, audits = _fake_audits(tmp_path) + audits[3].ownership.fresh_fingerprint_sha256 = _digest("different-fresh") + iterator = iter(audits) + monkeypatch.setattr( + deep_acceptance, + "_validate_frame", + lambda *args, **kwargs: next(iterator), + ) + + with pytest.raises( + deep_acceptance.DeepAcceptanceError, + match="disagree on reservation, preview, transport, or fingerprint", + ): + deep_acceptance.validate_six_frame_batch( + outputs, + output_dir=tmp_path, + builder=object(), + evaluator=object(), + hybrid_runtime=_Runtime(), + ) + + +def test_six_frame_batch_binds_canonical_live_run_receipt( + monkeypatch: pytest.MonkeyPatch, + tmp_path: Path, +) -> None: + output_dir = tmp_path / "outputs" + output_dir.mkdir() + outputs, audits = _fake_audits(output_dir) + iterator = iter(audits) + monkeypatch.setattr( + deep_acceptance, + "_validate_frame", + lambda *args, **kwargs: next(iterator), + ) + contact_path = tmp_path / "reviewed-contact-sheet.png" + contact_path.write_bytes(b"pinned contact sheet") + contact_sha256 = hashlib.sha256(contact_path.read_bytes()).hexdigest() + approval_rows = [ + deep_acceptance._validate_manual_approval( + audits[slot - 1], + expected=True, + ) + for slot in sorted(deep_acceptance.APPROVED_SLOTS) + ] + review_document = { + "approvals": approval_rows, + "contact_sheet": { + "bytes": contact_path.stat().st_size, + "path": str(contact_path), + "sha256": contact_sha256, + }, + "preview_session": { + "bytes": 2, + "path": str(tmp_path / "preview-session.json"), + "sha256": hashlib.sha256(b"{}").hexdigest(), + }, + "review_basis": ("visual-inspection-of-six-frame-contact-sheet-and-canonical-restored-thumbnails"), + "reviewed_fingerprint_sha256": (audits[0].ownership.reviewed_fingerprint_sha256), + "schema": "negpy.ls5000-reviewed-approval.v1", + } + review_path = tmp_path / "reviewed-approval.json" + review_path.write_bytes( + deep_acceptance._canonical_json( + review_document, + newline=True, + ensure_ascii=True, + ) + ) + review_sha256 = hashlib.sha256(review_path.read_bytes()).hexdigest() + run_receipt = { + "approved_slots": [1, 6], + "close": { + "iterator": {"attempted": True, "succeeded": True}, + "roll": {"attempted": True, "succeeded": True}, + }, + "deep_acceptance": {"slots": [1, 2, 3, 4, 5, 6], "status": "passed"}, + "device_id": "usb:2:7", + "eject_requested": False, + "frames": [ + { + "artifacts": frame.output_artifacts, + "expected_slot": frame.summary["slot"], + "frame_receipt_sha256": frame.summary["frame_receipt"]["sha256"], + "slot": frame.summary["slot"], + } + for frame in audits + ], + "operation_state": { + "batch_exhausted": True, + "verified_slots": [1, 2, 3, 4, 5, 6], + }, + "output_dir": str(output_dir), + "output_lease": { + "acquired": True, + "release_attempted": True, + "released": True, + }, + "phase": "succeeded", + "reviewed_approval": { + "bytes": review_path.stat().st_size, + "contact_sheet": { + "path": str(contact_path), + "sha256": contact_sha256, + }, + "expected_sha256": review_sha256, + "path": str(review_path), + "reviewed_fingerprint_sha256": (audits[0].ownership.reviewed_fingerprint_sha256), + "verified_sha256": review_sha256, + }, + "retry_count": 0, + "schema": "negpy.ls5000-live-acceptance.v2", + "settings": { + "filename_pattern": 'acceptance_slot{{ "%02d" % seq }}', + "positive_mode": "nikon-exact", + "repair_mode": "hybrid", + "write_positive": True, + "write_repaired": True, + "write_unrepaired": True, + }, + "slots": [1, 2, 3, 4, 5, 6], + "status": "succeeded", + } + run_path = tmp_path / "run-receipt.json" + run_path.write_bytes( + deep_acceptance._canonical_json( + run_receipt, + newline=True, + ensure_ascii=True, + ) + ) + + result = deep_acceptance.validate_six_frame_batch( + outputs, + output_dir=output_dir, + run_receipt_path=run_path, + builder=object(), + evaluator=object(), + hybrid_runtime=_Runtime(), + ) + + assert result["run_receipt"]["path"] == str(run_path) + assert result["run_receipt"]["sha256"] == hashlib.sha256(run_path.read_bytes()).hexdigest() + + +def test_artifact_row_rejects_hash_drift(tmp_path: Path) -> None: + artifact = tmp_path / "artifact.bin" + artifact.write_bytes(b"bound bytes") + row = { + "path": str(artifact), + "bytes": artifact.stat().st_size, + "sha256": "0" * 64, + } + + with pytest.raises( + deep_acceptance.DeepAcceptanceError, + match="SHA-256 changed", + ): + deep_acceptance._artifact_row( + row, + root=tmp_path.resolve(), + label="test artifact", + ) + + +def test_regular_file_rejects_a_symlinked_parent(tmp_path: Path) -> None: + real = tmp_path / "real" + real.mkdir() + artifact = real / "artifact.bin" + artifact.write_bytes(b"payload") + alias = tmp_path / "alias" + os.symlink(real, alias) + + with pytest.raises( + deep_acceptance.DeepAcceptanceError, + match="symlink or non-directory parent", + ): + deep_acceptance._regular_file( + alias / artifact.name, + root=tmp_path.resolve(), + label="test artifact", + ) + + +def test_runtime_receipt_binding_rejects_retained_input_drift() -> None: + main = np.arange(24, dtype=np.uint16).reshape(2, 3, 4) + prepass = np.arange(16, dtype=np.uint16).reshape(2, 2, 4) + + def raw_digest(array: np.ndarray) -> str: + canonical = np.array(array, dtype=" None: + evidence = tmp_path / ".negpy-dice-acquisition" / "token" + evidence.mkdir(parents=True) + binding_path = evidence / "acquisition-binding.json" + rgb_path = tmp_path / "frame.tif" + ir_path = tmp_path / "frame_IR.tif" + rgb_path.write_bytes(b"rgb") + ir_path.write_bytes(b"ir") + + outer_artifacts: dict[str, dict[str, object]] = {} + bound_artifacts: dict[str, dict[str, object]] = {} + for key, filename, payload in ( + ("prepass_rgbi", "prepass.rgbi16.npy", b"prepass"), + ("ir_validity", "ir-validity.npy", b"validity"), + ): + path = evidence / filename + path.write_bytes(payload) + bound = { + "bytes": len(payload), + "dtype": " None: + """Only the external engine may be optional; broken NegPy code is fatal.""" + + target = "negpy.services.repair.fauxice_ir_repair" + real_import = builtins.__import__ + + def fail_internal_import(name, *args, **kwargs): + if name == target: + raise ImportError("sentinel internal packaging failure") + return real_import(name, *args, **kwargs) + + monkeypatch.setattr(builtins, "__import__", fail_internal_import) + path = Path(fauxice_bridge.__file__) + spec = importlib.util.spec_from_file_location( + "negpy.infrastructure.roll._fauxice_bridge_import_probe", + path, + ) + assert spec is not None and spec.loader is not None + probe = importlib.util.module_from_spec(spec) + + with pytest.raises(ImportError, match="sentinel internal packaging failure"): + spec.loader.exec_module(probe) + + +def _png(mask: np.ndarray) -> bytes: + stream = io.BytesIO() + Image.fromarray(mask.astype(np.uint8) * 255, mode="L").save(stream, format="PNG") + return stream.getvalue() + + +def _acquisition() -> roll_repair.RepairAcquisition: + main = np.arange(4 * 3 * 4, dtype=np.uint16).reshape(4, 3, 4) + prepass = np.arange(2 * 2 * 4, dtype=np.uint16).reshape(2, 2, 4) + validity = np.ones(main.shape[:2], dtype=np.bool_) + validity[0, -1] = False + return roll_repair.RepairAcquisition.from_arrays( + acquisition_id="dice-" + "1" * 64, + slot=9, + reservation_id="reservation-009", + capture_attempt_id="fine-slot-9-attempt-001", + storage_transform=roll_repair.DIGITAL_ICE_STORAGE_TRANSFORM, + evidence_sha256="2" * 64, + main_rgbi=main, + prepass_rgbi=prepass, + ir_validity=validity, + ) + + +def _runtime(tmp_path: Path) -> HybridRuntimeConfig: + return HybridRuntimeConfig( + hybrid_python=tmp_path / "hybrid" / "bin" / "python", + executable=tmp_path / "hybrid" / "bin" / "fauxce-hybrid", + core_source_manifest_sha256="1" * 64, + hybrid_source_manifest_sha256="2" * 64, + iopaint_python=tmp_path / "python", + iopaint_executable=tmp_path / "iopaint", + iopaint_source_manifest_sha256="3" * 64, + model_dir=tmp_path / "models", + model_weights=tmp_path / "models" / "big-lama.pt", + model_weights_sha256="4" * 64, + ) + + +def test_bridge_runs_hybrid_in_scanner_native_orientation_and_rotates_once(monkeypatch, tmp_path: Path) -> None: + acquisition = _acquisition() + runtime = _runtime(tmp_path) + native_repaired = np.ascontiguousarray( + np.where( + acquisition.ir_validity[..., None], + acquisition.main_rgbi[..., :3] + 19, + acquisition.main_rgbi[..., :3], + ) + ) + native_mask = np.zeros(acquisition.main_rgbi.shape[:2], dtype=np.bool_) + native_mask[1, 2] = True + native_mask[0, -1] = True # routed, but invalid IR means not applied + applied_mask = np.ascontiguousarray(native_mask & acquisition.ir_validity) + native_mask_png = _png(native_mask) + hybrid_receipt = b'{"schema":"fauxce-hybrid-receipt-v2"}' + calls = [] + + def fake_repair(rgb, ir, **kwargs): + calls.append((rgb, ir, kwargs)) + return FauxiceRepairResult( + status=RepairStatus.APPLIED, + reason="hybrid applied", + mode_requested=FauxiceMode.HYBRID, + mode_resolved=FauxiceMode.HYBRID, + repaired_rgb16=native_repaired, + engine_version="0.3.0", + backend_requested="auto", + backend_used="cpu-fast", + backend_selection_reason="parity self-test passed", + hybrid_mask_png=native_mask_png, + hybrid_mask_sha256=hashlib.sha256(native_mask_png).hexdigest(), + hybrid_mask=native_mask, + hybrid_synthesis_fraction=2 / native_mask.size, + hybrid_routing_counts={ + "final_regions": 1, + "synthesis_pixels": 2, + "frame_pixels": native_mask.size, + "at_floor_pixels": 2, + }, + hybrid_receipt=hybrid_receipt, + hybrid_receipt_sha256=hashlib.sha256(hybrid_receipt).hexdigest(), + hybrid_provenance_class="caller_asserted_bare_npy", + native_output_rgb_sha256=hashlib.sha256(native_repaired.astype(" None: + acquisition = _acquisition() + native_repaired = np.ascontiguousarray(acquisition.main_rgbi[..., :3] + 7) + + def fake_repair(rgb, ir, **kwargs): + assert kwargs["hybrid_runtime"] is None + return FauxiceRepairResult( + status=RepairStatus.APPLIED, + reason=("hybrid mode requested but no hybrid runtime is configured; degraded to exact repair"), + mode_requested=FauxiceMode.HYBRID, + mode_resolved=FauxiceMode.EXACT, + repaired_rgb16=native_repaired, + engine_version="0.3.0", + backend_requested="auto", + backend_used="cpu-fast", + backend_selection_reason="parity self-test passed", + native_output_rgb_sha256=hashlib.sha256(native_repaired.astype(" None: + self.validated = False + + def validate_files(self) -> None: + self.validated = True + + +@dataclasses.dataclass(frozen=True) +class _Approval: + payload: dict[str, object] + + def to_payload(self) -> dict[str, object]: + return dict(self.payload) + + +@dataclasses.dataclass(frozen=True) +class _AcceptanceFixture: + request: LiveAcceptanceRequest + review: ValidatedReviewedApproval + thumbnails: dict[int, np.ndarray] + + +def _json_sha256(document: object) -> str: + payload = json.dumps( + document, + sort_keys=True, + separators=(",", ":"), + ensure_ascii=False, + allow_nan=False, + ).encode("utf-8") + return hashlib.sha256(payload).hexdigest() + + +def _approval(slot: int, image: np.ndarray, fingerprint: str) -> _Approval: + payload: dict[str, object] = { + "boundary_offset_rows": 0, + "review_reasons": [f"slot-{slot}-edge-review"], + "reviewed_fingerprint_sha256": fingerprint, + "reviewed_lookup_row": (slot - 1) * 143, + "reviewed_native_origin": (slot - 1) * 5_959, + "schema_version": 1, + "slot": slot, + "thumbnail_sha256": thumbnail_sha256(image), + } + return _Approval({**payload, "binding_sha256": _json_sha256(payload)}) + + +def _fixture( + tmp_path: Path, + *, + confirm_live: bool = True, +) -> _AcceptanceFixture: + session_path = tmp_path / "preview-session.json" + session_path.write_text('{"version":1}', encoding="utf-8") + session_bytes = session_path.read_bytes() + + thumbnails = {slot: (np.arange(60, dtype=np.uint16).reshape(4, 5, 3) + np.uint16(slot * 1_000)) for slot in _SLOTS} + fingerprint = "c" * 64 + approvals = {slot: _approval(slot, thumbnails[slot], fingerprint) for slot in _APPROVED_SLOTS} + + contact_path = tmp_path / "reviewed-contact-sheet.png" + contact = np.concatenate( + tuple((thumbnails[slot] >> 8).astype(np.uint8) for slot in _SLOTS), + axis=1, + ) + Image.fromarray(contact).save(contact_path) + contact_bytes = contact_path.read_bytes() + + review_document = { + "approvals": [approvals[slot].to_payload() for slot in _APPROVED_SLOTS], + "contact_sheet": { + "bytes": len(contact_bytes), + "path": str(contact_path), + "sha256": hashlib.sha256(contact_bytes).hexdigest(), + }, + "preview_session": { + "bytes": len(session_bytes), + "path": str(session_path), + "sha256": hashlib.sha256(session_bytes).hexdigest(), + }, + "review_basis": REVIEW_BASIS, + "reviewed_fingerprint_sha256": fingerprint, + "schema": REVIEW_SCHEMA, + } + review_path = tmp_path / "reviewed-approval.json" + review_path.write_text( + json.dumps( + review_document, + sort_keys=True, + separators=(",", ":"), + ensure_ascii=False, + allow_nan=False, + ), + encoding="utf-8", + ) + review_bytes = review_path.read_bytes() + review = ValidatedReviewedApproval( + sha256=hashlib.sha256(review_bytes).hexdigest(), + byte_length=len(review_bytes), + reviewed_fingerprint_sha256=fingerprint, + contact_sheet_path=contact_path, + contact_sheet_sha256=hashlib.sha256(contact_bytes).hexdigest(), + approvals=approvals, + ) + + output_dir = tmp_path / "outputs" + output_dir.mkdir() + request = LiveAcceptanceRequest( + device_id="ls5000-usb-001", + preview_session_path=session_path, + preview_session_sha256=hashlib.sha256(session_bytes).hexdigest(), + reviewed_approval_path=review_path, + reviewed_approval_sha256=review.sha256, + output_dir=output_dir, + run_receipt_path=tmp_path / "run-receipt.json", + confirm_live=confirm_live, + ) + return _AcceptanceFixture( + request=request, + review=review, + thumbnails=thumbnails, + ) + + +class _Service: + def __init__( + self, + fixture: _AcceptanceFixture, + *, + restored_slots: tuple[int, ...] = _SLOTS, + approval_slots: tuple[int, ...] = _APPROVED_SLOTS, + wrong_approval_slot: int | None = None, + scan_error: Exception | None = None, + write_error_slot: int | None = None, + ) -> None: + self.output_dir = fixture.request.output_dir + self.thumbnails = fixture.thumbnails + self.approvals = fixture.review.approvals + self.restored_slots = restored_slots + self.approval_slots = approval_slots + self.wrong_approval_slot = wrong_approval_slot + self.scan_error = scan_error + self.write_error_slot = write_error_slot + self.calls: list[object] = [] + self.approved: set[int] = set() + self.closed = False + self.safe_stop_calls = 0 + self.eject_calls = 0 + + def open_roll(self, device_id: str) -> None: + self.calls.append(("open_roll", device_id)) + + def restore_preview_session(self, payload: str, slots=None): + self.calls.append(("restore_preview_session", payload, slots)) + return [ + SimpleNamespace( + slot=slot, + image=np.array( + self.thumbnails.get( + slot, + np.full((4, 5, 3), slot, dtype=np.uint16), + ), + copy=True, + ), + ) + for slot in self.restored_slots + ] + + def needs_approval(self, slot: int) -> bool: + return slot in self.approval_slots + + def approve(self, slot: int) -> object: + self.calls.append(("approve", slot)) + self.approved.add(slot) + approval = self.approvals[slot] + if slot != self.wrong_approval_slot: + return approval + wrong = approval.to_payload() + wrong["binding_sha256"] = "0" * 64 + return _Approval(wrong) + + def prepare_batch(self) -> None: + self.calls.append("prepare_batch") + + def scan_many(self, slots, *, on_progress=None): + ordered = tuple(slots) + self.calls.append(("scan_many", ordered)) + if self.scan_error is not None: + raise self.scan_error + for index, slot in enumerate(ordered, start=1): + if on_progress is not None: + on_progress( + SimpleNamespace( + stage="fine-scan", + slot=slot, + index=index, + total=len(ordered), + fraction=index / len(ordered), + message=f"slot {slot} complete", + ) + ) + yield SimpleNamespace(slot=slot) + + def write_frame( + self, + frame, + output_folder: str, + filename_pattern: str, + **kwargs, + ) -> RollFrameOutput: + self.calls.append(("write_frame", frame.slot, output_folder, filename_pattern, kwargs)) + on_repair_progress = kwargs.get("on_repair_progress") + if callable(on_repair_progress): + on_repair_progress(0.5) + if frame.slot == self.write_error_slot: + raise RuntimeError(f"write failed for slot {frame.slot}") + base = self.output_dir / f"acceptance_slot{frame.slot:02d}" + paths = { + "rgb_path": base.with_suffix(".tif"), + "ir_path": base.with_name(base.name + "_IR.tif"), + "repaired_rgb_path": base.with_name(base.name + "_repaired.tif"), + "repaired_ir_path": base.with_name(base.name + "_repaired_IR.tif"), + "positive_path": base.with_name(base.name + "_positive.tif"), + "synthesis_mask_path": base.with_name(base.name + "_repaired_SYNTH.png"), + "native_synthesis_mask_path": base.with_name(base.name + "_native_SYNTH.png"), + "hybrid_receipt_path": base.with_name(base.name + "_hybrid_receipt.json"), + } + for path in paths.values(): + path.write_bytes(b"artifact") + receipt_path = base.with_name(base.name + "_receipt.json") + receipt_path.write_text( + json.dumps( + { + "depth": 16, + "dpi": 4_000, + "slot": frame.slot, + "outputs": { + "unrepaired": {"written": True}, + "repair_acquisition_evidence": { + "retained": True, + "replayable": True, + }, + "native_color_evidence": {"retained": True}, + "repaired": { + "written": True, + "mode_requested": "hybrid", + "mode_resolved": "hybrid", + "degraded": False, + }, + "positive": { + "written": True, + "color_mode": "nikon-exact", + "exact_nikon_color": True, + "native_per_acquisition_builder": True, + "builder_validated": True, + "cms_verified": True, + }, + }, + } + ), + encoding="utf-8", + ) + return RollFrameOutput( + slot=frame.slot, + receipt_path=str(receipt_path), + **{name: str(path) for name, path in paths.items()}, + ) + + def close(self) -> None: + self.calls.append("close") + self.closed = True + + def safe_stop(self) -> None: + self.safe_stop_calls += 1 + + def eject(self) -> None: + self.eject_calls += 1 + + +class _ExplodingProgress: + @property + def stage(self) -> str: + raise RuntimeError("progress extraction exploded") + + +class _UnrepresentableProgressValue: + def __repr__(self) -> str: + raise RuntimeError("progress repr exploded") + + +class _PoisonTelemetryService(_Service): + def scan_many(self, slots, *, on_progress=None): + ordered = tuple(slots) + self.calls.append(("scan_many", ordered)) + for slot in ordered: + if on_progress is not None: + on_progress(_ExplodingProgress()) + yield SimpleNamespace(slot=slot) + + def write_frame( + self, + frame, + output_folder: str, + filename_pattern: str, + **kwargs, + ) -> RollFrameOutput: + callback = kwargs.get("on_repair_progress") + if callable(callback): + callback(_UnrepresentableProgressValue()) + without_callback = dict(kwargs) + without_callback["on_repair_progress"] = None + return super().write_frame( + frame, + output_folder, + filename_pattern, + **without_callback, + ) + + +def _output_paths(output: RollFrameOutput) -> list[Path]: + paths = [Path(getattr(output, field)) for field in _OUTPUT_FIELDS] + assert all(path.is_absolute() and path.is_file() for path in paths) + return sorted(paths) + + +class _ValidationHarness: + def __init__(self, service: _Service) -> None: + self.service = service + self.inventory_closed_states: list[bool] = [] + self.batch_closed_states: list[bool] = [] + + def collect( + self, + output: RollFrameOutput, + *, + output_dir: Path, + expected_slot: int, + ) -> list[str]: + assert output.slot == expected_slot + assert Path(output_dir) == self.service.output_dir.resolve() + self.inventory_closed_states.append(self.service.closed) + return [str(path) for path in _output_paths(output)] + + def validate_batch( + self, + outputs, + **kwargs, + ) -> dict[str, Any]: + self.batch_closed_states.append(self.service.closed) + assert self.service.closed is True + output_dir = Path(kwargs["output_dir"]) + assert output_dir == self.service.output_dir.resolve() + assert kwargs["allowed_output_lock_name"] == OUTPUT_LOCK_NAME + ordered = tuple(outputs) + assert tuple(output.slot for output in ordered) == _SLOTS + frames = [] + all_paths: set[Path] = set() + for output in ordered: + paths = _output_paths(output) + all_paths.update(paths) + receipt_path = Path(output.receipt_path) + receipt_bytes = receipt_path.read_bytes() + frames.append( + { + "slot": output.slot, + "frame_receipt": { + "path": str(receipt_path), + "bytes": len(receipt_bytes), + "sha256": hashlib.sha256(receipt_bytes).hexdigest(), + }, + "referenced_file_count": len(paths), + "referenced_files": [str(path) for path in paths], + } + ) + referenced = [str(path) for path in sorted(all_paths)] + approval_payloads = {slot: self.service.approvals[slot].to_payload() for slot in _APPROVED_SLOTS} + return { + "schema": "negpy.ls5000-deep-acceptance.v1", + "status": "passed", + "scope": "six-frame-batch", + "output_dir": str(output_dir), + "slots": list(_SLOTS), + "approved_slots": list(_APPROVED_SLOTS), + "device_id": "ls5000-usb-001", + "device_model": "Nikon LS-5000 ED 1.03", + "reviewed_fingerprint_sha256": approval_payloads[1]["reviewed_fingerprint_sha256"], + "manual_approval_bindings": [ + { + "slot": slot, + "binding_sha256": approval_payloads[slot]["binding_sha256"], + } + for slot in _APPROVED_SLOTS + ], + "frames": frames, + "referenced_files": referenced, + "inventory": { + "regular_file_count": len(referenced) + 1, + "visible_file_count": len(referenced), + "allowed_output_lock_path": str(output_dir / OUTPUT_LOCK_NAME), + "exact": True, + }, + } + + +def _review_loader(fixture: _AcceptanceFixture): + def load(request: LiveAcceptanceRequest, session) -> ValidatedReviewedApproval: + assert request.reviewed_approval_path == fixture.request.reviewed_approval_path + assert request.reviewed_approval_sha256 == fixture.review.sha256 + assert session.sha256 == fixture.request.preview_session_sha256 + return fixture.review + + return load + + +def _invoke( + fixture: _AcceptanceFixture, + service: _Service, + runtime: _Runtime, + **overrides, +) -> dict[str, Any]: + dependencies: dict[str, Any] = { + "service_factory": lambda *, hybrid_runtime: service, + "hybrid_runtime_loader": lambda _path: runtime, + "review_loader": _review_loader(fixture), + "emit": lambda _event: None, + } + dependencies.update(overrides) + return run_live_acceptance(fixture.request, **dependencies) + + +def _assert_no_recovery_or_eject( + service: _Service, + receipt: dict[str, Any], +) -> None: + assert service.safe_stop_calls == 0 + assert service.eject_calls == 0 + assert receipt["retry_count"] == 0 + assert receipt["eject_requested"] is False + + +def test_runs_one_six_slot_hybrid_nikon_exact_batch_and_records_truthful_state( + tmp_path: Path, +) -> None: + fixture = _fixture(tmp_path) + runtime = _Runtime() + service = _Service(fixture) + harness = _ValidationHarness(service) + events: list[dict[str, Any]] = [] + + receipt = _invoke( + fixture, + service, + runtime, + frame_inventory_collector=harness.collect, + batch_validator=harness.validate_batch, + emit=events.append, + ) + + assert receipt["status"] == "succeeded" + assert receipt["slots"] == list(_SLOTS) + assert receipt["approved_slots"] == list(_APPROVED_SLOTS) + assert [frame["slot"] for frame in receipt["frames"]] == list(_SLOTS) + assert all(frame["deep_acceptance"]["slot"] == frame["slot"] for frame in receipt["frames"]) + assert receipt["deep_acceptance"]["status"] == "passed" + assert runtime.validated is True + assert service.calls[:5] == [ + ("open_roll", "ls5000-usb-001"), + ("restore_preview_session", '{"version":1}', _SLOTS), + ("approve", 1), + ("approve", 6), + "prepare_batch", + ] + assert [call for call in service.calls if isinstance(call, tuple) and call[0] == "scan_many"] == [("scan_many", _SLOTS)] + writes = [call for call in service.calls if isinstance(call, tuple) and call[0] == "write_frame"] + assert [call[1] for call in writes] == list(_SLOTS) + assert all(call[4]["write_unrepaired"] is True for call in writes) + assert all(call[4]["write_repaired"] is True for call in writes) + assert all(call[4]["write_positive"] is True for call in writes) + assert all(call[4]["repair_mode"] == RepairMode.HYBRID.value for call in writes) + assert all(call[4]["positive_mode"] == PositiveColorMode.NIKON_EXACT.value for call in writes) + assert harness.inventory_closed_states == [False] * 6 + assert harness.batch_closed_states == [True] + assert service.closed is True + assert service.calls[-1] == "close" + state = receipt["operation_state"] + assert state == { + "phase": "succeeded", + "service_constructed": True, + "roll_opened": True, + "requested_slots": list(_SLOTS), + "restored_slots": list(_SLOTS), + "required_approval_slots": list(_APPROVED_SLOTS), + "approved_slots": list(_APPROVED_SLOTS), + "batch_prepared": True, + "yielded_slots": list(_SLOTS), + "committed_slots": list(_SLOTS), + "verified_slots": list(_SLOTS), + "processing_slot": None, + "batch_exhausted": True, + "last_progress": { + "event": "repair_progress", + "slot": 6, + "fraction": 0.5, + }, + "transport_may_have_advanced_beyond_yielded": False, + } + assert receipt["output_lease"]["released"] is True + assert not (fixture.request.output_dir / OUTPUT_LOCK_NAME).exists() + assert json.loads(fixture.request.run_receipt_path.read_text(encoding="utf-8")) == receipt + assert any(event["event"] == "scan_progress" for event in events) + _assert_no_recovery_or_eject(service, receipt) + + +def test_telemetry_callback_exceptions_never_abort_the_live_run( + tmp_path: Path, +) -> None: + fixture = _fixture(tmp_path) + runtime = _Runtime() + service = _Service(fixture) + harness = _ValidationHarness(service) + + def broken_emit(_event: dict[str, Any]) -> None: + raise RuntimeError("telemetry sink is unavailable") + + receipt = _invoke( + fixture, + service, + runtime, + frame_inventory_collector=harness.collect, + batch_validator=harness.validate_batch, + emit=broken_emit, + ) + + assert receipt["status"] == "succeeded" + assert len(receipt["telemetry_errors"]) == 16 + assert all(error["type"] == "RuntimeError" for error in receipt["telemetry_errors"]) + assert all(error["message"] == "telemetry sink is unavailable" for error in receipt["telemetry_errors"]) + _assert_no_recovery_or_eject(service, receipt) + + +def test_poison_progress_extraction_and_repr_never_abort_the_live_run( + tmp_path: Path, +) -> None: + fixture = _fixture(tmp_path) + runtime = _Runtime() + service = _PoisonTelemetryService(fixture) + harness = _ValidationHarness(service) + + receipt = _invoke( + fixture, + service, + runtime, + frame_inventory_collector=harness.collect, + batch_validator=harness.validate_batch, + ) + + assert receipt["status"] == "succeeded" + assert receipt["telemetry_error_count"] == 6 + assert all( + error + == { + "event": "scan_progress", + "type": "RuntimeError", + "message": "progress extraction exploded", + } + for error in receipt["telemetry_errors"] + ) + assert receipt["operation_state"]["last_progress"] == { + "event": "repair_progress", + "slot": 6, + "fraction": "", + } + _assert_no_recovery_or_eject(service, receipt) + + +@pytest.mark.parametrize( + ("field", "wrong_value"), + [ + ("device_id", "usb:another-scanner"), + ("device_model", "Nikon LS-4000 ED 1.10"), + ("reviewed_fingerprint_sha256", "d" * 64), + ( + "manual_approval_bindings", + [ + {"slot": 1, "binding_sha256": "0" * 64}, + {"slot": 6, "binding_sha256": "1" * 64}, + ], + ), + ], +) +def test_deep_batch_identity_mismatch_can_never_publish_success( + tmp_path: Path, + field: str, + wrong_value: object, +) -> None: + fixture = _fixture(tmp_path) + runtime = _Runtime() + service = _Service(fixture) + harness = _ValidationHarness(service) + + def mismatched_batch(outputs, **kwargs): + result = harness.validate_batch(outputs, **kwargs) + result[field] = wrong_value + return result + + with pytest.raises(LiveAcceptanceError, match="invalid result") as raised: + _invoke( + fixture, + service, + runtime, + frame_inventory_collector=harness.collect, + batch_validator=mismatched_batch, + ) + + assert raised.value.receipt["status"] == "failed" + assert raised.value.receipt["operation_state"]["verified_slots"] == [] + assert json.loads(fixture.request.run_receipt_path.read_text(encoding="utf-8"))["status"] == "failed" + _assert_no_recovery_or_eject(service, raised.value.receipt) + + +@pytest.mark.parametrize( + "trigger_event", + ["six_frame_batch_verified", "run_finished"], +) +def test_output_path_swap_during_finalization_overwrites_any_success_with_failure( + tmp_path: Path, + trigger_event: str, +) -> None: + fixture = _fixture(tmp_path) + runtime = _Runtime() + service = _Service(fixture) + harness = _ValidationHarness(service) + moved = tmp_path / "moved-outputs" + swapped = False + + def swapping_emit(event: dict[str, Any]) -> None: + nonlocal swapped + if event.get("event") != trigger_event or swapped: + return + swapped = True + fixture.request.output_dir.rename(moved) + fixture.request.output_dir.mkdir() + + with pytest.raises( + LiveAcceptanceError, + match="pathname no longer identifies", + ) as raised: + _invoke( + fixture, + service, + runtime, + frame_inventory_collector=harness.collect, + batch_validator=harness.validate_batch, + emit=swapping_emit, + ) + + assert swapped is True + assert raised.value.receipt["status"] == "failed" + assert json.loads(fixture.request.run_receipt_path.read_text(encoding="utf-8"))["status"] == "failed" + assert not (moved / OUTPUT_LOCK_NAME).exists() + assert list(fixture.request.output_dir.iterdir()) == [] + assert len(list(moved.iterdir())) == len(_SLOTS) * len(_OUTPUT_FIELDS) + _assert_no_recovery_or_eject(service, raised.value.receipt) + + +def _never_constructed_factory(calls: list[str]): + def factory(*, hybrid_runtime): + calls.append("constructed") + raise AssertionError("service must not be constructed") + + return factory + + +def test_missing_confirmation_fails_before_constructing_service( + tmp_path: Path, +) -> None: + fixture = _fixture(tmp_path, confirm_live=False) + factory_calls: list[str] = [] + + with pytest.raises(LiveAcceptanceError, match="--confirm-live is required") as raised: + run_live_acceptance( + fixture.request, + service_factory=_never_constructed_factory(factory_calls), + emit=lambda _event: None, + ) + + assert factory_calls == [] + assert raised.value.receipt["status"] == "failed" + assert raised.value.receipt["operation_state"]["roll_opened"] is False + assert not fixture.request.run_receipt_path.exists() + + +def test_session_hash_mismatch_fails_before_constructing_service( + tmp_path: Path, +) -> None: + fixture = _fixture(tmp_path) + request = dataclasses.replace( + fixture.request, + preview_session_sha256="0" * 64, + ) + factory_calls: list[str] = [] + + with pytest.raises(LiveAcceptanceError, match="SHA-256 mismatch"): + run_live_acceptance( + request, + service_factory=_never_constructed_factory(factory_calls), + emit=lambda _event: None, + ) + + assert factory_calls == [] + receipt = json.loads(request.run_receipt_path.read_text(encoding="utf-8")) + assert receipt["status"] == "failed" + assert receipt["operation_state"]["roll_opened"] is False + + +def test_missing_session_fails_before_constructing_service(tmp_path: Path) -> None: + fixture = _fixture(tmp_path) + fixture.request.preview_session_path.unlink() + factory_calls: list[str] = [] + + with pytest.raises(LiveAcceptanceError, match="preview session is missing"): + run_live_acceptance( + fixture.request, + service_factory=_never_constructed_factory(factory_calls), + emit=lambda _event: None, + ) + + assert factory_calls == [] + receipt = json.loads(fixture.request.run_receipt_path.read_text(encoding="utf-8")) + assert receipt["status"] == "failed" + assert receipt["operation_state"]["roll_opened"] is False + + +def test_preexisting_output_collision_is_preserved_without_opening_roll( + tmp_path: Path, +) -> None: + fixture = _fixture(tmp_path) + existing = fixture.request.output_dir / "existing.tif" + existing.write_bytes(b"do not overwrite") + factory_calls: list[str] = [] + + with pytest.raises(LiveAcceptanceError, match="unexpected files"): + run_live_acceptance( + fixture.request, + service_factory=_never_constructed_factory(factory_calls), + emit=lambda _event: None, + ) + + assert factory_calls == [] + assert existing.read_bytes() == b"do not overwrite" + assert sorted(path.name for path in fixture.request.output_dir.iterdir()) == ["existing.tif"] + receipt = json.loads(fixture.request.run_receipt_path.read_text(encoding="utf-8")) + assert receipt["status"] == "failed" + assert receipt["operation_state"]["roll_opened"] is False + + +def test_preexisting_run_receipt_is_never_overwritten_or_live_opened( + tmp_path: Path, +) -> None: + fixture = _fixture(tmp_path) + fixture.request.run_receipt_path.write_bytes(b"existing receipt") + before = fixture.request.run_receipt_path.stat() + factory_calls: list[str] = [] + + with pytest.raises(LiveAcceptanceError, match="already exists") as raised: + run_live_acceptance( + fixture.request, + service_factory=_never_constructed_factory(factory_calls), + emit=lambda _event: None, + ) + + after = fixture.request.run_receipt_path.stat() + assert factory_calls == [] + assert fixture.request.run_receipt_path.read_bytes() == b"existing receipt" + assert (after.st_dev, after.st_ino) == (before.st_dev, before.st_ino) + assert raised.value.receipt["operation_state"]["roll_opened"] is False + + +def test_wrong_restored_slots_fails_without_scanning_and_closes( + tmp_path: Path, +) -> None: + fixture = _fixture(tmp_path) + runtime = _Runtime() + service = _Service(fixture, restored_slots=(1, 2, 3, 4, 5, 7)) + + with pytest.raises(LiveAcceptanceError, match="restored slots") as raised: + _invoke(fixture, service, runtime) + + assert not any(isinstance(call, tuple) and call[0] == "scan_many" for call in service.calls) + assert service.closed is True + _assert_no_recovery_or_eject(service, raised.value.receipt) + + +def test_missing_edge_approval_fails_without_scanning_and_closes( + tmp_path: Path, +) -> None: + fixture = _fixture(tmp_path) + runtime = _Runtime() + service = _Service(fixture, approval_slots=(1,)) + + with pytest.raises(LiveAcceptanceError, match="approval set") as raised: + _invoke(fixture, service, runtime) + + assert not any(isinstance(call, tuple) and call[0] == "scan_many" for call in service.calls) + assert service.closed is True + _assert_no_recovery_or_eject(service, raised.value.receipt) + + +def test_wrong_service_approval_return_fails_before_scan_with_truthful_partial_state( + tmp_path: Path, +) -> None: + fixture = _fixture(tmp_path) + runtime = _Runtime() + service = _Service(fixture, wrong_approval_slot=6) + + with pytest.raises(LiveAcceptanceError, match="differs from reviewed evidence") as raised: + _invoke(fixture, service, runtime) + + assert [call for call in service.calls if isinstance(call, tuple) and call[0] == "approve"] == [("approve", 1), ("approve", 6)] + assert "prepare_batch" not in service.calls + assert not any(isinstance(call, tuple) and call[0] == "scan_many" for call in service.calls) + state = raised.value.receipt["operation_state"] + assert state["required_approval_slots"] == list(_APPROVED_SLOTS) + assert state["approved_slots"] == [1] + assert state["yielded_slots"] == [] + assert service.closed is True + _assert_no_recovery_or_eject(service, raised.value.receipt) + + +def test_write_failure_records_yielded_committed_verified_and_transport_state( + tmp_path: Path, +) -> None: + fixture = _fixture(tmp_path) + runtime = _Runtime() + service = _Service(fixture, write_error_slot=3) + harness = _ValidationHarness(service) + + def batch_must_not_run(*_args, **_kwargs): + raise AssertionError("deep batch validation must not run after write failure") + + with pytest.raises(LiveAcceptanceError, match="write failed for slot 3") as raised: + _invoke( + fixture, + service, + runtime, + frame_inventory_collector=harness.collect, + batch_validator=batch_must_not_run, + ) + + receipt = raised.value.receipt + state = receipt["operation_state"] + assert state["yielded_slots"] == [1, 2, 3] + assert state["committed_slots"] == [1, 2] + assert state["verified_slots"] == [] + assert state["processing_slot"] == 3 + assert state["batch_exhausted"] is False + assert state["transport_may_have_advanced_beyond_yielded"] is True + assert [frame["slot"] for frame in receipt["frames"]] == [1, 2] + assert harness.inventory_closed_states == [False, False] + assert harness.batch_closed_states == [] + assert service.closed is True + assert json.loads(fixture.request.run_receipt_path.read_text(encoding="utf-8")) == receipt + _assert_no_recovery_or_eject(service, receipt) + + +def test_scan_exception_closes_without_retry_safe_stop_or_eject( + tmp_path: Path, +) -> None: + fixture = _fixture(tmp_path) + runtime = _Runtime() + service = _Service(fixture, scan_error=RuntimeError("scan failed")) + harness = _ValidationHarness(service) + + with pytest.raises(LiveAcceptanceError, match="scan failed") as raised: + _invoke( + fixture, + service, + runtime, + frame_inventory_collector=harness.collect, + batch_validator=harness.validate_batch, + ) + + state = raised.value.receipt["operation_state"] + assert state["yielded_slots"] == [] + assert state["committed_slots"] == [] + assert state["verified_slots"] == [] + assert state["batch_exhausted"] is False + assert state["transport_may_have_advanced_beyond_yielded"] is True + assert service.closed is True + _assert_no_recovery_or_eject(service, raised.value.receipt) + + +def test_default_deep_batch_validator_rejects_literal_fake_artifacts_after_close( + tmp_path: Path, +) -> None: + fixture = _fixture(tmp_path) + runtime = _Runtime() + service = _Service(fixture) + harness = _ValidationHarness(service) + + with pytest.raises(LiveAcceptanceError) as raised: + _invoke( + fixture, + service, + runtime, + frame_inventory_collector=harness.collect, + ) + + receipt = raised.value.receipt + state = receipt["operation_state"] + assert service.closed is True + assert state["yielded_slots"] == list(_SLOTS) + assert state["committed_slots"] == list(_SLOTS) + assert state["verified_slots"] == [] + assert state["batch_exhausted"] is True + assert state["transport_may_have_advanced_beyond_yielded"] is False + assert receipt["deep_acceptance"] is None + assert receipt["error"]["type"] == "DeepAcceptanceError" + _assert_no_recovery_or_eject(service, receipt) + + +def test_injected_deep_batch_failure_fails_only_after_scanner_close( + tmp_path: Path, +) -> None: + fixture = _fixture(tmp_path) + runtime = _Runtime() + service = _Service(fixture) + harness = _ValidationHarness(service) + close_states: list[bool] = [] + + def reject_batch(_outputs, **_kwargs): + close_states.append(service.closed) + raise RuntimeError("deep six-frame audit rejected") + + with pytest.raises( + LiveAcceptanceError, + match="deep six-frame audit rejected", + ) as raised: + _invoke( + fixture, + service, + runtime, + frame_inventory_collector=harness.collect, + batch_validator=reject_batch, + ) + + assert close_states == [True] + state = raised.value.receipt["operation_state"] + assert state["yielded_slots"] == list(_SLOTS) + assert state["committed_slots"] == list(_SLOTS) + assert state["verified_slots"] == [] + assert state["batch_exhausted"] is True + assert state["transport_may_have_advanced_beyond_yielded"] is False + assert service.closed is True + _assert_no_recovery_or_eject(service, raised.value.receipt) diff --git a/tests/roll/test_live_reservation.py b/tests/roll/test_live_reservation.py new file mode 100644 index 00000000..703dcee4 --- /dev/null +++ b/tests/roll/test_live_reservation.py @@ -0,0 +1,357 @@ +from __future__ import annotations + +import json +import os +import stat +from pathlib import Path + +import pytest + +from negpy.services.roll import live_reservation +from negpy.services.roll.live_reservation import ( + OUTPUT_LOCK_NAME, + ExclusiveReceiptReservation, + FixedOutputLease, + InventoryConflict, + LiveReservationError, + OwnershipLost, + ReservationConflict, +) + + +def _in_progress() -> dict[str, object]: + return { + "schema": "negpy.test-live-reservation.v1", + "status": "in_progress", + } + + +def _lock_document() -> dict[str, object]: + return { + "reservation_id": "0123456789abcdef", + "run_receipt": "/outside/output/run-receipt.json", + } + + +def test_receipt_is_exclusive_durable_canonical_and_finalized_in_place( + tmp_path: Path, +) -> None: + path = tmp_path / "run-receipt.json" + reservation = ExclusiveReceiptReservation.reserve(path, _in_progress()) + try: + initial_inode = reservation.inode + assert path.read_bytes() == (b'{"schema":"negpy.test-live-reservation.v1","status":"in_progress"}\n') + + final = { + "schema": "negpy.test-live-reservation.v1", + "status": "succeeded", + "frames": [1, 2, 3, 4, 5, 6], + } + reservation.publish(final) + + assert reservation.inode == initial_inode + assert (path.stat().st_dev, path.stat().st_ino) == initial_inode + assert json.loads(path.read_bytes()) == final + finally: + reservation.close() + + +def test_preexisting_receipt_is_preserved_and_never_opened_for_writing( + tmp_path: Path, +) -> None: + path = tmp_path / "run-receipt.json" + path.write_bytes(b"existing receipt") + before = path.stat() + + with pytest.raises(ReservationConflict, match="already exists"): + ExclusiveReceiptReservation.reserve(path, _in_progress()) + + after = path.stat() + assert path.read_bytes() == b"existing receipt" + assert (after.st_dev, after.st_ino) == (before.st_dev, before.st_ino) + + +def test_receipt_reservation_requires_in_progress_status(tmp_path: Path) -> None: + path = tmp_path / "run-receipt.json" + + with pytest.raises(LiveReservationError, match="exactly 'in_progress'"): + ExclusiveReceiptReservation.reserve(path, {"status": "succeeded"}) + + assert not path.exists() + + +def test_receipt_publication_refuses_replaced_path_and_preserves_collision( + tmp_path: Path, +) -> None: + path = tmp_path / "run-receipt.json" + reservation = ExclusiveReceiptReservation.reserve(path, _in_progress()) + path.unlink() + path.write_bytes(b"collision") + + try: + with pytest.raises(OwnershipLost, match="no longer identifies"): + reservation.publish({"status": "succeeded"}) + assert path.read_bytes() == b"collision" + finally: + reservation.close() + + +def test_receipt_publication_refuses_an_added_hard_link(tmp_path: Path) -> None: + path = tmp_path / "run-receipt.json" + linked = tmp_path / "unexpected-hard-link.json" + reservation = ExclusiveReceiptReservation.reserve(path, _in_progress()) + os.link(path, linked) + + try: + with pytest.raises(OwnershipLost, match="no longer identifies"): + reservation.publish({"status": "succeeded"}) + assert json.loads(path.read_bytes()) == _in_progress() + assert linked.read_bytes() == path.read_bytes() + finally: + reservation.close() + + +def test_oversized_final_receipt_is_rejected_before_mutating_reservation( + tmp_path: Path, +) -> None: + path = tmp_path / "run-receipt.json" + reservation = ExclusiveReceiptReservation.reserve( + path, + {"status": "in_progress"}, + maximum_bytes=64, + ) + before = path.read_bytes() + + try: + with pytest.raises(LiveReservationError, match="size limit"): + reservation.publish({"status": "failed", "error": "x" * 100}) + assert path.read_bytes() == before + finally: + reservation.close() + + +def test_fixed_output_lease_is_exclusive_visible_and_removed_on_release( + tmp_path: Path, +) -> None: + output = tmp_path / "outputs" + output.mkdir() + + lease = FixedOutputLease.acquire(output, _lock_document()) + assert lease.lock_path == output / OUTPUT_LOCK_NAME + assert json.loads(lease.lock_path.read_bytes()) == _lock_document() + assert sorted(path.name for path in output.iterdir()) == [OUTPUT_LOCK_NAME] + assert lease.assert_inventory(()).directories == () + + lease.release() + + assert lease.released is True + assert list(output.iterdir()) == [] + + +def test_preexisting_fixed_lock_is_preserved(tmp_path: Path) -> None: + output = tmp_path / "outputs" + output.mkdir() + lock = output / OUTPUT_LOCK_NAME + lock.write_bytes(b"existing lock") + before = lock.stat() + + with pytest.raises(ReservationConflict, match="already reserved"): + FixedOutputLease.acquire(output, _lock_document()) + + after = lock.stat() + assert lock.read_bytes() == b"existing lock" + assert (after.st_dev, after.st_ino) == (before.st_dev, before.st_ino) + + +def test_nonempty_output_fails_and_removes_only_its_new_lock(tmp_path: Path) -> None: + output = tmp_path / "outputs" + output.mkdir() + existing = output / "existing.tif" + existing.write_bytes(b"do not overwrite") + + with pytest.raises(InventoryConflict, match="unexpected files: existing.tif"): + FixedOutputLease.acquire(output, _lock_document()) + + assert existing.read_bytes() == b"do not overwrite" + assert not (output / OUTPUT_LOCK_NAME).exists() + + +def test_inventory_allows_only_declared_files_and_implied_directories( + tmp_path: Path, +) -> None: + output = tmp_path / "outputs" + output.mkdir() + lease = FixedOutputLease.acquire(output, _lock_document()) + evidence_dir = output / ".negpy-native-builder" / "receipt-a" + evidence_dir.mkdir(parents=True) + first = evidence_dir / "evidence.json" + first.write_bytes(b"one") + + try: + first_snapshot = lease.assert_inventory([first]) + assert first_snapshot.directories == ( + ".negpy-native-builder", + os.path.join(".negpy-native-builder", "receipt-a"), + ) + + second = output / "acceptance_slot01.tif" + second.write_bytes(b"two") + second_snapshot = lease.assert_inventory( + [first, second], + previous=first_snapshot, + ) + assert dict(second_snapshot.files)[os.fspath(second.relative_to(output))].size == 3 + + rogue = output / "acceptance_slot02.tif" + rogue.write_bytes(b"collision") + with pytest.raises(InventoryConflict, match="unexpected files: acceptance_slot02.tif"): + lease.assert_inventory([first, second], previous=second_snapshot) + rogue.unlink() + finally: + lease.release() + + +def test_inventory_detects_mutation_of_previously_owned_file(tmp_path: Path) -> None: + output = tmp_path / "outputs" + output.mkdir() + lease = FixedOutputLease.acquire(output, _lock_document()) + artifact = output / "acceptance_slot01.tif" + artifact.write_bytes(b"before") + snapshot = lease.assert_inventory([artifact]) + + try: + artifact.write_bytes(b"after!") + with pytest.raises(InventoryConflict, match="previously owned output changed"): + lease.assert_inventory([artifact], previous=snapshot) + finally: + lease.release() + + +def test_inventory_rejects_symlink_and_owned_path_escape(tmp_path: Path) -> None: + output = tmp_path / "outputs" + output.mkdir() + outside = tmp_path / "outside.tif" + outside.write_bytes(b"outside") + lease = FixedOutputLease.acquire(output, _lock_document()) + symlink = output / "linked.tif" + symlink.symlink_to(outside) + + try: + with pytest.raises(InventoryConflict, match="symbolic link"): + lease.assert_inventory([symlink]) + symlink.unlink() + with pytest.raises(InventoryConflict, match="escapes"): + lease.assert_inventory([outside]) + finally: + lease.release() + + +def test_output_path_replacement_is_detected_but_original_lock_is_released( + tmp_path: Path, +) -> None: + output = tmp_path / "outputs" + moved = tmp_path / "moved-outputs" + output.mkdir() + lease = FixedOutputLease.acquire(output, _lock_document()) + output.rename(moved) + output.mkdir() + + with pytest.raises(OwnershipLost, match="pathname no longer identifies"): + lease.assert_owned() + + lease.release() + + assert not (moved / OUTPUT_LOCK_NAME).exists() + assert list(output.iterdir()) == [] + + +def test_release_refuses_to_unlink_replacement_lock(tmp_path: Path) -> None: + output = tmp_path / "outputs" + output.mkdir() + lease = FixedOutputLease.acquire(output, _lock_document()) + lease.lock_path.unlink() + lease.lock_path.write_bytes(b"replacement lock") + + with pytest.raises(OwnershipLost, match="no longer exclusively owned"): + lease.release() + + assert lease.released is True + assert lease.lock_path.read_bytes() == b"replacement lock" + + +def test_verified_release_checks_inventory_around_final_publication( + tmp_path: Path, +) -> None: + output = tmp_path / "outputs" + output.mkdir() + artifact = output / "acceptance_slot01.tif" + lease = FixedOutputLease.acquire(output, _lock_document()) + artifact.write_bytes(b"stable") + snapshot = lease.assert_inventory([artifact]) + finalized: list[bool] = [] + + result = lease.release_verified( + [artifact], + previous=snapshot, + finalize=lambda: finalized.append(True), + ) + + assert finalized == [True] + assert lease.released is True + assert [name for name, _identity in result.files] == [artifact.name] + assert sorted(path.name for path in output.iterdir()) == [artifact.name] + + +def test_verified_release_rejects_output_path_swap_during_final_publication( + tmp_path: Path, +) -> None: + output = tmp_path / "outputs" + moved = tmp_path / "moved-outputs" + output.mkdir() + artifact = output / "acceptance_slot01.tif" + lease = FixedOutputLease.acquire(output, _lock_document()) + artifact.write_bytes(b"stable") + snapshot = lease.assert_inventory([artifact]) + + def replace_output_path() -> None: + output.rename(moved) + output.mkdir() + + with pytest.raises(OwnershipLost, match="pathname no longer identifies"): + lease.release_verified( + [artifact], + previous=snapshot, + finalize=replace_output_path, + ) + + assert lease.released is True + assert not (moved / OUTPUT_LOCK_NAME).exists() + assert (moved / artifact.name).read_bytes() == b"stable" + assert list(output.iterdir()) == [] + + +def test_receipt_lock_and_directories_are_fsynced( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + real_fsync = os.fsync + synced_modes: list[int] = [] + + def recording_fsync(descriptor: int) -> None: + synced_modes.append(stat.S_IFMT(os.fstat(descriptor).st_mode)) + real_fsync(descriptor) + + monkeypatch.setattr(live_reservation.os, "fsync", recording_fsync) + receipt = ExclusiveReceiptReservation.reserve( + tmp_path / "run-receipt.json", + _in_progress(), + ) + receipt.publish({"status": "succeeded"}) + receipt.close() + output = tmp_path / "outputs" + output.mkdir() + lease = FixedOutputLease.acquire(output, _lock_document()) + lease.release() + + assert synced_modes.count(stat.S_IFREG) >= 3 + assert synced_modes.count(stat.S_IFDIR) >= 4 diff --git a/tests/roll/test_live_review.py b/tests/roll/test_live_review.py new file mode 100644 index 00000000..479a75ad --- /dev/null +++ b/tests/roll/test_live_review.py @@ -0,0 +1,201 @@ +from __future__ import annotations + +import hashlib +import json +from pathlib import Path +from types import SimpleNamespace + +import numpy as np +import pytest +from PIL import Image + +from negpy.services.roll.live_review import ( + REVIEW_BASIS, + SCHEMA, + load_reviewed_approval, + thumbnail_sha256, + validate_restored_thumbnails, +) + + +class _Approval: + def __init__(self, payload: dict) -> None: + self._payload = dict(payload) + + def to_payload(self) -> dict: + return dict(self._payload) + + +class _Session: + def __init__(self, fingerprint: str, approvals: dict[int, dict]) -> None: + self._fingerprint = fingerprint + self._approvals = approvals + + def reviewed_fingerprint(self): + return SimpleNamespace(binding_sha256=self._fingerprint) + + def approve_manual_origin(self, slot: int, offset: int): + assert self._approvals[slot]["boundary_offset_rows"] == offset + return _Approval(self._approvals[slot]) + + +def _approval(slot: int, thumbnail: str, fingerprint: str) -> dict: + payload = { + "boundary_offset_rows": 0, + "review_reasons": [f"slot-{slot}-edge-review"], + "reviewed_fingerprint_sha256": fingerprint, + "reviewed_lookup_row": slot, + "reviewed_native_origin": slot * 100, + "schema_version": 1, + "slot": slot, + "thumbnail_sha256": thumbnail, + } + encoded = json.dumps( + payload, + sort_keys=True, + separators=(",", ":"), + ensure_ascii=False, + allow_nan=False, + ).encode() + return {**payload, "binding_sha256": hashlib.sha256(encoded).hexdigest()} + + +def _fixture(tmp_path: Path): + fingerprint = "c" * 64 + images = { + slot: np.full((2, 3, 3), slot, dtype=np.uint16) + for slot in range(1, 7) + } + approvals = { + slot: _approval(slot, thumbnail_sha256(images[slot]), fingerprint) + for slot in (1, 6) + } + session_payload = '{"version":1}' + session_path = tmp_path / "session.json" + session_path.write_text(session_payload, encoding="utf-8") + session_sha = hashlib.sha256(session_payload.encode()).hexdigest() + contact_path = tmp_path / "contact.png" + Image.new("RGB", (6, 2), "white").save(contact_path) + contact_bytes = contact_path.read_bytes() + document = { + "approvals": [approvals[1], approvals[6]], + "contact_sheet": { + "bytes": len(contact_bytes), + "path": str(contact_path), + "sha256": hashlib.sha256(contact_bytes).hexdigest(), + }, + "preview_session": { + "bytes": len(session_payload.encode()), + "path": str(session_path), + "sha256": session_sha, + }, + "review_basis": REVIEW_BASIS, + "reviewed_fingerprint_sha256": fingerprint, + "schema": SCHEMA, + } + review_path = tmp_path / "review.json" + review_path.write_text(json.dumps(document), encoding="utf-8") + review_sha = hashlib.sha256(review_path.read_bytes()).hexdigest() + session = _Session(fingerprint, approvals) + return ( + review_path, + review_sha, + session_path, + session_payload, + session_sha, + session, + images, + document, + ) + + +def _load(values): + review_path, review_sha, session_path, payload, session_sha, session, *_ = values + return load_reviewed_approval( + review_path, + review_sha, + preview_session_path=session_path, + preview_session_payload=payload, + preview_session_sha256=session_sha, + session_loader=lambda _payload: session, + approval_parser=lambda row: _Approval(row), + ) + + +def test_loads_pinned_review_and_rederives_both_approvals(tmp_path: Path) -> None: + values = _fixture(tmp_path) + reviewed = _load(values) + + assert reviewed.reviewed_fingerprint_sha256 == "c" * 64 + assert tuple(reviewed.approvals) == (1, 6) + + images = values[6] + thumbnails = [SimpleNamespace(slot=slot, image=images[slot]) for slot in range(1, 7)] + validate_restored_thumbnails(thumbnails, reviewed) + + +def test_wrong_review_hash_fails_before_parsing(tmp_path: Path) -> None: + values = list(_fixture(tmp_path)) + values[1] = "0" * 64 + + with pytest.raises(ValueError, match="SHA-256 mismatch"): + _load(tuple(values)) + + +@pytest.mark.parametrize( + ("field", "value", "message"), + [ + ("schema", "wrong", "schema or review basis"), + ("reviewed_fingerprint_sha256", "d" * 64, "fingerprint"), + ], +) +def test_review_identity_changes_fail_closed( + tmp_path: Path, + field: str, + value: str, + message: str, +) -> None: + values = list(_fixture(tmp_path)) + document = dict(values[7]) + document[field] = value + values[0].write_text(json.dumps(document), encoding="utf-8") + values[1] = hashlib.sha256(values[0].read_bytes()).hexdigest() + + with pytest.raises(ValueError, match=message): + _load(tuple(values)) + + +def test_changed_approval_is_not_reaccepted_under_a_new_file_hash(tmp_path: Path) -> None: + values = list(_fixture(tmp_path)) + document = dict(values[7]) + rows = [dict(row) for row in document["approvals"]] + rows[0]["thumbnail_sha256"] = "e" * 64 + document["approvals"] = rows + values[0].write_text(json.dumps(document), encoding="utf-8") + values[1] = hashlib.sha256(values[0].read_bytes()).hexdigest() + + with pytest.raises(ValueError, match="does not match its thumbnail"): + _load(tuple(values)) + + +def test_restored_thumbnail_change_fails(tmp_path: Path) -> None: + values = _fixture(tmp_path) + reviewed = _load(values) + images = dict(values[6]) + images[1] = images[1].copy() + images[1][0, 0, 0] += 1 + thumbnails = [SimpleNamespace(slot=slot, image=images[slot]) for slot in range(1, 7)] + + with pytest.raises(ValueError, match="slot 1 changed"): + validate_restored_thumbnails(thumbnails, reviewed) + + +def test_review_mapping_is_immutable(tmp_path: Path) -> None: + reviewed = _load(_fixture(tmp_path)) + with pytest.raises(TypeError): + reviewed.approvals[1] = object() # type: ignore[index] + + +def test_thumbnail_validator_rejects_non_rgb16() -> None: + with pytest.raises(ValueError, match="HxWx3 uint16"): + thumbnail_sha256(np.zeros((2, 2), dtype=np.uint8)) diff --git a/tests/roll/test_native_builder.py b/tests/roll/test_native_builder.py new file mode 100644 index 00000000..a17bb1ee --- /dev/null +++ b/tests/roll/test_native_builder.py @@ -0,0 +1,1038 @@ +"""Native Nikon Stage-1 builder tests; no scanner or VM required.""" + +from __future__ import annotations + +import json +import os +import struct +import types +from dataclasses import dataclass, replace +from hashlib import sha256 +from pathlib import Path + +import numpy as np +import pytest + +from negpy.infrastructure.roll import repair as roll_repair +from negpy.services.roll import exact_color +from negpy.services.roll import service as roll_service +from negpy.services.roll.native_builder import ( + NativeBuilderEvidence, + adapt_native_builder_evidence, + build_native_builder_receipt, +) +from negpy.services.roll.portable_builder import PortableStage1Builder +from negpy.services.roll.portable_cms import PortableCMSOnEvaluator +from negpy.services.roll.service import RollScanningService + + +CAPTURE_D = Path( + os.environ.get( + "CAPTURE_D", + "/Volumes/isos/NikonRE/session20260719/capture-d", + ) +) +CAPTURE_D_PREF_SHA256 = ( + "46a0d68ae20c72088e64a1144a0d38bf692f15f506539bbe94eb563fe437c976", + "23eda81294817e7a2a31f1488544a6f8d3e7ac817f22d43c8a39882565c34b95", + "3cfc61c06bac49c4c28e69afe99af01366fae6bf5ea88954f688592a8e2756bb", +) +DENSITY_ALGORITHM_ID = "ls5000-md3-10088810-layout1-u16-proven-inputs-macos-binary64-exact-v6" +FRAME_OWNERSHIP_STATUS = "proven-exact-reservation-preview-registration-and-transport" +RESERVATION_ID = "reservation-test-001" +PREVIEW_SHA256 = sha256(b"test-preview").hexdigest() +PREVIEW_IDENTITY_SHA256 = sha256(b"test-preview-identity").hexdigest() +TRANSPORT_TABLE_SHA256 = sha256(b"test-transport-table").hexdigest() +REVIEWED_FINGERPRINT_SHA256 = sha256(b"test-reviewed-registration").hexdigest() +FRESH_FINGERPRINT_SHA256 = sha256(b"test-fresh-registration").hexdigest() +DENSITY_WIRE_SHA256 = PREVIEW_SHA256 +DENSITY_CHILD_SHA256 = sha256(b"test-only-capture-d-density-child").hexdigest() + + +def _canonical_json(payload: dict[str, object]) -> bytes: + return json.dumps(payload, sort_keys=True, separators=(",", ":"), allow_nan=False).encode() + + +def _ownership_document() -> dict[str, object]: + material = { + "reservation_id": RESERVATION_ID, + "batch_session_id": RESERVATION_ID, + "preview_sha256": PREVIEW_SHA256, + "preview_identity_sha256": PREVIEW_IDENTITY_SHA256, + "transport_table_sha256": TRANSPORT_TABLE_SHA256, + "reviewed_fingerprint_sha256": REVIEWED_FINGERPRINT_SHA256, + "fresh_fingerprint_sha256": FRESH_FINGERPRINT_SHA256, + "selected_slots": [5], + } + return { + "schema_version": 1, + "scope": "reservation-preview-frame", + "binding_status": FRAME_OWNERSHIP_STATUS, + "session_reservation_retained": True, + **material, + "transport_identity_sha256": sha256(_canonical_json(material)).hexdigest(), + "frame_capture_attempt_id": "capture-d", + "frame_index": 1, + "frame_total": 1, + "selected_slot": 5, + } + + +def _density_document( + *, + numerators: tuple[int, int, int] = (57_114, 48_036, 32_683), + density_f03: tuple[int, int, int] = (70_307, 136_614, 125_470), + densities: tuple[float, float, float] = ( + struct.unpack(">d", bytes.fromhex("3fd8b159777b9d5f"))[0], + struct.unpack(">d", bytes.fromhex("3fe9cc75f7f6705a"))[0], + struct.unpack(">d", bytes.fromhex("3ff0b0dae0533338"))[0], + ), +) -> dict[str, object]: + binding_identity = { + "session_id": RESERVATION_ID, + "capture_attempt_id": "capture-d", + "scan_identity": "capture-d-slot", + } + return { + "schema_version": 1, + "scope": "reservation-preview", + "per_frame_binding_status": "requires-explicit-frame-ownership-receipt", + "preview_identity_sha256": PREVIEW_IDENTITY_SHA256, + "source_payload_bytes": 6_250_496, + "calibration_binding": { + "calibration": { + "session_id": RESERVATION_ID, + "numerators_rgb": list(numerators), + }, + "capture_attempt_id": binding_identity["capture_attempt_id"], + "scan_identity": binding_identity["scan_identity"], + }, + "source_binding": { + **binding_identity, + "resolution_dpi": 97, + "wire_sha256": DENSITY_WIRE_SHA256, + "child_buffer_sha256": DENSITY_CHILD_SHA256, + }, + "exposure_binding": { + **binding_identity, + "density_f03_exposures_raw_10ns_rgb": list(density_f03), + }, + "result": { + **binding_identity, + "algorithm_id": DENSITY_ALGORITHM_ID, + "promotable": True, + "source_wire_sha256": DENSITY_WIRE_SHA256, + "source_child_buffer_sha256": DENSITY_CHILD_SHA256, + "numerators_rgb": list(numerators), + "density_f03_denominators_raw_10ns_rgb": list(density_f03), + "densities_rgb": list(densities), + "density_binary64_be_hex_rgb": [struct.pack(">d", value).hex() for value in densities], + }, + } + + +@dataclass(frozen=True) +class _PayloadReceipt: + payload: dict[str, object] + + def to_dict(self) -> dict[str, object]: + return self.payload + + +@dataclass(frozen=True) +class _RejectingOwnershipReceipt(_PayloadReceipt): + def validate_evidence(self, evidence: object) -> None: + del evidence + raise ValueError("preview identity changed") + + +@dataclass(frozen=True) +class _FrameReceipt: + version: int + slot: int + nikon_density_ownership: object | None + + +def _bound_repair_frame_fields(*, slot: int = 5) -> dict[str, object]: + """Create one valid, immutable scanner-native repair acquisition.""" + + storage_rgb = np.full((2, 3, 3), 12_000, dtype=np.uint16) + storage_ir = np.full((2, 3), 3_000, dtype=np.uint16) + storage_validity = np.ones(storage_rgb.shape[:2], dtype=np.bool_) + meter_rgbi = np.zeros((1, 1, 4), dtype=np.uint16) + native_rgbi = np.ascontiguousarray( + np.rot90( + np.dstack((storage_rgb, storage_ir)), + k=-1, + axes=(0, 1), + ) + ) + native_validity = np.ascontiguousarray(np.rot90(storage_validity, k=-1, axes=(0, 1))) + capture_attempt_id = f"capture-d-slot-{slot}" + acquisition_id, evidence_sha256 = roll_service._derive_digital_ice_producer_binding( + slot=slot, + reservation_id=RESERVATION_ID, + capture_attempt_id=capture_attempt_id, + main_rgbi=native_rgbi, + prepass_rgbi=meter_rgbi, + ir_validity=native_validity, + ) + acquisition = roll_repair.RepairAcquisition.from_arrays( + acquisition_id=acquisition_id, + slot=slot, + reservation_id=RESERVATION_ID, + capture_attempt_id=capture_attempt_id, + storage_transform=roll_repair.DIGITAL_ICE_STORAGE_TRANSFORM, + evidence_sha256=evidence_sha256, + main_rgbi=native_rgbi, + prepass_rgbi=meter_rgbi, + ir_validity=native_validity, + ) + return { + "rgb": storage_rgb, + "ir": storage_ir, + "ir_validity": storage_validity, + "meter_rgbi": meter_rgbi, + "prepare_digital_ice": lambda: acquisition, + } + + +def _capture_d_evidence() -> NativeBuilderEvidence: + if not CAPTURE_D.is_dir(): + pytest.skip("archived capture-d is unavailable") + descriptor = (CAPTURE_D / "analyzer-desc.bin").read_bytes() + width, height, r0, c0, r1, c1 = struct.unpack_from("<6I", descriptor) + numerators = struct.unpack_from("<3I", descriptor, 0x1C) + final_f02 = struct.unpack_from("<3I", descriptor, 0x28) + densities = struct.unpack_from("<3d", descriptor, 0x40) + analyzer = np.frombuffer((CAPTURE_D / "analyzer-pixels.bin").read_bytes(), dtype=" NativeBuilderEvidence: + """Small deterministic provenance fixture whose analyzer is self-contained.""" + + numerators = (57_114, 48_036, 32_683) + density_f03 = (70_307, 136_614, 125_470) + densities = ( + struct.unpack(">d", bytes.fromhex("3fd8b159777b9d5f"))[0], + struct.unpack(">d", bytes.fromhex("3fe9cc75f7f6705a"))[0], + struct.unpack(">d", bytes.fromhex("3ff0b0dae0533338"))[0], + ) + analyzer = np.full((425, 281, 3), 20_000, dtype=np.uint16) + ownership = _ownership_document() + ownership_payload = _canonical_json(ownership) + density_payload = _canonical_json( + _density_document( + numerators=numerators, + density_f03=density_f03, + densities=densities, + ) + ) + return NativeBuilderEvidence( + session_id=RESERVATION_ID, + capture_attempt_id="capture-d", + scan_identity="capture-d-slot", + slot=5, + density_source_wire_sha256=DENSITY_WIRE_SHA256, + density_source_child_sha256=DENSITY_CHILD_SHA256, + calibration_numerators=numerators, + density_f03_denominators=density_f03, + densities=densities, + density_arithmetic=DENSITY_ALGORITHM_ID, + frame_ownership_status=FRAME_OWNERSHIP_STATUS, + frame_ownership_receipt=ownership_payload, + frame_ownership_receipt_sha256=sha256(ownership_payload).hexdigest(), + density_evidence_receipt=density_payload, + density_evidence_receipt_sha256=sha256(density_payload).hexdigest(), + reservation_id=RESERVATION_ID, + batch_session_id=RESERVATION_ID, + preview_sha256=PREVIEW_SHA256, + preview_identity_sha256=PREVIEW_IDENTITY_SHA256, + transport_table_sha256=TRANSPORT_TABLE_SHA256, + transport_identity_sha256=ownership["transport_identity_sha256"], + reviewed_fingerprint_sha256=REVIEWED_FINGERPRINT_SHA256, + fresh_fingerprint_sha256=FRESH_FINGERPRINT_SHA256, + frame_index=1, + frame_total=1, + selected_slots=(5,), + analyzer_rgb=analyzer, + analyzer_rgb_sha256=sha256(analyzer.astype(" object: + ownership = _PayloadReceipt(_ownership_document()) + return types.SimpleNamespace( + slot=5, + **_bound_repair_frame_fields(), + receipt=_FrameReceipt( + version=1, + slot=5, + nikon_density_ownership=ownership, + ), + nikon_density_evidence=_PayloadReceipt(_density_document()), + nikon_density_ownership=ownership, + nikon_exact_builder_evidence=_synthetic_native_evidence(), + ) + + +def _coolscanpy_capture_d_evidence() -> object: + try: + from coolscanpy.protocol.ls5000_single_pass.density import ( + NikonExactBuilderEvidence, + ) + except ImportError: + pytest.skip("installed Coolscanpy lacks the exact builder producer") + native = _capture_d_evidence() + return NikonExactBuilderEvidence(**{name: getattr(native, name) for name in NativeBuilderEvidence.__dataclass_fields__}) + + +def _coolscanpy_built_frame() -> object: + try: + from coolscanpy.protocol.ls5000_single_pass.density import ( + assemble_density_calibration, + build_nikon_density_evidence, + build_nikon_density_frame_ownership, + build_nikon_exact_builder_evidence, + decode_density_calibration_read, + ) + from coolscanpy.types import ( + Frame, + build_digital_ice_acquisition_evidence, + ) + except ImportError: + pytest.skip("installed Coolscanpy lacks the exact builder producer") + native = _capture_d_evidence() + calibration = assemble_density_calibration( + [ + decode_density_calibration_read( + bytes.fromhex(f"28008c000{color}0300000a80"), + bytes.fromhex(payload), + ) + for color, payload in enumerate( + ( + "8c20000000040000df1a", + "8c20000000040000bba4", + "8c200000000400007fab", + ), + start=1, + ) + ], + session_id=RESERVATION_ID, + ) + samples = np.concatenate( + ( + np.full(96, 45_000, dtype=np.uint16), + np.full(96, 40_000, dtype=np.uint16), + np.full(96, 32_000, dtype=np.uint16), + ) + ).astype(">u2") + density_source = bytes(100 * 1_024) + samples.tobytes() + bytes(448) + bytes((6_104 - 101) * 1_024) + density = build_nikon_density_evidence( + density_source, + calibration=calibration, + density_f03_exposures_raw_10ns=(70_307, 136_614, 125_470), + session_id=RESERVATION_ID, + capture_attempt_id="preview-attempt-1", + scan_identity="reservation-test-001-density-97dpi-preview", + ) + ownership = build_nikon_density_frame_ownership( + density, + reservation_id=RESERVATION_ID, + batch_session_id=RESERVATION_ID, + transport_table_sha256=TRANSPORT_TABLE_SHA256, + reviewed_fingerprint_sha256=REVIEWED_FINGERPRINT_SHA256, + fresh_fingerprint_sha256=FRESH_FINGERPRINT_SHA256, + frame_capture_attempt_id="fine-slot-5-attempt-1", + frame_index=1, + frame_total=1, + selected_slots=(5,), + selected_slot=5, + ) + exact_builder = build_nikon_exact_builder_evidence( + density, + ownership, + analyzer_rgb=native.analyzer_rgb, + final_f02_denominators=native.final_f02_denominators, + ) + repair_fields = _bound_repair_frame_fields() + digital_ice_evidence = build_digital_ice_acquisition_evidence( + slot=5, + reservation_id=RESERVATION_ID, + capture_attempt_id="fine-slot-5-attempt-1", + storage_rgb=repair_fields["rgb"], + storage_ir=repair_fields["ir"], + storage_ir_validity=repair_fields["ir_validity"], + meter_rgbi=repair_fields["meter_rgbi"], + ) + return Frame( + slot=5, + rgb=repair_fields["rgb"], + ir=repair_fields["ir"], + ir_validity=repair_fields["ir_validity"], + receipt=_FrameReceipt( + version=1, + slot=5, + nikon_density_ownership=ownership, + ), + meter_rgbi=repair_fields["meter_rgbi"], + nikon_density_evidence=density, + nikon_exact_builder_evidence=exact_builder, + digital_ice_evidence=digital_ice_evidence, + ) + + +def test_exact_coolscanpy_producer_is_narrowly_adapted_and_revalidated() -> None: + producer = _coolscanpy_capture_d_evidence() + adapted = adapt_native_builder_evidence(producer) + + assert type(adapted) is NativeBuilderEvidence + assert adapted.analyzer_rgb_sha256 == producer.analyzer_rgb_sha256 + receipt = build_native_builder_receipt(adapted) + assert tuple(sha256(blob).hexdigest() for blob in receipt.pre_f_luts) == (CAPTURE_D_PREF_SHA256) + + +def test_coolscanpy_built_frame_runs_through_negpy_native_service( + fake_repair_engine, + tmp_path: Path, +) -> None: + frame = _coolscanpy_built_frame() + + output = RollScanningService().write_frame( + frame, + str(tmp_path), + '{{ "%03d" % seq }}', + write_unrepaired=False, + write_positive=True, + positive_mode="nikon-exact", + ) + + assert output.positive_path is not None + positive = json.loads(Path(output.receipt_path).read_text())["outputs"]["positive"] + assert positive["native_per_acquisition_builder"] is True + + +def test_archived_capture_builder_math_reproduces_all_three_pref_luts_byte_exact() -> None: + receipt = build_native_builder_receipt(_capture_d_evidence()) + + assert tuple(sha256(blob).hexdigest() for blob in receipt.pre_f_luts) == CAPTURE_D_PREF_SHA256 + payload = exact_color.builder_receipt_payload(receipt) + assert payload["native_per_acquisition_builder"] is True + assert payload["density_source"]["resolution_dpi"] == 97 + assert payload["analyzer_source"]["resolution_dpi"] == 285 + assert payload["density_source"]["f03_denominators_raw_10ns_rgb"] != payload["analyzer_source"]["final_f02_denominators_raw_10ns_rgb"] + + +def test_native_receipt_runs_through_stage1_and_cms_with_native_application_binding() -> None: + receipt = build_native_builder_receipt(_capture_d_evidence()) + source = np.array([[[0, 1, 2], [65_535, 42_000, 17]]], dtype=np.uint16) + + result = exact_color.evaluate_exact_color( + source, + builder_receipt=receipt, + builder=PortableStage1Builder(chunk_pixels=1), + evaluator=PortableCMSOnEvaluator(chunk_pixels=1), + ) + + application = exact_color.receipt_payload(result.builder_application_receipt) + assert application["native_per_acquisition_builder"] is True + assert application["scope"] == exact_color.NATIVE_BUILDER_SCOPE + assert application["native_evidence_sha256"] == receipt.evidence_sha256 + + +@pytest.mark.parametrize("evidence_source", ["negpy", "coolscanpy"]) +def test_service_auto_builds_native_receipt_and_retains_analyzer_evidence( + fake_repair_engine, + tmp_path: Path, + evidence_source: str, +) -> None: + ownership = _PayloadReceipt(_ownership_document()) + density = _PayloadReceipt(_density_document()) + + @dataclass(frozen=True) + class Receipt: + version: int = 1 + slot: int = 5 + nikon_density_ownership: object = ownership + + frame = types.SimpleNamespace( + slot=5, + **_bound_repair_frame_fields(), + receipt=Receipt(), + nikon_density_evidence=density, + nikon_density_ownership=ownership, + nikon_exact_builder_evidence=(_capture_d_evidence() if evidence_source == "negpy" else _coolscanpy_capture_d_evidence()), + ) + + service = RollScanningService() + output = service.write_frame( + frame, + str(tmp_path), + '{{ "%03d" % seq }}', + write_unrepaired=False, + write_positive=True, + positive_mode="nikon-exact", + ) + + assert output.positive_path is not None + entry = json.loads(Path(output.receipt_path).read_text())["outputs"]["positive"] + assert entry["native_per_acquisition_builder"] is True + assert entry["inversion_path"] == "native-per-acquisition-builder-and-verified-portable-cms" + retained = entry["retained_builder_evidence"] + assert retained["scope"] == exact_color.NATIVE_BUILDER_SCOPE + analyzer = Path(retained["analyzer_rgb"]["path"]).read_bytes() + assert sha256(analyzer).hexdigest() == retained["analyzer_rgb"]["sha256"] + ownership_payload = Path(retained["frame_ownership_receipt"]["path"]).read_bytes() + assert sha256(ownership_payload).hexdigest() == retained["frame_ownership_receipt"]["sha256"] + density_payload = Path(retained["density_evidence_receipt"]["path"]).read_bytes() + assert sha256(density_payload).hexdigest() == retained["density_evidence_receipt"]["sha256"] + + +def test_service_retains_native_builder_evidence_when_positive_is_not_selected( + tmp_path: Path, +) -> None: + output = RollScanningService().write_frame( + _synthetic_native_frame(), + str(tmp_path), + '{{ "%03d" % seq }}', + write_unrepaired=True, + write_repaired=False, + write_positive=False, + ) + + receipt = json.loads(Path(output.receipt_path).read_text()) + assert receipt["outputs"]["positive"] == { + "written": False, + "status": "not selected", + } + entry = receipt["outputs"]["native_color_evidence"] + assert entry["retained"] is True + assert entry["builder_receipt_sha256"] == entry["retained_builder_evidence"]["builder_receipt"]["sha256"] + for artifact in ( + entry["retained_builder_evidence"]["builder_receipt"], + entry["retained_builder_evidence"]["evidence_receipt"], + entry["retained_builder_evidence"]["frame_ownership_receipt"], + entry["retained_builder_evidence"]["density_evidence_receipt"], + entry["retained_builder_evidence"]["analyzer_rgb"], + *entry["retained_builder_evidence"]["pre_f_luts"], + ): + payload = Path(artifact["path"]).read_bytes() + assert len(payload) == artifact["bytes"] + assert sha256(payload).hexdigest() == artifact["sha256"] + + reloaded = exact_color.load_native_builder_receipt(entry["retained_builder_evidence"]["builder_receipt"]["path"]) + assert reloaded.sha256 == entry["builder_receipt_sha256"] + replay = exact_color.evaluate_exact_color( + np.array([[[1, 2, 3], [65_535, 42_000, 17]]], dtype=np.uint16), + builder_receipt=reloaded, + builder=PortableStage1Builder(chunk_pixels=1), + evaluator=PortableCMSOnEvaluator(chunk_pixels=1), + ) + assert replay.builder_receipt.sha256 == entry["builder_receipt_sha256"] + + +def test_retained_native_builder_loader_rejects_a_symlinked_artifact( + tmp_path: Path, +) -> None: + output = RollScanningService().write_frame( + _synthetic_native_frame(), + str(tmp_path), + '{{ "%03d" % seq }}', + write_unrepaired=False, + write_repaired=False, + write_positive=False, + ) + retained = json.loads(Path(output.receipt_path).read_text())["outputs"]["native_color_evidence"]["retained_builder_evidence"] + target = Path(retained["pre_f_luts"][1]["path"]) + real = target.with_name("real-builder-preF-g.bin") + target.replace(real) + target.symlink_to(real) + + with pytest.raises(exact_color.ExactColorUnavailable, match="non-symlink"): + exact_color.load_native_builder_receipt(retained["builder_receipt"]["path"]) + + +def test_retained_native_builder_loader_does_not_block_on_a_fifo_swap( + monkeypatch: pytest.MonkeyPatch, + tmp_path: Path, +) -> None: + output = RollScanningService().write_frame( + _synthetic_native_frame(), + str(tmp_path), + '{{ "%03d" % seq }}', + write_unrepaired=False, + write_repaired=False, + write_positive=False, + ) + retained = json.loads(Path(output.receipt_path).read_text())["outputs"]["native_color_evidence"]["retained_builder_evidence"] + target = Path(retained["pre_f_luts"][1]["path"]) + saved = target.with_name("saved-builder-preF-g.bin") + real_open = os.open + swapped = False + + def swap_to_fifo_then_open(path, flags, *args, **kwargs): + nonlocal swapped + if not swapped and Path(path) == target: + swapped = True + target.replace(saved) + os.mkfifo(target) + return real_open(path, flags, *args, **kwargs) + + monkeypatch.setattr(exact_color.os, "open", swap_to_fifo_then_open) + + with pytest.raises( + exact_color.ExactColorIntegrityError, + match="changed while being read", + ): + exact_color.load_native_builder_receipt(retained["builder_receipt"]["path"]) + + +def test_retained_native_builder_loader_rederives_luts_instead_of_trusting_rehashed_files( + tmp_path: Path, +) -> None: + output = RollScanningService().write_frame( + _synthetic_native_frame(), + str(tmp_path), + '{{ "%03d" % seq }}', + write_unrepaired=False, + write_repaired=False, + write_positive=False, + ) + retained = json.loads(Path(output.receipt_path).read_text())["outputs"]["native_color_evidence"]["retained_builder_evidence"] + lut_path = Path(retained["pre_f_luts"][0]["path"]) + changed_lut = bytearray(lut_path.read_bytes()) + changed_lut[13_579] ^= 1 + lut_path.write_bytes(changed_lut) + receipt_path = Path(retained["builder_receipt"]["path"]) + envelope = json.loads(receipt_path.read_bytes()) + envelope["pre_f_luts"][0]["sha256"] = sha256(changed_lut).hexdigest() + receipt_path.write_bytes(_canonical_json(envelope)) + + with pytest.raises( + exact_color.ExactColorIntegrityError, + match="fresh native derivation", + ): + exact_color.load_native_builder_receipt(receipt_path) + + +def test_retained_native_builder_loader_requires_its_content_addressed_directory( + tmp_path: Path, +) -> None: + output = RollScanningService().write_frame( + _synthetic_native_frame(), + str(tmp_path), + '{{ "%03d" % seq }}', + write_unrepaired=False, + write_repaired=False, + write_positive=False, + ) + retained = json.loads(Path(output.receipt_path).read_text())["outputs"]["native_color_evidence"]["retained_builder_evidence"] + original_receipt = Path(retained["builder_receipt"]["path"]) + renamed_directory = original_receipt.parent.with_name("wrong-content-address") + original_receipt.parent.rename(renamed_directory) + + with pytest.raises( + exact_color.ExactColorIntegrityError, + match="content-addressed directory", + ): + exact_color.load_native_builder_receipt(renamed_directory / original_receipt.name) + + +def test_service_keeps_native_builder_evidence_when_exact_tiff_verification_fails( + fake_repair_engine, + monkeypatch: pytest.MonkeyPatch, + tmp_path: Path, +) -> None: + def reject_tiff(*args: object, **kwargs: object) -> dict[str, object]: + del args, kwargs + raise exact_color.ExactColorIntegrityError("test TIFF rejection") + + monkeypatch.setattr(roll_service, "_verify_exact_positive_tiff", reject_tiff) + + output = RollScanningService().write_frame( + _synthetic_native_frame(), + str(tmp_path), + '{{ "%03d" % seq }}', + write_unrepaired=False, + write_repaired=False, + write_positive=True, + positive_mode="nikon-exact", + ) + + assert output.positive_path is None + receipt = json.loads(Path(output.receipt_path).read_text()) + assert "test TIFF rejection" in receipt["outputs"]["positive"]["status"] + retained = receipt["outputs"]["native_color_evidence"] + assert retained["retained"] is True + assert not (tmp_path / "005_positive.tif").exists() + for artifact in ( + retained["retained_builder_evidence"]["builder_receipt"], + retained["retained_builder_evidence"]["evidence_receipt"], + retained["retained_builder_evidence"]["analyzer_rgb"], + *retained["retained_builder_evidence"]["pre_f_luts"], + ): + assert Path(artifact["path"]).is_file() + + +def test_noncanonical_dice_binding_withholds_positive_but_keeps_native_color_evidence( + fake_repair_engine, + tmp_path: Path, +) -> None: + frame = _synthetic_native_frame() + forged_acquisition = replace( + frame.prepare_digital_ice(), + evidence_sha256="0" * 64, + ) + frame.prepare_digital_ice = lambda: forged_acquisition + + output = RollScanningService().write_frame( + frame, + str(tmp_path), + '{{ "%03d" % seq }}', + write_unrepaired=False, + write_repaired=False, + write_positive=True, + positive_mode="nikon-exact", + ) + + assert fake_repair_engine.calls == [] + assert output.positive_path is None + receipt = json.loads(Path(output.receipt_path).read_text())["outputs"] + assert "producer evidence SHA-256 changed" in receipt["repaired"]["status"] + assert "Tier 2" in receipt["positive"]["status"] + native = receipt["native_color_evidence"] + assert native["retained"] is True + assert Path( + native["retained_builder_evidence"]["builder_receipt"]["path"] + ).is_file() + + +def test_service_refuses_explicit_builder_evidence_without_current_frame_ownership(fake_repair_engine, tmp_path: Path) -> None: + @dataclass(frozen=True) + class Receipt: + version: int = 1 + slot: int = 5 + + frame = types.SimpleNamespace( + slot=5, + **_bound_repair_frame_fields(), + receipt=Receipt(), + nikon_exact_builder_evidence=_capture_d_evidence(), + ) + + output = RollScanningService().write_frame( + frame, + str(tmp_path), + '{{ "%03d" % seq }}', + write_unrepaired=False, + write_positive=True, + positive_mode="nikon-exact", + ) + + assert output.positive_path is None + status = json.loads(Path(output.receipt_path).read_text())["outputs"]["positive"]["status"] + assert "frame ownership receipt is missing" in status + + +@pytest.mark.parametrize( + ("field", "replacement"), + [ + ("preview_sha256", sha256(b"new-preview").hexdigest()), + ("reservation_id", "new-reservation"), + ("batch_session_id", "new-reservation"), + ("transport_table_sha256", sha256(b"moved-transport-table").hexdigest()), + ("reviewed_fingerprint_sha256", sha256(b"new-registration").hexdigest()), + ("fresh_fingerprint_sha256", sha256(b"film-moved").hexdigest()), + ("frame_capture_attempt_id", "another-frame-attempt"), + ("selected_slot", 6), + ], + ids=[ + "new-preview", + "new-reservation", + "new-batch", + "transport-change", + "re-registration", + "film-move", + "new-frame-attempt", + "different-slot", + ], +) +def test_service_refuses_changed_frame_ownership_identity( + fake_repair_engine, + tmp_path: Path, + field: str, + replacement: object, +) -> None: + ownership_document = _ownership_document() + ownership_document[field] = replacement + ownership = _PayloadReceipt(ownership_document) + density = _PayloadReceipt(_density_document()) + frame = types.SimpleNamespace( + slot=5, + **_bound_repair_frame_fields(), + receipt=_FrameReceipt(version=1, slot=5, nikon_density_ownership=ownership), + nikon_density_evidence=density, + nikon_density_ownership=ownership, + nikon_exact_builder_evidence=_capture_d_evidence(), + ) + + output = RollScanningService().write_frame( + frame, + str(tmp_path), + '{{ "%03d" % seq }}', + write_unrepaired=False, + write_positive=True, + positive_mode="nikon-exact", + ) + + assert output.positive_path is None + status = json.loads(Path(output.receipt_path).read_text())["outputs"]["positive"]["status"] + assert "frame ownership does not match the native builder evidence" in status + + +def test_service_refuses_density_evidence_from_a_new_preview(fake_repair_engine, tmp_path: Path) -> None: + ownership = _PayloadReceipt(_ownership_document()) + density_document = _density_document() + density_document["preview_identity_sha256"] = sha256(b"new-preview-identity").hexdigest() + density = _PayloadReceipt(density_document) + frame = types.SimpleNamespace( + slot=5, + **_bound_repair_frame_fields(), + receipt=_FrameReceipt(version=1, slot=5, nikon_density_ownership=ownership), + nikon_density_evidence=density, + nikon_density_ownership=ownership, + nikon_exact_builder_evidence=_capture_d_evidence(), + ) + + output = RollScanningService().write_frame( + frame, + str(tmp_path), + '{{ "%03d" % seq }}', + write_unrepaired=False, + write_positive=True, + positive_mode="nikon-exact", + ) + + assert output.positive_path is None + status = json.loads(Path(output.receipt_path).read_text())["outputs"]["positive"]["status"] + assert "density evidence does not match the native builder evidence" in status + + +def test_service_refuses_frame_and_public_receipt_ownership_disagreement(fake_repair_engine, tmp_path: Path) -> None: + ownership = _PayloadReceipt(_ownership_document()) + changed = _ownership_document() + changed["fresh_fingerprint_sha256"] = sha256(b"film-moved").hexdigest() + public_ownership = _PayloadReceipt(changed) + frame = types.SimpleNamespace( + slot=5, + **_bound_repair_frame_fields(), + receipt=_FrameReceipt(version=1, slot=5, nikon_density_ownership=public_ownership), + nikon_density_evidence=_PayloadReceipt(_density_document()), + nikon_density_ownership=ownership, + nikon_exact_builder_evidence=_capture_d_evidence(), + ) + + output = RollScanningService().write_frame( + frame, + str(tmp_path), + '{{ "%03d" % seq }}', + write_unrepaired=False, + write_positive=True, + positive_mode="nikon-exact", + ) + + assert output.positive_path is None + status = json.loads(Path(output.receipt_path).read_text())["outputs"]["positive"]["status"] + assert "frame and public receipt disagree" in status + + +def test_service_honors_ownership_objects_runtime_validation(fake_repair_engine, tmp_path: Path) -> None: + ownership = _RejectingOwnershipReceipt(_ownership_document()) + frame = types.SimpleNamespace( + slot=5, + **_bound_repair_frame_fields(), + receipt=_FrameReceipt(version=1, slot=5, nikon_density_ownership=ownership), + nikon_density_evidence=_PayloadReceipt(_density_document()), + nikon_density_ownership=ownership, + nikon_exact_builder_evidence=_capture_d_evidence(), + ) + + output = RollScanningService().write_frame( + frame, + str(tmp_path), + '{{ "%03d" % seq }}', + write_unrepaired=False, + write_positive=True, + positive_mode="nikon-exact", + ) + + assert output.positive_path is None + status = json.loads(Path(output.receipt_path).read_text())["outputs"]["positive"]["status"] + assert "frame ownership is invalid: preview identity changed" in status + + +def test_service_refuses_generic_session_density_evidence_without_frame_ownership(fake_repair_engine, tmp_path: Path) -> None: + @dataclass(frozen=True) + class Receipt: + version: int = 1 + slot: int = 5 + + storage_rgb = np.full((2, 3, 3), 12_000, dtype=np.uint16) + storage_ir = np.full((2, 3), 3_000, dtype=np.uint16) + storage_rgbi = np.concatenate((storage_rgb, storage_ir[..., None]), axis=2) + native_rgbi = np.ascontiguousarray(np.rot90(storage_rgbi, k=-1, axes=(0, 1))) + prepass_rgbi = np.zeros((1, 1, 4), dtype=np.uint16) + native_validity = np.ones(native_rgbi.shape[:2], dtype=np.bool_) + acquisition_id, evidence_sha256 = roll_service._derive_digital_ice_producer_binding( + slot=5, + reservation_id="reservation-005", + capture_attempt_id="fine-slot-05-attempt-001", + main_rgbi=native_rgbi, + prepass_rgbi=prepass_rgbi, + ir_validity=native_validity, + ) + acquisition = roll_repair.RepairAcquisition.from_arrays( + acquisition_id=acquisition_id, + slot=5, + reservation_id="reservation-005", + capture_attempt_id="fine-slot-05-attempt-001", + storage_transform=roll_repair.DIGITAL_ICE_STORAGE_TRANSFORM, + evidence_sha256=evidence_sha256, + main_rgbi=native_rgbi, + prepass_rgbi=prepass_rgbi, + ir_validity=native_validity, + ) + frame = types.SimpleNamespace( + slot=5, + rgb=storage_rgb, + ir=storage_ir, + ir_validity=None, + receipt=Receipt(), + meter_rgbi=None, + nikon_density_session_evidence=object(), + prepare_digital_ice=lambda: acquisition, + ) + + output = RollScanningService().write_frame( + frame, + str(tmp_path), + '{{ "%03d" % seq }}', + write_unrepaired=False, + write_positive=True, + positive_mode="nikon-exact", + ) + + assert output.positive_path is None + entry = json.loads(Path(output.receipt_path).read_text())["outputs"]["positive"] + assert "no native builder evidence" in entry["status"] + + +def test_native_builder_rejects_conflated_density_and_final_exposure_triplets() -> None: + evidence = _capture_d_evidence() + density_payload = _canonical_json( + _density_document( + numerators=evidence.calibration_numerators, + density_f03=evidence.final_f02_denominators, + densities=evidence.densities, + ) + ) + candidate = replace( + evidence, + density_f03_denominators=evidence.final_f02_denominators, + density_evidence_receipt=density_payload, + density_evidence_receipt_sha256=sha256(density_payload).hexdigest(), + ) + + with pytest.raises(exact_color.ExactColorUnavailable, match="conflated"): + build_native_builder_receipt(candidate) + + +def test_native_builder_rejects_analyzer_mutation_after_identity_was_bound() -> None: + evidence = _capture_d_evidence() + analyzer = evidence.analyzer_rgb.copy() + candidate = replace(evidence, analyzer_rgb=analyzer) + analyzer[0, 0, 0] ^= 1 + + with pytest.raises(exact_color.ExactColorUnavailable, match="does not match its SHA-256"): + build_native_builder_receipt(candidate) + + +def test_service_rejects_native_evidence_for_a_different_frame_slot(fake_repair_engine, tmp_path: Path) -> None: + @dataclass(frozen=True) + class Receipt: + version: int = 1 + slot: int = 6 + + frame = types.SimpleNamespace( + slot=6, + **_bound_repair_frame_fields(slot=6), + receipt=Receipt(), + nikon_exact_builder_evidence=_capture_d_evidence(), + ) + + output = RollScanningService().write_frame( + frame, + str(tmp_path), + '{{ "%03d" % seq }}', + write_unrepaired=False, + write_positive=True, + positive_mode="nikon-exact", + ) + + assert output.positive_path is None + status = json.loads(Path(output.receipt_path).read_text())["outputs"]["positive"]["status"] + assert "different slot" in status + + +def test_native_receipt_rejects_evidence_mutation() -> None: + receipt = build_native_builder_receipt(_capture_d_evidence()) + tampered = replace(receipt, evidence_payload=receipt.evidence_payload + b" ") + + with pytest.raises(exact_color.ExactColorIntegrityError, match="evidence does not match"): + exact_color.builder_receipt_payload(tampered) diff --git a/tests/roll/test_nikon_icc.py b/tests/roll/test_nikon_icc.py new file mode 100644 index 00000000..ba54fbae --- /dev/null +++ b/tests/roll/test_nikon_icc.py @@ -0,0 +1,52 @@ +"""Exact Nikon output-profile identity and fail-closed tests.""" + +from __future__ import annotations + +import base64 +import hashlib +from io import BytesIO + +import pytest +from PIL import ImageCms + +from negpy.services.roll import nikon_icc + + +def test_embedded_profile_is_the_pinned_nikon_scan_rgb_profile() -> None: + profile = nikon_icc.nikon_adobe_rgb_profile() + + assert len(profile) == 492 + assert int.from_bytes(profile[:4], "big") == len(profile) + assert profile[12:16] == b"mntr" + assert profile[16:20] == b"RGB " + assert profile[20:24] == b"XYZ " + assert profile[36:40] == b"acsp" + assert hashlib.sha256(profile).hexdigest() == ( + "a8d0d753bd6129357cc2647435ce675e8637a679eb526fa180fba460874ce1d3" + ) + parsed = ImageCms.ImageCmsProfile(BytesIO(profile)) + assert ImageCms.getProfileName(parsed).strip() == "Nikon Adobe RGB 4.0.0.3000" + assert ImageCms.getProfileCopyright(parsed).strip() == ( + "Nikon Inc. & Nikon Corporation 2001" + ) + assert nikon_icc.profile_receipt_binding() == { + "bytes": 492, + "name": "Nikon Adobe RGB 4.0.0.3000", + "sha256": "a8d0d753bd6129357cc2647435ce675e8637a679eb526fa180fba460874ce1d3", + } + + +def test_embedded_profile_one_byte_change_fails_closed( + monkeypatch: pytest.MonkeyPatch, +) -> None: + profile = bytearray(nikon_icc.nikon_adobe_rgb_profile()) + profile[100] ^= 1 + monkeypatch.setattr( + nikon_icc, + "_NIKON_ADOBE_RGB_PROFILE_BASE64", + base64.b64encode(profile).decode("ascii"), + ) + nikon_icc.nikon_adobe_rgb_profile.cache_clear() + + with pytest.raises(nikon_icc.NikonICCProfileError, match="pinned SHA-256"): + nikon_icc.nikon_adobe_rgb_profile() diff --git a/tests/roll/test_portable_builder.py b/tests/roll/test_portable_builder.py new file mode 100644 index 00000000..cd84f88a --- /dev/null +++ b/tests/roll/test_portable_builder.py @@ -0,0 +1,248 @@ +"""Portable Stage-1 builder tests; all hardware and VM free.""" + +from __future__ import annotations + +import dataclasses +import json +import shutil +from hashlib import sha256 +from pathlib import Path + +import numpy as np +import pytest + +from negpy.services.roll import exact_color +from negpy.services.roll.portable_builder import ( + FIXED_LUT_FILENAME, + MAX_CHUNK_PIXELS, + PortableStage1Builder, +) +from negpy.services.roll.portable_cms import PortableCMSOnEvaluator +from tests.roll._exact_fixtures import make_stage3_replay_receipt + + +CAPTURED_PREF = Path("/Volumes/isos/NikonRE/session20260719/capture-d") +CAPTURED_PREF_SHA256 = ( + "46a0d68ae20c72088e64a1144a0d38bf692f15f506539bbe94eb563fe437c976", + "23eda81294817e7a2a31f1488544a6f8d3e7ac817f22d43c8a39882565c34b95", + "3cfc61c06bac49c4c28e69afe99af01366fae6bf5ea88954f688592a8e2756bb", +) +# These are post-F only. The different `final_luts` hashes in the older +# CMS-off per-pixel report include an additional Adobe gamma encoding stage. +CAPTURED_POSTF_SHA256 = { + "r": "4fcc07a38a64fa004e252705905775952e73a2c5f0ab1f06c56de6f51073a907", + "g": "e4965e69289dacb21a3241db1fbbee2aa7dba4a1a15f458ed3ca55638b684a30", + "b": "95ec797e1afed10990ba3609f728af85b7547a33a99a2eee2d67dcae1832bf70", +} + + +def _receipt(pre_f_arrays: tuple[np.ndarray, np.ndarray, np.ndarray]) -> exact_color.ValidatedBuilderReceipt: + return make_stage3_replay_receipt(pre_f_arrays) + + +def _synthetic_luts() -> tuple[np.ndarray, np.ndarray, np.ndarray]: + values = np.arange(65_536, dtype=np.uint32) + return ( + ((values + 17) & 0xFFFF).astype(np.uint16), + (65_535 - values).astype(np.uint16), + (values // 3).astype(np.uint16), + ) + + +def test_builder_composes_fixed_after_each_pref_plane() -> None: + source = np.array([[[0, 1, 2], [65_535, 40_000, 17]]], dtype=np.uint16) + pre_f = _synthetic_luts() + fixed_path = Path(__file__).resolve().parents[2] / "negpy/assets/portable_builder" / FIXED_LUT_FILENAME + fixed = np.frombuffer(fixed_path.read_bytes(), dtype=" None: + source = np.random.default_rng(44).integers(0, 65_536, size=(2, 5, 3), dtype=np.uint16) + receipt = _receipt(_synthetic_luts()) + outputs = [PortableStage1Builder(chunk_pixels=size).apply(source, builder_receipt=receipt).rgb for size in (1, 3, 10, 11)] + + for output in outputs[1:]: + np.testing.assert_array_equal(output, outputs[0]) + + +def test_exact_boundary_runs_builder_then_cms_and_keeps_receipts_separate() -> None: + source = np.random.default_rng(46).integers(0, 65_536, size=(2, 4, 3), dtype=np.uint16) + receipt = _receipt(_synthetic_luts()) + + result = exact_color.evaluate_exact_color( + source, + builder_receipt=receipt, + builder=PortableStage1Builder(chunk_pixels=3), + evaluator=PortableCMSOnEvaluator(chunk_pixels=3), + ) + + assert result.source_rgb_sha256 == exact_color.rgb16_content_sha256(source) + assert result.builder_application_receipt is not None + builder_application = exact_color.receipt_payload(result.builder_application_receipt) + cms = exact_color.receipt_payload(result.cms_receipt) + assert result.input_rgb_sha256 == builder_application["stage1_input_rgb_sha256"] + assert cms["input_rgb_sha256"] == result.input_rgb_sha256 + assert cms["output_rgb_sha256"] == result.output_rgb_sha256 + assert builder_application["kind"] == "negpy.verified-stage1-builder-application" + assert cms["kind"] == "negpy.portable-cms-on-receipt" + + +@pytest.mark.parametrize( + "field", + ["pre_f_blob", "stage3_report", "stage3_artifact_binding", "fixed_identity"], +) +def test_builder_receipt_tampering_fails_closed(field: str) -> None: + receipt = _receipt(_synthetic_luts()) + if field == "pre_f_blob": + tampered = bytearray(receipt.pre_f_luts[0]) + tampered[99] ^= 1 + candidate = dataclasses.replace( + receipt, + pre_f_luts=(bytes(tampered), *receipt.pre_f_luts[1:]), + ) + elif field == "stage3_report": + tampered = bytearray(receipt.stage3_receipt) + tampered[-2] ^= 1 + candidate = dataclasses.replace(receipt, stage3_receipt=bytes(tampered)) + elif field == "stage3_artifact_binding": + report = json.loads(receipt.stage3_receipt) + next(row for row in report["artifacts"] if row["name"] == "builder-preF-r.bin")["sha256"] = "0" * 64 + stage3 = json.dumps(report, sort_keys=True, separators=(",", ":")).encode() + stage3_hash = sha256(stage3).hexdigest() + envelope = json.loads(receipt.payload) + envelope["stage3_receipt_sha256"] = stage3_hash + payload = json.dumps(envelope, sort_keys=True, separators=(",", ":")).encode() + candidate = dataclasses.replace( + receipt, + payload=payload, + sha256=sha256(payload).hexdigest(), + stage3_receipt=stage3, + stage3_receipt_sha256=stage3_hash, + ) + else: + candidate = dataclasses.replace(receipt, fixed_composition_sha256="0" * 64) + + with pytest.raises(exact_color.ExactColorIntegrityError): + PortableStage1Builder().apply( + np.zeros((1, 1, 3), dtype=np.uint16), + builder_receipt=candidate, + ) + + +def test_stage3_report_with_errors_is_rejected_even_when_summary_claims_pass() -> None: + receipt = _receipt(_synthetic_luts()) + report = json.loads(receipt.stage3_receipt) + report["errors"] = [{"code": "suppressed", "message": "must remain fatal"}] + stage3 = json.dumps(report, sort_keys=True, separators=(",", ":")).encode() + stage3_hash = sha256(stage3).hexdigest() + envelope = json.loads(receipt.payload) + envelope["stage3_receipt_sha256"] = stage3_hash + payload = json.dumps(envelope, sort_keys=True, separators=(",", ":")).encode() + candidate = dataclasses.replace( + receipt, + payload=payload, + sha256=sha256(payload).hexdigest(), + stage3_receipt=stage3, + stage3_receipt_sha256=stage3_hash, + ) + + with pytest.raises(exact_color.ExactColorIntegrityError, match="errors"): + exact_color.builder_receipt_payload(candidate) + + +def test_stage3_replay_receipts_require_the_trusted_file_loader() -> None: + assert callable(exact_color.load_stage3_replay_builder_receipt) + + +@pytest.mark.parametrize("failure", ["missing", "tampered"]) +def test_fixed_lut_asset_verification_fails_closed(tmp_path: Path, failure: str) -> None: + packaged = Path(__file__).resolve().parents[2] / "negpy/assets/portable_builder" + copied = tmp_path / "assets" + shutil.copytree(packaged, copied) + target = copied / FIXED_LUT_FILENAME + if failure == "missing": + target.unlink() + expected = exact_color.ExactColorUnavailable + else: + payload = bytearray(target.read_bytes()) + payload[1000] ^= 1 + target.write_bytes(payload) + expected = exact_color.ExactColorIntegrityError + + with pytest.raises(expected): + PortableStage1Builder(assets_dir=copied) + + +def test_builder_preserves_input_and_freezes_stage1_output() -> None: + source = np.random.default_rng(45).integers(0, 65_536, size=(2, 3, 3), dtype=np.uint16) + original = source.copy() + + result = PortableStage1Builder(chunk_pixels=2).apply( + source, + builder_receipt=_receipt(_synthetic_luts()), + ) + + np.testing.assert_array_equal(source, original) + assert not result.rgb.flags.writeable + with pytest.raises(ValueError): + result.rgb[0, 0, 0] ^= 1 + + +@pytest.mark.parametrize( + "source", + [ + np.zeros((2, 2, 3), dtype=np.uint8), + np.zeros((2, 2), dtype=np.uint16), + np.zeros((2, 2, 4), dtype=np.uint16), + np.zeros((0, 2, 3), dtype=np.uint16), + ], +) +def test_builder_rejects_wrong_input_dtype_or_shape(source: np.ndarray) -> None: + with pytest.raises(exact_color.ExactColorIntegrityError): + PortableStage1Builder().apply( + source, + builder_receipt=_receipt(_synthetic_luts()), + ) + + +@pytest.mark.parametrize("chunk_pixels", [0, MAX_CHUNK_PIXELS + 1, True, 1.5]) +def test_builder_chunk_size_is_bounded(chunk_pixels: object) -> None: + with pytest.raises(exact_color.ExactColorUnavailable): + PortableStage1Builder(chunk_pixels=chunk_pixels) # ty: ignore[invalid-argument-type] + + +def test_accessible_captured_pref_luts_reproduce_pinned_postf_hashes() -> None: + paths = [CAPTURED_PREF / f"builder-preF-{channel}.bin" for channel in ("r", "g", "b")] + if not all(path.is_file() for path in paths): + pytest.skip("stored capture-d builder LUTs are unavailable") + blobs = tuple(path.read_bytes() for path in paths) + assert tuple(sha256(blob).hexdigest() for blob in blobs) == CAPTURED_PREF_SHA256 + luts = ( + np.frombuffer(blobs[0], dtype=" np.ndarray: + path = ORACLE_LAB / f"event{event}-{kind}.bin" + if not path.is_file(): + pytest.skip(f"stored CML fixture is unavailable: {path}") + width, height = GEOMETRY[event] + raw = np.frombuffer(path.read_bytes(), dtype=" exact_color.ValidatedBuilderReceipt: + identity = np.frombuffer(IDENTITY_LUT, dtype=" None: + source = _event(event, "source") + expected = _event(event, "expected") + transforms = PortableCMSOnEvaluator()._transforms # noqa: SLF001 - oracle-stage verification + flat_source = source.reshape(-1, 3) + + if event in STAGE1_EVENTS: + actual = transforms.stage1(flat_source) + else: + actual = transforms.stage2(flat_source) + + np.testing.assert_array_equal(actual.reshape(source.shape), expected) + + +@pytest.mark.parametrize( + ("stage1_event", "stage2_event"), + [(30, 31), (34, 35), (38, 39), (42, 43), (47, 48), (52, 53)], +) +def test_adapter_replays_all_stage1_to_stage2_fixture_pairs( + stage1_event: int, + stage2_event: int, +) -> None: + source = _event(stage1_event, "source") + expected = _event(stage2_event, "expected") + builder_receipt = _builder_receipt() + + result = PortableCMSOnEvaluator(chunk_pixels=997).evaluate( + source, + builder_receipt=builder_receipt, + ) + + np.testing.assert_array_equal(result.rgb, expected) + cms = exact_color.receipt_payload(result.cms_receipt) + assert cms["scope"] == "captured-cml4-stage1-stage2-only" + assert cms["builder_receipt_sha256"] == builder_receipt.sha256 + assert cms["input_rgb_sha256"] == exact_color.rgb16_content_sha256(source) + assert cms["output_rgb_sha256"] == exact_color.rgb16_content_sha256(expected) + assert len(cms["assets"]) == 9 + assert cms["validation"] == { + "events": 12, + "full_payload_mismatched_bytes": 0, + "full_payload_total_bytes": 698_880, + "mismatched_u16": 0, + "receipt_sha256": VALIDATION_RECEIPT_SHA256, + "total_u16": 265_440, + } + + +def test_chunk_boundaries_do_not_change_integer_results() -> None: + source = np.random.default_rng(22).integers(0, 65_536, size=(2, 5, 3), dtype=np.uint16) + builder_receipt = _builder_receipt() + outputs = [] + + for chunk_pixels in (1, 3, 10, 11): + result = PortableCMSOnEvaluator(chunk_pixels=chunk_pixels).evaluate( + source, + builder_receipt=builder_receipt, + ) + outputs.append(result.rgb) + + for output in outputs[1:]: + np.testing.assert_array_equal(output, outputs[0]) + + +@pytest.mark.parametrize("failure", ["missing", "tampered"]) +def test_asset_verification_fails_closed(tmp_path: Path, failure: str) -> None: + packaged = Path(__file__).resolve().parents[2] / "negpy/assets/portable_cms" + copied = tmp_path / "assets" + shutil.copytree(packaged, copied) + target = copied / next(iter(ASSET_SHA256)) + if failure == "missing": + target.unlink() + expected = exact_color.ExactColorUnavailable + else: + payload = bytearray(target.read_bytes()) + payload[len(payload) // 2] ^= 1 + target.write_bytes(payload) + expected = exact_color.ExactColorIntegrityError + + with pytest.raises(expected): + PortableCMSOnEvaluator(assets_dir=copied) + + +def test_packaged_validation_receipt_is_exact_and_closes_the_claimed_totals() -> None: + packaged = Path(__file__).resolve().parents[2] / "negpy/assets/portable_cms" + payload = (packaged / VALIDATION_RECEIPT_FILENAME).read_bytes() + + assert len(payload) == VALIDATION_RECEIPT_BYTES + assert hashlib.sha256(payload).hexdigest() == VALIDATION_RECEIPT_SHA256 + summary = portable_cms._validate_validation_receipt(payload) # noqa: SLF001 - release provenance contract + assert summary == { + "events": 12, + "mismatched_u16": 0, + "total_u16": 265_440, + "full_payload_mismatched_bytes": 0, + "full_payload_total_bytes": 698_880, + } + + +@pytest.mark.parametrize("failure", ["missing", "one_byte"]) +def test_validation_receipt_file_failure_is_fatal(tmp_path: Path, failure: str) -> None: + packaged = Path(__file__).resolve().parents[2] / "negpy/assets/portable_cms" + copied = tmp_path / "assets" + shutil.copytree(packaged, copied) + target = copied / VALIDATION_RECEIPT_FILENAME + if failure == "missing": + target.unlink() + expected = exact_color.ExactColorUnavailable + else: + payload = bytearray(target.read_bytes()) + payload[len(payload) // 2] ^= 1 + target.write_bytes(payload) + expected = exact_color.ExactColorIntegrityError + + with pytest.raises(expected): + PortableCMSOnEvaluator(assets_dir=copied) + + +def test_correctly_hashed_but_incomplete_validation_schema_is_fatal( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + packaged = Path(__file__).resolve().parents[2] / "negpy/assets/portable_cms" + copied = tmp_path / "assets" + shutil.copytree(packaged, copied) + target = copied / VALIDATION_RECEIPT_FILENAME + document = json.loads(target.read_bytes()) + del document["stage1"]["per_event"]["30"] + mutated = json.dumps(document, sort_keys=True, separators=(",", ":")).encode() + target.write_bytes(mutated) + monkeypatch.setattr(portable_cms, "VALIDATION_RECEIPT_SHA256", hashlib.sha256(mutated).hexdigest()) + monkeypatch.setattr(portable_cms, "VALIDATION_RECEIPT_BYTES", len(mutated)) + + with pytest.raises(exact_color.ExactColorIntegrityError, match="validation receipt"): + PortableCMSOnEvaluator(assets_dir=copied) + + +@pytest.mark.parametrize( + "source", + [ + np.zeros((2, 2, 3), dtype=np.uint8), + np.zeros((2, 2), dtype=np.uint16), + np.zeros((2, 2, 4), dtype=np.uint16), + ], +) +def test_adapter_rejects_wrong_input_dtype_or_shape(source: np.ndarray) -> None: + with pytest.raises(exact_color.ExactColorIntegrityError): + PortableCMSOnEvaluator().evaluate(source, builder_receipt=_builder_receipt()) + + +def test_adapter_rejects_empty_input_geometry() -> None: + source = np.zeros((0, 2, 3), dtype=np.uint16) + + with pytest.raises(exact_color.ExactColorIntegrityError): + PortableCMSOnEvaluator().evaluate(source, builder_receipt=_builder_receipt()) + + +def test_adapter_does_not_mutate_input_and_returns_immutable_output() -> None: + source = np.random.default_rng(23).integers(0, 65_536, size=(2, 3, 3), dtype=np.uint16) + original = source.copy() + + result = PortableCMSOnEvaluator(chunk_pixels=2).evaluate( + source, + builder_receipt=_builder_receipt(), + ) + + np.testing.assert_array_equal(source, original) + assert not result.rgb.flags.writeable + with pytest.raises(ValueError): + result.rgb[0, 0, 0] ^= 1 + + +@pytest.mark.parametrize("chunk_pixels", [0, MAX_CHUNK_PIXELS + 1, True, 1.5]) +def test_chunk_size_is_bounded(chunk_pixels: object) -> None: + with pytest.raises(exact_color.ExactColorUnavailable): + PortableCMSOnEvaluator(chunk_pixels=chunk_pixels) # ty: ignore[invalid-argument-type] + + +def test_preloaded_oracle_module_substitution_is_rejected(monkeypatch: pytest.MonkeyPatch) -> None: + canonical_name = "negpy.services.roll.portable_oracle_evaluator" + substitute = types.ModuleType(canonical_name) + substitute.lookup_three = lambda *_args: pytest.fail("substituted oracle executed") + monkeypatch.setitem(sys.modules, canonical_name, substitute) + + with pytest.raises(exact_color.ExactColorIntegrityError, match="preloaded"): + PortableCMSOnEvaluator() + + +def test_verified_oracle_executes_isolated_without_registering_the_canonical_module() -> None: + canonical_name = "negpy.services.roll.portable_oracle_evaluator" + assert canonical_name not in sys.modules + + PortableCMSOnEvaluator() + + assert canonical_name not in sys.modules + + +def test_oracle_source_hash_mutation_is_rejected(tmp_path: Path) -> None: + source = Path(portable_cms.__file__).with_name("portable_oracle_evaluator.py") + copied = tmp_path / source.name + copied.write_bytes(source.read_bytes() + b"\n# mutation\n") + + with pytest.raises(exact_color.ExactColorIntegrityError, match="source hash mismatch"): + portable_cms._load_verified_oracle(copied) # noqa: SLF001 - adversarial loader contract + + +def test_oracle_source_swap_between_lstat_and_open_is_rejected( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + packaged = Path(portable_cms.__file__).with_name("portable_oracle_evaluator.py") + copied = tmp_path / packaged.name + copied.write_bytes(packaged.read_bytes()) + replacement = tmp_path / "replacement.py" + replacement.write_bytes(packaged.read_bytes() + b"\n# swapped\n") + real_open = os.open + swapped = False + + def swap_then_open(path, flags, *args, **kwargs): + nonlocal swapped + if not swapped and Path(path) == copied: + swapped = True + os.replace(replacement, copied) + return real_open(path, flags, *args, **kwargs) + + monkeypatch.setattr(portable_cms.os, "open", swap_then_open) + + with pytest.raises(exact_color.ExactColorIntegrityError, match="changed while being read"): + portable_cms._load_verified_oracle(copied) # noqa: SLF001 - adversarial loader contract diff --git a/tests/roll/test_repair.py b/tests/roll/test_repair.py index b9da715c..238ae166 100644 --- a/tests/roll/test_repair.py +++ b/tests/roll/test_repair.py @@ -1,11 +1,10 @@ """Tests for the Tier-2 repair engine seam (negpy.infrastructure.roll.repair). -No repair engine ships in this package, so `available()` must be False and -`repair()` must raise until a caller registers one -- these tests pin that -contract down, since `RollScanningService.write_frame`'s Tier 2/3 degrade -path depends on it. `fake_repair_engine` (tests/roll/conftest.py) scripts a -registered engine for the tests that need one; it always unregisters on -teardown, so a test that does not use it sees the unavailable state. +The optional digital-fauxice bridge auto-registers only when its engine is +installed. These tests force the seam's independently-unavailable state, +then pin that degrade contract down because `RollScanningService.write_frame`'s +Tier 2/3 path depends on it. `fake_repair_engine` (tests/roll/conftest.py) +scripts a registered engine for tests that need one. """ from __future__ import annotations @@ -16,15 +15,28 @@ from negpy.infrastructure.roll import repair as roll_repair +def _acquisition() -> roll_repair.RepairAcquisition: + main = np.full((3, 2, 4), 100, dtype=np.uint16) + return roll_repair.RepairAcquisition.from_arrays( + acquisition_id="dice-" + "a" * 64, + slot=7, + reservation_id="reservation-007", + capture_attempt_id="fine-slot-7-attempt-001", + storage_transform=roll_repair.DIGITAL_ICE_STORAGE_TRANSFORM, + evidence_sha256="b" * 64, + main_rgbi=main, + prepass_rgbi=np.full((2, 2, 4), 50, dtype=np.uint16), + ir_validity=np.ones((3, 2), dtype=np.bool_), + ) + + class TestUnavailableByDefault: - def test_available_is_false_with_nothing_registered(self) -> None: + def test_available_is_false_with_nothing_registered(self, no_repair_engine) -> None: assert roll_repair.available() is False - def test_repair_raises_when_unavailable(self) -> None: - rgb = np.zeros((2, 2, 3), dtype=np.uint16) - ir = np.zeros((2, 2), dtype=np.uint16) + def test_repair_raises_when_unavailable(self, no_repair_engine) -> None: with pytest.raises(RuntimeError, match="no dust-repair engine is registered"): - roll_repair.repair(rgb, ir, roll_repair.RepairMode.EXACT) + roll_repair.repair(_acquisition(), roll_repair.RepairMode.EXACT) class TestRegisteredEngine: @@ -32,22 +44,39 @@ def test_available_becomes_true(self, fake_repair_engine) -> None: assert roll_repair.available() is True def test_repair_delegates_to_the_registered_engine(self, fake_repair_engine) -> None: - rgb = np.full((2, 2, 3), 100, dtype=np.uint16) - ir = np.full((2, 2), 50, dtype=np.uint16) - - result = roll_repair.repair(rgb, ir, roll_repair.RepairMode.HYBRID) - - assert fake_repair_engine.calls == [(rgb, ir, roll_repair.RepairMode.HYBRID)] + main = np.full((3, 2, 4), 100, dtype=np.uint16) + prepass = np.full((2, 2, 4), 50, dtype=np.uint16) + validity = np.ones((3, 2), dtype=np.bool_) + acquisition = roll_repair.RepairAcquisition.from_arrays( + acquisition_id="dice-" + "a" * 64, + slot=7, + reservation_id="reservation-007", + capture_attempt_id="fine-slot-7-attempt-001", + storage_transform="rot90-k1-scanner-native-to-upright-v1", + evidence_sha256="b" * 64, + main_rgbi=main, + prepass_rgbi=prepass, + ir_validity=validity, + ) + + result = roll_repair.repair(acquisition, roll_repair.RepairMode.HYBRID) + + assert fake_repair_engine.calls == [(acquisition, roll_repair.RepairMode.HYBRID, None)] assert result.engine == "test-repair-engine" assert result.engine_version == "0.0.1-test" - assert result.mode == roll_repair.RepairMode.HYBRID - np.testing.assert_array_equal(result.rgb, rgb) # default transform is identity + assert result.mode_requested == roll_repair.RepairMode.HYBRID + assert result.mode_resolved == roll_repair.RepairMode.EXACT + assert result.degraded is True + np.testing.assert_array_equal( + result.rgb, + np.rot90(main[..., :3], k=1, axes=(0, 1)), + ) def test_unregister_reverts_to_unavailable(self, fake_repair_engine) -> None: roll_repair.unregister_engine() assert roll_repair.available() is False with pytest.raises(RuntimeError): - roll_repair.repair(np.zeros((1, 1, 3), dtype=np.uint16), np.zeros((1, 1), dtype=np.uint16), roll_repair.RepairMode.EXACT) + roll_repair.repair(_acquisition(), roll_repair.RepairMode.EXACT) class TestRepairMode: diff --git a/tests/roll/test_service.py b/tests/roll/test_service.py index cb3897f1..d8606001 100644 --- a/tests/roll/test_service.py +++ b/tests/roll/test_service.py @@ -8,15 +8,26 @@ from __future__ import annotations +import dataclasses +import io import json import os +import time +from hashlib import sha256 +from pathlib import Path +from unittest.mock import MagicMock import numpy as np import pytest import tifffile +from PIL import Image +from negpy.infrastructure.roll import repair as roll_repair from negpy.services.roll import service as roll_service +from negpy.services.roll import exact_color +from negpy.services.roll.portable_cms import PortableCMSOnEvaluator from negpy.services.roll.service import RollScanningError, RollScanningService +from tests.roll._exact_fixtures import make_stage3_replay_receipt, production_cms_payload class TestAvailable: @@ -39,6 +50,25 @@ def test_open_then_close(self, fake_coolscanpy) -> None: assert roll.closed is True assert device.closed is True + def test_failed_close_retains_the_open_handle_for_a_safe_retry(self, fake_coolscanpy) -> None: + ownership_error = RuntimeError("USB ownership is retained") + + class UncertainRoll(fake_coolscanpy.Roll): + def close(self) -> None: + raise ownership_error + + service, _roll, device = self._open_service( + fake_coolscanpy, + UncertainRoll(), + ) + + with pytest.raises(RuntimeError) as raised: + service.close() + + assert raised.value is ownership_error + assert service._roll is not None + assert device.closed is False + def test_double_open_raises(self, fake_coolscanpy) -> None: service, _roll, _device = self._open_service(fake_coolscanpy) with pytest.raises(RollScanningError, match="already open"): @@ -53,13 +83,60 @@ def test_close_without_open_is_a_no_op(self) -> None: RollScanningService().close() # must not raise def test_preview_and_approve_delegate_to_handle(self, fake_coolscanpy) -> None: - thumb = fake_coolscanpy.Thumbnail(slot=1, image=np.zeros((2, 2, 3)), boundary_rows=(0, 2), spacing_offset=0, needs_approval=True) - service, roll, _device = self._open_service(fake_coolscanpy, fake_coolscanpy.Roll(thumbnails=[thumb])) + approval = object() + + class ReturningApprovalRoll(fake_coolscanpy.Roll): + def approve(self, slot): + super().approve(slot) + return approval + + thumb = fake_coolscanpy.Thumbnail( + slot=1, + image=np.zeros((2, 2, 3)), + boundary_rows=(0, 2), + spacing_offset=0, + needs_approval=True, + ) + service, roll, _device = self._open_service( + fake_coolscanpy, + ReturningApprovalRoll(thumbnails=[thumb]), + ) assert service.preview() == [thumb] - service.approve(1) + assert service.approve(1) is approval assert roll.approved == [1] + def test_restore_preview_session_delegates_to_open_handle( + self, fake_coolscanpy + ) -> None: + thumbnails = [ + fake_coolscanpy.Thumbnail( + slot=slot, + image=np.zeros((2, 2, 3)), + boundary_rows=(0, 2), + spacing_offset=0, + needs_approval=slot == 1, + ) + for slot in (1, 2) + ] + service, roll, _device = self._open_service( + fake_coolscanpy, + fake_coolscanpy.Roll(thumbnails=thumbnails), + ) + + result = service.restore_preview_session("saved-session", [2]) + + assert [thumbnail.slot for thumbnail in result] == [2] + assert roll.restore_preview_session_calls == [ + ("saved-session", (2,)) + ] + + def test_restore_preview_session_requires_an_open_roll(self) -> None: + service = RollScanningService() + + with pytest.raises(RollScanningError, match="no roll is open"): + service.restore_preview_session("saved-session") + def test_scan_many_delegates_to_handle(self, fake_coolscanpy) -> None: frame = fake_coolscanpy.Frame(slot=1, rgb=np.zeros((2, 2, 3), dtype=np.uint16), ir=None, ir_validity=None, receipt=None) service, _roll, _device = self._open_service(fake_coolscanpy, fake_coolscanpy.Roll(frames=[frame])) @@ -80,10 +157,10 @@ def test_context_manager_closes(self, fake_coolscanpy) -> None: class TestWriteFrame: - def _frame(self, fake_coolscanpy, *, slot=7, ir=None): + def _frame(self, fake_coolscanpy, *, slot=7, ir=None, meter_rgbi=None): rgb = np.random.randint(0, 65535, (40, 60, 3), dtype=np.uint16) receipt = fake_coolscanpy.Receipt(version=1, slot=slot, dpi=4000, depth=16, device_id="usb:1:2", transport_smear_verdict="clean") - return fake_coolscanpy.Frame(slot=slot, rgb=rgb, ir=ir, ir_validity=None, receipt=receipt) + return fake_coolscanpy.Frame(slot=slot, rgb=rgb, ir=ir, ir_validity=None, receipt=receipt, meter_rgbi=meter_rgbi) def test_writes_rgb_tiff(self, fake_coolscanpy, tmp_path) -> None: frame = self._frame(fake_coolscanpy) @@ -141,6 +218,94 @@ def test_writes_receipt_json_sidecar(self, fake_coolscanpy, tmp_path) -> None: assert payload["dpi"] == 4000 assert payload["transport_smear_verdict"] == "clean" + def test_writes_current_coolscan_receipt_with_immutable_artifacts(self, tmp_path) -> None: + from coolscanpy.types import ( + ArtifactEvidence, + ClippingTelemetry, + ExposureVector, + FocusDetailTelemetry, + Frame, + Receipt, + TransportSmearAssessment, + ) + + rgb = np.zeros((2, 3, 3), dtype=np.uint16) + artifact = ArtifactEvidence( + sha256=sha256(memoryview(rgb).cast("B")).hexdigest(), + byte_length=rgb.nbytes, + shape=rgb.shape, + dtype=rgb.dtype.str, + ) + receipt = Receipt( + version=1, + slot=1, + spacing_offset=0, + dpi=4000, + depth=16, + device_id="usb:2:7", + device_model="LS-5000 ED", + reviewed_fingerprint_sha256="a" * 64, + fresh_fingerprint_sha256="a" * 64, + manual_approval=None, + exposure=ExposureVector( + focus_position=1, + exposure_multiplier=1.0, + red_exposure_us=1.0, + green_exposure_us=1.0, + blue_exposure_us=1.0, + ), + split_alignment=None, + clipping=ClippingTelemetry( + fractions=(0.0, 0.0, 0.0), + clip_level=65535.0, + warning_fraction=0.01, + warning=False, + ), + focus_detail=FocusDetailTelemetry( + method="laplacian", + verdict="measured", + score=1.0, + texture_span=1.0, + ), + transport_smear=TransportSmearAssessment( + verdict="clean", + start_row=None, + suffix_rows=0, + minimum_matches=0, + tail_median_rms=None, + tail_min_corr=None, + pre_tail_median_rms=None, + texture_span=None, + reason="no repeated tail", + ), + artifacts={"rgb": artifact}, + ) + assert type(receipt.artifacts).__name__ == "_ImmutableArtifacts" + frame = Frame( + slot=1, + rgb=rgb, + ir=None, + ir_validity=None, + receipt=receipt, + ) + + output = RollScanningService().write_frame( + frame, + str(tmp_path), + '{{ "%03d" % seq }}', + ) + + with open(output.receipt_path, encoding="utf-8") as stream: + payload = json.load(stream) + assert payload["artifacts"] == { + "rgb": { + "byte_length": rgb.nbytes, + "dtype": rgb.dtype.str, + "sha256": artifact.sha256, + "shape": list(rgb.shape), + } + } + def test_rescanning_same_slot_overwrites(self, fake_coolscanpy, tmp_path) -> None: service = RollScanningService() pattern = '{{ "%03d" % seq }}' @@ -163,7 +328,15 @@ def test_creates_output_folder(self, fake_coolscanpy, tmp_path) -> None: assert os.path.exists(output.rgb_path) -def _tier_frame(fake_coolscanpy, *, slot=11, ir=True, seed=0): +def _tier_frame( + fake_coolscanpy, + *, + slot=11, + ir=True, + seed=0, + meter_rgbi=None, + attempt=1, +): """A frame with real per-pixel variance (not a flat fill) so a rendered Tier-3 positive has something non-degenerate to measure -- red-biased like a plausible C41 negative, matching tests/roll/test_positive.py's @@ -175,8 +348,223 @@ def _tier_frame(fake_coolscanpy, *, slot=11, ir=True, seed=0): rgb[..., 1] = rng.integers(25000, 45000, size=shape) rgb[..., 2] = rng.integers(15000, 35000, size=shape) ir_plane = rng.integers(0, 65535, size=shape, dtype=np.uint16) if ir else None + acquisition = None + validity = None + if ir_plane is not None and 1 <= slot <= 40: + if meter_rgbi is None: + meter_rgbi = rng.integers( + 0, + 65535, + size=(4, 5, 4), + dtype=np.uint16, + ) + storage_rgbi = np.dstack((rgb, ir_plane)) + native_rgbi = np.ascontiguousarray(np.rot90(storage_rgbi, k=-1, axes=(0, 1))) + validity = np.ones(shape, dtype=np.bool_) + native_validity = np.ascontiguousarray(np.rot90(validity, k=-1, axes=(0, 1))) + reservation_id = f"reservation-{slot:03d}" + capture_attempt_id = f"fine-slot-{slot}-attempt-{attempt:03d}" + acquisition_id, evidence_sha256 = roll_service._derive_digital_ice_producer_binding( + slot=slot, + reservation_id=reservation_id, + capture_attempt_id=capture_attempt_id, + main_rgbi=native_rgbi, + prepass_rgbi=meter_rgbi, + ir_validity=native_validity, + ) + acquisition = roll_repair.RepairAcquisition.from_arrays( + acquisition_id=acquisition_id, + slot=slot, + reservation_id=reservation_id, + capture_attempt_id=capture_attempt_id, + storage_transform=roll_repair.DIGITAL_ICE_STORAGE_TRANSFORM, + evidence_sha256=evidence_sha256, + main_rgbi=native_rgbi, + prepass_rgbi=meter_rgbi, + ir_validity=native_validity, + ) receipt = fake_coolscanpy.Receipt(version=1, slot=slot, dpi=4000, depth=16, device_id="usb:1:2", transport_smear_verdict="clean") - return fake_coolscanpy.Frame(slot=slot, rgb=rgb, ir=ir_plane, ir_validity=None, receipt=receipt) + return fake_coolscanpy.Frame( + slot=slot, + rgb=rgb, + ir=ir_plane, + ir_validity=validity, + receipt=receipt, + meter_rgbi=meter_rgbi, + digital_ice_acquisition=acquisition, + ) + + +def _valid_hybrid_result( + acquisition: roll_repair.RepairAcquisition, +) -> roll_repair.RepairResult: + native_rgb = np.ascontiguousarray(acquisition.main_rgbi[..., :3] + 3) + routed_mask = np.zeros(acquisition.main_rgbi.shape[:2], dtype=np.bool_) + routed_mask.reshape(-1)[0] = True + applied_mask = np.ascontiguousarray(routed_mask & acquisition.ir_validity) + + def png(mask: np.ndarray) -> bytes: + stream = io.BytesIO() + Image.fromarray(mask.astype(np.uint8) * 255, mode="L").save( + stream, + format="PNG", + ) + return stream.getvalue() + + routed_png = png(routed_mask) + applied_png = png(applied_mask) + storage_mask = acquisition.storage_mask(applied_mask) + storage_png = png(storage_mask) + native_hash = sha256(native_rgb.astype(" exact_color.VerifiedCMSReceipt: + encoded = payload if type(payload) is bytes else json.dumps(payload, sort_keys=True, separators=(",", ":")).encode("utf-8") + return dataclasses.replace(receipt, payload=encoded, sha256=sha256(encoded).hexdigest()) + + +def _self_attested_cms_receipt(payload: dict) -> exact_color.VerifiedCMSReceipt: + encoded = json.dumps(payload, sort_keys=True, separators=(",", ":")).encode("utf-8") + return exact_color.VerifiedCMSReceipt( + payload=encoded, + sha256=sha256(encoded).hexdigest(), + _factory_token=object(), + ) + + +def _builder_receipt() -> exact_color.ValidatedBuilderReceipt: + identity = np.arange(65_536, dtype=np.uint16) + return make_stage3_replay_receipt((identity, identity, identity)) + + +class _ExactStage1Builder: + def __init__(self) -> None: + self.calls = [] + + def apply(self, rgb, *, builder_receipt): + self.calls.append((rgb, builder_receipt)) + output = rgb.copy() + source_hash = exact_color.rgb16_content_sha256(rgb) + stage1_hash = exact_color.rgb16_content_sha256(output) + return exact_color.Stage1BuilderResult( + rgb=output, + source_rgb_sha256=source_hash, + stage1_input_rgb_sha256=stage1_hash, + builder_receipt=builder_receipt, + application_receipt=_receipt_blob( + exact_color.VerifiedBuilderApplicationReceipt, + { + "builder_receipt_sha256": builder_receipt.sha256, + "fixed_composition": { + "lut_sha256": exact_color.FIXED_COMPOSITION_SHA256, + "order": "F[B_c(i)]", + }, + "kind": "negpy.verified-stage1-builder-application", + "native_per_acquisition_builder": False, + "pre_f_lut_sha256": dict(zip(("r", "g", "b"), builder_receipt.pre_f_lut_sha256, strict=True)), + "source_rgb_sha256": source_hash, + "scope": exact_color.STAGE3_REPLAY_SCOPE, + "stage1_input_rgb_sha256": stage1_hash, + "stage3_receipt_sha256": builder_receipt.stage3_receipt_sha256, + "version": 1, + }, + attested=True, + ), + ) + + +class _ExactColorEvaluator: + def __init__(self) -> None: + self.calls = [] + self.results = [] + self._evaluator = PortableCMSOnEvaluator(chunk_pixels=17) + + def evaluate(self, rgb, *, builder_receipt): + self.calls.append((rgb, builder_receipt)) + result = self._evaluator.evaluate(rgb, builder_receipt=builder_receipt) + self.results.append(result) + return result class TestThreeTierWriting: @@ -216,7 +604,7 @@ def test_no_tier_selected_writes_only_the_receipt(self, fake_coolscanpy, tmp_pat # -- Tier 2 without a registered engine ---------------------------------- - def test_repaired_without_an_engine_degrades_but_unrepaired_still_writes(self, fake_coolscanpy, tmp_path) -> None: + def test_repaired_without_an_engine_degrades_but_unrepaired_still_writes(self, fake_coolscanpy, no_repair_engine, tmp_path) -> None: frame = _tier_frame(fake_coolscanpy) service = RollScanningService() @@ -229,7 +617,270 @@ def test_repaired_without_an_engine_degrades_but_unrepaired_still_writes(self, f assert payload["outputs"]["unrepaired"]["written"] is True assert payload["outputs"]["repaired"] == {"written": False, "status": "unavailable: no dust-repair engine registered"} - def test_positive_without_an_engine_degrades_too_since_it_needs_tier_2(self, fake_coolscanpy, tmp_path) -> None: + def test_tier1_retains_replayable_dice_acquisition_when_repair_is_unavailable( + self, + fake_coolscanpy, + no_repair_engine, + tmp_path, + ) -> None: + frame = _tier_frame(fake_coolscanpy, slot=12) + original = frame.prepare_digital_ice() + archive = tmp_path / "archive" + + output = RollScanningService().write_frame( + frame, + str(archive), + 'nested/{{ "%03d" % seq }}', + write_unrepaired=True, + write_repaired=True, + ) + + receipt = self._receipt(output.receipt_path) + evidence = receipt["outputs"]["repair_acquisition_evidence"] + assert evidence["retained"] is True + assert receipt["outputs"]["repaired"]["written"] is False + replay = roll_service.load_repair_acquisition_evidence(evidence["binding"]["path"]) + assert replay.acquisition_id == original.acquisition_id + assert replay.capture_attempt_id == original.capture_attempt_id + np.testing.assert_array_equal(replay.main_rgbi, original.main_rgbi) + np.testing.assert_array_equal(replay.prepass_rgbi, original.prepass_rgbi) + np.testing.assert_array_equal(replay.ir_validity, original.ir_validity) + + # Artifact references are relative to the binding as well as recorded + # absolutely in the receipt, so moving the complete archive preserves + # its ability to replay after the scanner media is gone. + relative_binding = Path(evidence["binding"]["path"]).relative_to(archive) + moved_archive = tmp_path / "moved-archive" + archive.rename(moved_archive) + moved_replay = roll_service.load_repair_acquisition_evidence(moved_archive / relative_binding) + np.testing.assert_array_equal(moved_replay.main_rgbi, original.main_rgbi) + + def test_tier1_survives_disclosed_dice_evidence_retention_failure( + self, + fake_coolscanpy, + tmp_path, + monkeypatch, + ) -> None: + real_atomic_write_bytes = roll_service._atomic_write_bytes + + def fail_prepass(path, payload): + if path.endswith("prepass.rgbi16.npy"): + raise OSError("synthetic evidence volume failure") + return real_atomic_write_bytes(path, payload) + + monkeypatch.setattr(roll_service, "_atomic_write_bytes", fail_prepass) + output = RollScanningService().write_frame( + _tier_frame(fake_coolscanpy, slot=13), + str(tmp_path), + '{{ "%03d" % seq }}', + write_unrepaired=True, + write_repaired=False, + ) + + assert output.rgb_path is not None and Path(output.rgb_path).is_file() + assert output.ir_path is not None and Path(output.ir_path).is_file() + evidence = self._receipt(output.receipt_path)["outputs"]["repair_acquisition_evidence"] + assert evidence["retained"] is False + assert "synthetic evidence volume failure" in evidence["status"] + assert not (tmp_path / ".negpy-dice-acquisition").exists() + + @pytest.mark.parametrize( + ("tamper", "match"), + [ + ("producer-hash", "producer evidence SHA-256 changed"), + ("missing-prepass", "No such file"), + ("symlink-prepass", "regular non-symlink"), + ("storage-rgb", "storage RGB SHA-256 changed"), + ], + ) + def test_dice_acquisition_replay_rejects_tampered_or_missing_archive_parts( + self, + fake_coolscanpy, + tmp_path, + tamper, + match, + ) -> None: + output = RollScanningService().write_frame( + _tier_frame(fake_coolscanpy, slot=14), + str(tmp_path), + '{{ "%03d" % seq }}', + write_unrepaired=True, + write_repaired=False, + ) + evidence = self._receipt(output.receipt_path)["outputs"]["repair_acquisition_evidence"] + binding_path = Path(evidence["binding"]["path"]) + document = json.loads(binding_path.read_bytes()) + assert str(tmp_path) not in binding_path.read_text(encoding="utf-8") + + if tamper == "producer-hash": + document["acquisition"]["evidence_sha256"] = "0" * 64 + binding_path.write_bytes( + json.dumps( + document, + sort_keys=True, + separators=(",", ":"), + ).encode() + + b"\n" + ) + elif tamper in {"missing-prepass", "symlink-prepass"}: + relative = document["artifacts"]["prepass_rgbi"]["relative_path"] + prepass_path = (binding_path.parent / relative).resolve() + if tamper == "missing-prepass": + prepass_path.unlink() + else: + real_prepass = prepass_path.with_suffix(".real") + prepass_path.rename(real_prepass) + prepass_path.symlink_to(real_prepass.name) + else: + assert output.rgb_path is not None + tifffile.imwrite( + output.rgb_path, + np.zeros_like(tifffile.imread(output.rgb_path)), + photometric="rgb", + ) + + with pytest.raises((OSError, ValueError), match=match): + roll_service.load_repair_acquisition_evidence(binding_path) + + @pytest.mark.skipif(not hasattr(os, "mkfifo"), reason="POSIX FIFO test") + def test_dice_replay_tiff_regular_to_fifo_swap_never_blocks( + self, + fake_coolscanpy, + tmp_path, + monkeypatch, + ) -> None: + output = RollScanningService().write_frame( + _tier_frame(fake_coolscanpy, slot=14), + str(tmp_path), + '{{ "%03d" % seq }}', + write_unrepaired=True, + ) + evidence = self._receipt(output.receipt_path)["outputs"]["repair_acquisition_evidence"] + binding_path = Path(evidence["binding"]["path"]) + rgb_path = Path(output.rgb_path) + real_open = roll_service.os.open + swapped = False + + def swap_before_open(path, flags, *args): + nonlocal swapped + if not swapped and Path(path) == rgb_path: + swapped = True + rgb_path.unlink() + os.mkfifo(rgb_path) + return real_open(path, flags, *args) + + monkeypatch.setattr(roll_service.os, "open", swap_before_open) + started = time.monotonic() + with pytest.raises(ValueError, match="regular non-symlink|changed"): + roll_service.load_repair_acquisition_evidence(binding_path) + assert time.monotonic() - started < 1.0 + + def test_dice_replay_rejects_npy_header_before_array_allocation( + self, + fake_coolscanpy, + tmp_path, + monkeypatch, + ) -> None: + output = RollScanningService().write_frame( + _tier_frame(fake_coolscanpy, slot=14), + str(tmp_path), + '{{ "%03d" % seq }}', + write_unrepaired=True, + ) + evidence = self._receipt(output.receipt_path)["outputs"]["repair_acquisition_evidence"] + binding_path = Path(evidence["binding"]["path"]) + document = json.loads(binding_path.read_bytes()) + prepass_row = document["artifacts"]["prepass_rgbi"] + prepass_path = binding_path.parent / prepass_row["relative_path"] + malicious = io.BytesIO() + np.lib.format.write_array_header_1_0( + malicious, + { + "descr": " None: + service = RollScanningService() + first = service.write_frame( + _tier_frame(fake_coolscanpy, slot=15, attempt=1), + str(tmp_path), + '{{ "%03d" % seq }}', + write_unrepaired=True, + ) + first_evidence = self._receipt(first.receipt_path)["outputs"]["repair_acquisition_evidence"] + old_directory = Path(first_evidence["binding"]["path"]).parent + assert old_directory.is_dir() + + second = service.write_frame( + _tier_frame(fake_coolscanpy, slot=15, seed=2, attempt=2), + str(tmp_path), + '{{ "%03d" % seq }}', + write_unrepaired=True, + ) + second_evidence = self._receipt(second.receipt_path)["outputs"]["repair_acquisition_evidence"] + + assert Path(second_evidence["binding"]["path"]).parent != old_directory + assert not old_directory.exists() + assert Path(second_evidence["binding"]["path"]).is_file() + + def test_rescan_preserves_dice_evidence_owned_by_a_sibling_receipt( + self, + fake_coolscanpy, + tmp_path, + ) -> None: + service = RollScanningService() + first = service.write_frame( + _tier_frame(fake_coolscanpy, slot=16, attempt=1), + str(tmp_path), + '{{ "%03d" % seq }}', + write_unrepaired=True, + ) + first_evidence = self._receipt(first.receipt_path)["outputs"]["repair_acquisition_evidence"] + old_binding = Path(first_evidence["binding"]["path"]) + sibling_receipt = tmp_path / "shared_receipt.json" + sibling_receipt.write_text( + json.dumps({"outputs": {"shared_dice": first_evidence}}), + encoding="utf-8", + ) + + with pytest.raises(OSError, match="owned by another receipt"): + service.write_frame( + _tier_frame(fake_coolscanpy, slot=16, seed=3, attempt=2), + str(tmp_path), + '{{ "%03d" % seq }}', + write_unrepaired=True, + ) + + assert old_binding.is_file() + replay = roll_service.load_repair_acquisition_evidence(old_binding) + assert replay.capture_attempt_id.endswith("001") + + def test_positive_without_an_engine_degrades_too_since_it_needs_tier_2(self, fake_coolscanpy, no_repair_engine, tmp_path) -> None: frame = _tier_frame(fake_coolscanpy) service = RollScanningService() @@ -245,6 +896,467 @@ def test_positive_without_an_engine_degrades_too_since_it_needs_tier_2(self, fak # -- Tier 2 with a registered engine ------------------------------------- + def test_repair_runs_scanner_native_then_rotates_once_to_storage(self, fake_coolscanpy, fake_repair_engine, tmp_path) -> None: + native_main = np.arange(3 * 2 * 4, dtype=np.uint16).reshape(3, 2, 4) + storage_rgbi = np.ascontiguousarray(np.rot90(native_main, k=1, axes=(0, 1))) + meter = np.arange(2 * 2 * 4, dtype=np.uint16).reshape(2, 2, 4) + validity = np.ones(native_main.shape[:2], dtype=np.bool_) + validity[-1, 0] = False + acquisition_id, evidence_sha256 = roll_service._derive_digital_ice_producer_binding( + slot=11, + reservation_id="reservation-011", + capture_attempt_id="fine-slot-11-attempt-001", + main_rgbi=native_main, + prepass_rgbi=meter, + ir_validity=validity, + ) + acquisition = roll_repair.RepairAcquisition.from_arrays( + acquisition_id=acquisition_id, + slot=11, + reservation_id="reservation-011", + capture_attempt_id="fine-slot-11-attempt-001", + storage_transform=roll_repair.DIGITAL_ICE_STORAGE_TRANSFORM, + evidence_sha256=evidence_sha256, + main_rgbi=native_main, + prepass_rgbi=meter, + ir_validity=validity, + ) + receipt = fake_coolscanpy.Receipt( + version=1, + slot=11, + dpi=4000, + depth=16, + device_id="usb:1:2", + transport_smear_verdict="clean", + ) + + class _Frame: + slot = 11 + rgb = storage_rgbi[..., :3] + ir = storage_rgbi[..., 3] + ir_validity = np.rot90(validity, k=1, axes=(0, 1)) + meter_rgbi = meter + + def __init__(self) -> None: + self.receipt = receipt + + def prepare_digital_ice(self): + return acquisition + + frame = _Frame() + fake_repair_engine.transform = lambda rgb: np.ascontiguousarray(rgb + 17) + + output = RollScanningService().write_frame( + frame, + str(tmp_path), + '{{ "%03d" % seq }}', + write_unrepaired=False, + write_repaired=True, + ) + + assert output.repaired_rgb_path is not None + seen = fake_repair_engine.calls[0][0] + np.testing.assert_array_equal(seen.main_rgbi, native_main) + np.testing.assert_array_equal(seen.prepass_rgbi, meter) + np.testing.assert_array_equal(seen.ir_validity, validity) + expected = np.ascontiguousarray(np.rot90(native_main[..., :3] + 17, k=1, axes=(0, 1))) + np.testing.assert_array_equal( + tifffile.imread(output.repaired_rgb_path), + expected, + ) + + def test_hybrid_persists_output_aligned_mask_and_native_verification_evidence( + self, fake_coolscanpy, fake_repair_engine, tmp_path, monkeypatch + ) -> None: + frame = _tier_frame(fake_coolscanpy) + acquisition = frame.prepare_digital_ice() + native_rgb = np.ascontiguousarray(acquisition.main_rgbi[..., :3] + 3) + native_mask = np.zeros(acquisition.main_rgbi.shape[:2], dtype=np.bool_) + native_mask[2, 4] = True + native_stream = io.BytesIO() + Image.fromarray(native_mask.astype(np.uint8) * 255, mode="L").save( + native_stream, + format="PNG", + ) + native_mask_png = native_stream.getvalue() + storage_mask = acquisition.storage_mask(native_mask) + storage_stream = io.BytesIO() + Image.fromarray(storage_mask.astype(np.uint8) * 255, mode="L").save( + storage_stream, + format="PNG", + ) + storage_mask_png = storage_stream.getvalue() + hybrid_output_hash = sha256(native_rgb.astype(" None: + acquisition = _tier_frame(fake_coolscanpy).prepare_digital_ice() + result = _valid_hybrid_result(acquisition) + + if tamper == "mode": + result = dataclasses.replace(result, mode_resolved=None) + elif tamper == "native-output": + result = dataclasses.replace( + result, + native_output_rgb_sha256="0" * 64, + ) + elif tamper in { + "receipt-output", + "receipt-composite", + "routing-regions", + }: + document = json.loads(result.hybrid_receipt) + routing_counts = result.routing_counts + if tamper == "receipt-output": + document["artifacts"][0]["raw_sha256"] = "0" * 64 + elif tamper == "receipt-composite": + document["composite"]["hybrid_rgb16_raw_sha256"] = "0" * 64 + else: + document["routing"]["counts"]["final_regions"] = 2 + routing_counts = {**routing_counts, "final_regions": 2} + receipt = ( + json.dumps( + document, + sort_keys=True, + separators=(",", ":"), + ).encode() + + b"\n" + ) + result = dataclasses.replace( + result, + hybrid_receipt=receipt, + hybrid_receipt_sha256=sha256(receipt).hexdigest(), + routing_counts=routing_counts, + ) + elif tamper == "noncanonical-receipt": + receipt = json.dumps( + json.loads(result.hybrid_receipt), + indent=2, + ).encode() + result = dataclasses.replace( + result, + hybrid_receipt=receipt, + hybrid_receipt_sha256=sha256(receipt).hexdigest(), + ) + else: + routed = np.zeros(acquisition.main_rgbi.shape[:2], dtype=np.bool_) + routed.reshape(-1)[1] = True + applied = np.ascontiguousarray(routed & acquisition.ir_validity) + + def png(mask): + stream = io.BytesIO() + Image.fromarray(mask.astype(np.uint8) * 255, mode="L").save( + stream, + format="PNG", + ) + return stream.getvalue() + + routed_png = png(routed) + applied_png = png(applied) + storage_png = png(acquisition.storage_mask(applied)) + result = dataclasses.replace( + result, + routed_native_synthesis_mask_png=routed_png, + routed_native_synthesis_mask_sha256=sha256(routed_png).hexdigest(), + native_synthesis_mask_png=applied_png, + native_synthesis_mask_sha256=sha256(applied_png).hexdigest(), + storage_synthesis_mask_png=storage_png, + storage_synthesis_mask_sha256=sha256(storage_png).hexdigest(), + ) + + with pytest.raises(ValueError, match=match): + roll_service._validate_repair_result_binding( + acquisition, + result, + requested_mode=roll_repair.RepairMode.HYBRID, + ) + + def test_service_snapshots_mutable_hybrid_result_before_validation_and_write( + self, + fake_coolscanpy, + fake_repair_engine, + tmp_path, + monkeypatch, + ) -> None: + frame = _tier_frame(fake_coolscanpy, slot=18) + acquisition = frame.prepare_digital_ice() + valid = _valid_hybrid_result(acquisition) + mutable_rgb = np.array(valid.rgb, copy=True) + mutable_counts = dict(valid.routing_counts) + returned = dataclasses.replace( + valid, + rgb=mutable_rgb, + routing_counts=mutable_counts, + ) + expected = mutable_rgb.copy() + fake_repair_engine.repair = lambda *_args, **_kwargs: returned + real_validate = roll_service._validate_repair_result_binding + + def mutate_producer_after_snapshot(acquisition, result, *, requested_mode): + mutable_rgb.fill(0) + mutable_counts["synthesis_pixels"] = 999 + return real_validate( + acquisition, + result, + requested_mode=requested_mode, + ) + + monkeypatch.setattr( + roll_service, + "_validate_repair_result_binding", + mutate_producer_after_snapshot, + ) + output = RollScanningService().write_frame( + frame, + str(tmp_path), + '{{ "%03d" % seq }}', + write_unrepaired=False, + write_repaired=True, + repair_mode="hybrid", + ) + + assert output.repaired_rgb_path is not None + np.testing.assert_array_equal( + tifffile.imread(output.repaired_rgb_path), + expected, + ) + + def test_malformed_repair_result_degrades_without_losing_tier1( + self, + fake_coolscanpy, + fake_repair_engine, + tmp_path, + ) -> None: + frame = _tier_frame(fake_coolscanpy, slot=18) + malformed = dataclasses.replace( + _valid_hybrid_result(frame.prepare_digital_ice()), + rgb=object(), + ) + fake_repair_engine.repair = lambda *_args, **_kwargs: malformed + + output = RollScanningService().write_frame( + frame, + str(tmp_path), + '{{ "%03d" % seq }}', + write_unrepaired=True, + write_repaired=True, + repair_mode="hybrid", + ) + + assert output.rgb_path is not None and Path(output.rgb_path).is_file() + assert output.repaired_rgb_path is None + repaired = self._receipt(output.receipt_path)["outputs"]["repaired"] + assert repaired["written"] is False + assert "repair evidence failed" in repaired["status"] + + def test_tier2_only_rejects_noncanonical_producer_evidence_before_engine( + self, + fake_coolscanpy, + fake_repair_engine, + tmp_path, + ) -> None: + frame = _tier_frame(fake_coolscanpy, slot=18) + forged = dataclasses.replace( + frame.prepare_digital_ice(), + evidence_sha256="0" * 64, + ) + frame = dataclasses.replace(frame, digital_ice_acquisition=forged) + + output = RollScanningService().write_frame( + frame, + str(tmp_path), + '{{ "%03d" % seq }}', + write_unrepaired=False, + write_repaired=True, + ) + + assert output.repaired_rgb_path is None + assert fake_repair_engine.calls == [] + repaired = self._receipt(output.receipt_path)["outputs"]["repaired"] + assert repaired["written"] is False + assert "producer evidence SHA-256 changed" in repaired["status"] + def test_repaired_writes_rgb_and_retains_original_ir(self, fake_coolscanpy, fake_repair_engine, tmp_path) -> None: fake_repair_engine.transform = lambda rgb: np.clip(rgb.astype(np.int32) + 1000, 0, 65535).astype(np.uint16) frame = _tier_frame(fake_coolscanpy) @@ -263,6 +1375,938 @@ def test_repaired_writes_rgb_and_retains_original_ir(self, fake_coolscanpy, fake ir_readback = tifffile.imread(output.repaired_ir_path) np.testing.assert_array_equal(ir_readback, frame.ir) # Tier 1's own IR, unchanged + def test_transaction_failure_keeps_prior_artifacts_and_receipt( + self, + fake_coolscanpy, + fake_repair_engine, + tmp_path, + monkeypatch, + ) -> None: + first = _tier_frame(fake_coolscanpy, slot=13, seed=1) + service = RollScanningService() + published = service.write_frame( + first, + str(tmp_path), + '{{ "%03d" % seq }}', + write_unrepaired=True, + write_repaired=True, + ) + prior_receipt = Path(published.receipt_path).read_bytes() + prior_repaired = Path(published.repaired_rgb_path).read_bytes() + second = _tier_frame(fake_coolscanpy, slot=13, seed=2) + + def fail_receipt(_path, _payload): + raise OSError("synthetic receipt staging failure") + + monkeypatch.setattr(roll_service, "_atomic_write_json", fail_receipt) + with pytest.raises(OSError, match="receipt staging failure"): + service.write_frame( + second, + str(tmp_path), + '{{ "%03d" % seq }}', + write_unrepaired=True, + write_repaired=True, + ) + + assert Path(published.receipt_path).read_bytes() == prior_receipt + assert Path(published.repaired_rgb_path).read_bytes() == prior_repaired + assert not any(path.name.startswith(".negpy-frame-stage-") for path in tmp_path.iterdir()) + + def test_frame_lock_contention_fails_closed_without_blocking( + self, + tmp_path, + ) -> None: + receipt_path = str(tmp_path / "013_receipt.json") + first = roll_service._OutputTransaction(str(tmp_path)) + second = roll_service._OutputTransaction(str(tmp_path)) + first._acquire_frame_lock(receipt_path) + started = time.monotonic() + try: + with pytest.raises(OSError, match="busy in another NegPy process"): + second._acquire_frame_lock(receipt_path) + assert time.monotonic() - started < 0.5 + finally: + second.abort() + first.abort() + + # The failed contender closed its descriptor and the owner released + # normally, so a later transaction can acquire the same frame lock. + retry = roll_service._OutputTransaction(str(tmp_path)) + try: + retry._acquire_frame_lock(receipt_path) + finally: + retry.abort() + assert not any(path.name.startswith(".negpy-frame-stage-") for path in tmp_path.iterdir()) + + def test_frame_publication_fails_closed_when_platform_has_no_fcntl( + self, + tmp_path, + monkeypatch, + ) -> None: + transaction = roll_service._OutputTransaction(str(tmp_path)) + monkeypatch.setattr(roll_service, "fcntl", None) + try: + with pytest.raises(OSError, match="locking is unavailable"): + transaction._acquire_frame_lock(str(tmp_path / "013_receipt.json")) + finally: + transaction.abort() + assert not any(path.name.startswith(".negpy-frame-stage-") for path in tmp_path.iterdir()) + + def test_receipt_commit_failure_rolls_back_new_derived_artifacts( + self, + fake_coolscanpy, + fake_repair_engine, + tmp_path, + monkeypatch, + ) -> None: + service = RollScanningService() + first = service.write_frame( + _tier_frame(fake_coolscanpy, slot=14, seed=1), + str(tmp_path), + '{{ "%03d" % seq }}', + write_unrepaired=True, + write_repaired=True, + ) + prior_receipt = Path(first.receipt_path).read_bytes() + prior_repaired = Path(first.repaired_rgb_path).read_bytes() + real_replace = roll_service.os.replace + failed = False + + def replace_with_failed_receipt_commit(source, destination): + nonlocal failed + if ( + not failed + and os.path.abspath(destination) == os.path.abspath(first.receipt_path) + and ".negpy-frame-stage-" in os.path.abspath(source) + and "backups" not in os.path.abspath(source) + ): + failed = True + raise OSError("synthetic receipt commit failure") + return real_replace(source, destination) + + monkeypatch.setattr(roll_service.os, "replace", replace_with_failed_receipt_commit) + with pytest.raises(OSError, match="receipt commit failure"): + service.write_frame( + _tier_frame(fake_coolscanpy, slot=14, seed=2), + str(tmp_path), + '{{ "%03d" % seq }}', + write_unrepaired=True, + write_repaired=True, + ) + + assert failed is True + assert Path(first.receipt_path).read_bytes() == prior_receipt + assert Path(first.repaired_rgb_path).read_bytes() == prior_repaired + + def test_incomplete_rollback_retains_prior_file_in_recovery_directory( + self, + fake_coolscanpy, + fake_repair_engine, + tmp_path, + monkeypatch, + ) -> None: + service = RollScanningService() + first = service.write_frame( + _tier_frame(fake_coolscanpy, slot=15, seed=1), + str(tmp_path), + '{{ "%03d" % seq }}', + write_unrepaired=True, + write_repaired=True, + ) + prior_repaired = Path(first.repaired_rgb_path).read_bytes() + real_replace = roll_service.os.replace + receipt_commit_failed = False + repair_restore_failed = False + + def fail_commit_and_one_restore(source, destination): + nonlocal receipt_commit_failed, repair_restore_failed + source_path = os.path.abspath(source) + destination_path = os.path.abspath(destination) + if ( + not receipt_commit_failed + and destination_path == os.path.abspath(first.receipt_path) + and ".negpy-frame-stage-" in source_path + and "backups" not in source_path + ): + receipt_commit_failed = True + raise OSError("synthetic receipt commit failure") + if ( + receipt_commit_failed + and not repair_restore_failed + and destination_path == os.path.abspath(first.repaired_rgb_path) + and "backups" in source_path + ): + repair_restore_failed = True + raise OSError("synthetic repaired restore failure") + return real_replace(source, destination) + + monkeypatch.setattr( + roll_service.os, + "replace", + fail_commit_and_one_restore, + ) + with pytest.raises(roll_service.OutputRollbackError) as raised: + service.write_frame( + _tier_frame(fake_coolscanpy, slot=15, seed=2), + str(tmp_path), + '{{ "%03d" % seq }}', + write_unrepaired=True, + write_repaired=True, + ) + + assert receipt_commit_failed is True + assert repair_restore_failed is True + recovery = Path(raised.value.recovery_path) + assert recovery.is_dir() + assert recovery.name.startswith(".negpy-recovery-") + manifest = json.loads((recovery / "RECOVERY.json").read_text()) + backup_relative = manifest["unrestored_backups"][os.path.abspath(first.repaired_rgb_path)] + assert (recovery / backup_relative).read_bytes() == prior_repaired + + def test_output_parent_symlink_cannot_escape_selected_folder( + self, + fake_coolscanpy, + fake_repair_engine, + tmp_path, + ) -> None: + outside = tmp_path.parent / f"{tmp_path.name}-outside" + outside.mkdir() + (tmp_path / "sub").symlink_to(outside, target_is_directory=True) + + with pytest.raises(OSError, match="symbolic link"): + RollScanningService().write_frame( + _tier_frame(fake_coolscanpy, slot=16), + str(tmp_path), + 'sub/{{ "%03d" % seq }}', + write_unrepaired=True, + ) + + assert list(outside.iterdir()) == [] + assert not any(path.name.startswith(".negpy-frame-stage-") for path in tmp_path.iterdir()) + + def test_unreferenced_staged_output_aborts_publication( + self, + fake_coolscanpy, + tmp_path, + monkeypatch, + ) -> None: + real_atomic_write_json = roll_service._atomic_write_json + + def stage_orphan_before_receipt(path, payload): + roll_service._atomic_write_bytes( + str(tmp_path / "unreferenced.bin"), + b"orphan", + ) + return real_atomic_write_json(path, payload) + + monkeypatch.setattr( + roll_service, + "_atomic_write_json", + stage_orphan_before_receipt, + ) + with pytest.raises(OSError, match="absent from its receipt"): + RollScanningService().write_frame( + _tier_frame(fake_coolscanpy, slot=17), + str(tmp_path), + '{{ "%03d" % seq }}', + write_unrepaired=True, + ) + + assert not (tmp_path / "unreferenced.bin").exists() + assert not any(path.name.endswith("_receipt.json") for path in tmp_path.iterdir()) + assert not any(path.name.startswith(".negpy-frame-stage-") for path in tmp_path.iterdir()) + + @pytest.mark.skipif(not hasattr(os, "mkfifo"), reason="POSIX FIFO test") + def test_existing_fifo_receipt_fails_without_blocking_or_publishing( + self, + fake_coolscanpy, + tmp_path, + ) -> None: + receipt_path = tmp_path / "019_receipt.json" + os.mkfifo(receipt_path) + started = time.monotonic() + + with pytest.raises(OSError, match="regular non-symlink"): + RollScanningService().write_frame( + _tier_frame(fake_coolscanpy, slot=19), + str(tmp_path), + '{{ "%03d" % seq }}', + write_unrepaired=True, + ) + + assert time.monotonic() - started < 1.0 + assert not (tmp_path / "019.tif").exists() + assert not (tmp_path / "019_IR.tif").exists() + + @pytest.mark.parametrize( + "hostile_receipt", + [ + b'{"outputs":{"first":{"path":"owned"}},"outputs":{}}', + b'{"outputs":{"value":NaN}}', + ], + ) + def test_unsafe_sibling_receipt_conservatively_blocks_overwrite( + self, + fake_coolscanpy, + tmp_path, + hostile_receipt, + ) -> None: + (tmp_path / "hostile_receipt.json").write_bytes(hostile_receipt) + + with pytest.raises(OSError, match="owned by another receipt"): + RollScanningService().write_frame( + _tier_frame(fake_coolscanpy, slot=20), + str(tmp_path), + '{{ "%03d" % seq }}', + write_unrepaired=True, + ) + + assert not (tmp_path / "020.tif").exists() + assert not any(path.name.startswith(".negpy-frame-stage-") for path in tmp_path.iterdir()) + + def test_receipt_rejects_non_json_values_instead_of_stringifying_them( + self, + fake_coolscanpy, + tmp_path, + ) -> None: + @dataclasses.dataclass(frozen=True) + class InvalidReceipt: + version: int + slot: int + opaque: object + + frame = dataclasses.replace( + _tier_frame(fake_coolscanpy, slot=18), + receipt=InvalidReceipt(version=1, slot=18, opaque=object()), + ) + + with pytest.raises(TypeError, match="not JSON serializable"): + RollScanningService().write_frame( + frame, + str(tmp_path), + '{{ "%03d" % seq }}', + write_unrepaired=True, + ) + + assert not any(path.name.endswith("_receipt.json") for path in tmp_path.iterdir()) + assert not any(path.name.endswith(".tif") for path in tmp_path.iterdir()) + + def test_repaired_forwards_the_coolscanpy_meter_prepass_unchanged(self, fake_coolscanpy, fake_repair_engine, tmp_path) -> None: + prepass = np.zeros((4, 5, 4), dtype=np.uint16) + frame = _tier_frame( + fake_coolscanpy, + ir=True, + meter_rgbi=prepass, + ) + + RollScanningService().write_frame(frame, str(tmp_path), '{{ "%03d" % seq }}', write_unrepaired=False, write_repaired=True) + + assert len(fake_repair_engine.prepasses) == 1 + np.testing.assert_array_equal(fake_repair_engine.prepasses[0], prepass) + + def test_exact_nikon_positive_uses_injected_evaluator_and_binds_both_receipts( + self, fake_coolscanpy, fake_repair_engine, tmp_path + ) -> None: + fake_repair_engine.transform = lambda rgb: np.clip(rgb.astype(np.int32) + 731, 0, 65535).astype(np.uint16) + builder = _ExactStage1Builder() + evaluator = _ExactColorEvaluator() + builder_receipt = _builder_receipt() + frame = _tier_frame(fake_coolscanpy) + + output = RollScanningService( + exact_color_builder=builder, + exact_color_evaluator=evaluator, + ).write_frame( + frame, + str(tmp_path), + '{{ "%03d" % seq }}', + write_unrepaired=False, + write_positive=True, + positive_mode="nikon-exact", + builder_receipt=builder_receipt, + ) + + assert output.positive_path is not None + repaired = fake_repair_engine.transform(frame.rgb) + readback = tifffile.imread(output.positive_path) + assert readback.dtype == np.uint16 + np.testing.assert_array_equal(readback, evaluator.results[0].rgb) + with tifffile.TiffFile(output.positive_path) as exact_tiff: + embedded_profile = exact_tiff.pages[0].tags[34675].value + assert len(embedded_profile) == 492 + assert sha256(embedded_profile).hexdigest() == "a8d0d753bd6129357cc2647435ce675e8637a679eb526fa180fba460874ce1d3" + positive_receipt = self._receipt(output.receipt_path)["outputs"]["positive"] + tiff_artifact = positive_receipt["tiff_artifact"] + assert tiff_artifact["file_sha256"] == sha256(Path(output.positive_path).read_bytes()).hexdigest() + assert tiff_artifact["pixel_sha256"] == exact_color.rgb16_content_sha256(readback) + assert tiff_artifact["icc_sha256"] == sha256(embedded_profile).hexdigest() + assert tiff_artifact["page_count"] == 1 + assert tiff_artifact["bits_per_sample"] == [16, 16, 16] + assert tiff_artifact["orientation"] == "top-left" + np.testing.assert_array_equal(builder.calls[0][0], repaired) + np.testing.assert_array_equal(evaluator.calls[0][0], repaired) + entry = self._receipt(output.receipt_path)["outputs"]["positive"] + assert entry["color_mode"] == "nikon-exact" + assert entry["input_rgb_sha256"] == exact_color.rgb16_content_sha256(repaired) + assert entry["output_rgb_sha256"] == exact_color.rgb16_content_sha256(readback) + assert entry["builder_receipt_sha256"] == builder_receipt.sha256 + assert entry["builder_receipt"]["schema"] == exact_color.BUILDER_RECEIPT_SCHEMA + assert entry["builder_application_receipt"]["stage1_input_rgb_sha256"] == entry["input_rgb_sha256"] + assert entry["cms_receipt"]["algorithm"] == exact_color.CMS_ALGORITHM_ID + assert entry["cms_receipt"]["validation"]["mismatched_u16"] == 0 + assert entry["cms_receipt"]["input_rgb_sha256"] == entry["input_rgb_sha256"] + assert entry["icc_profile"] == { + "name": "Nikon Adobe RGB 4.0.0.3000", + "bytes": 492, + "sha256": "a8d0d753bd6129357cc2647435ce675e8637a679eb526fa180fba460874ce1d3", + } + evidence = entry["retained_builder_evidence"] + assert evidence["scope"] == exact_color.STAGE3_REPLAY_SCOPE + assert evidence["native_per_acquisition_builder"] is False + rows = [evidence["stage3_report"], *evidence["pre_f_luts"]] + for row in rows: + payload = Path(row["path"]).read_bytes() + assert len(payload) == row["bytes"] + assert sha256(payload).hexdigest() == row["sha256"] + reproduced = exact_color.load_stage3_replay_builder_receipt(evidence["stage3_report"]["path"]) + assert reproduced.sha256 == builder_receipt.sha256 + assert reproduced.pre_f_lut_sha256 == builder_receipt.pre_f_lut_sha256 + + @pytest.mark.parametrize("corruption", ["pixels", "icc", "orientation"]) + def test_exact_positive_rejects_tiff_write_corruption_before_publication( + self, + corruption, + fake_coolscanpy, + fake_repair_engine, + tmp_path, + monkeypatch, + ) -> None: + builder = _ExactStage1Builder() + evaluator = _ExactColorEvaluator() + real_imwrite = roll_service.tifffile.imwrite + + def corrupting_imwrite(path, array, **kwargs): + if kwargs.get("iccprofile") is not None: + if corruption == "pixels": + array = np.array(array, copy=True) + array[0, 0, 0] ^= np.uint16(1) + elif corruption == "icc": + kwargs.pop("iccprofile") + else: + kwargs["extratags"] = ((274, "H", 1, 6, False),) + return real_imwrite(path, array, **kwargs) + + monkeypatch.setattr(roll_service.tifffile, "imwrite", corrupting_imwrite) + output = RollScanningService( + exact_color_builder=builder, + exact_color_evaluator=evaluator, + ).write_frame( + _tier_frame(fake_coolscanpy), + str(tmp_path), + '{{ "%03d" % seq }}', + write_unrepaired=True, + write_repaired=True, + write_positive=True, + positive_mode="nikon-exact", + builder_receipt=_builder_receipt(), + ) + + assert output.rgb_path is not None and Path(output.rgb_path).is_file() + assert output.repaired_rgb_path is not None and Path(output.repaired_rgb_path).is_file() + assert output.positive_path is None + receipt = self._receipt(output.receipt_path) + assert receipt["outputs"]["unrepaired"]["written"] is True + assert receipt["outputs"]["repaired"]["written"] is True + positive = receipt["outputs"]["positive"] + assert positive["written"] is False + assert "unavailable: exact Nikon color" in positive["status"] + assert "exact_nikon_color" not in positive + assert "retained_builder_evidence" not in positive + assert not any(path.name.endswith("_positive.tif") for path in tmp_path.iterdir()) + assert not (tmp_path / ".negpy-stage3-replay").exists() + assert not (tmp_path / ".negpy-native-builder").exists() + assert not any(path.name.startswith(".negpy-frame-stage-") for path in tmp_path.iterdir()) + + def test_exact_positive_detects_path_swap_after_stable_read( + self, + fake_coolscanpy, + fake_repair_engine, + tmp_path, + monkeypatch, + ) -> None: + real_lstat = roll_service.os.lstat + positive_lstats = 0 + + def swap_before_final_identity_check(path, *args, **kwargs): + nonlocal positive_lstats + path_string = os.fspath(path) + if path_string.endswith("_positive.tif"): + positive_lstats += 1 + if positive_lstats == 2: + replacement = path_string + ".swapped" + Path(replacement).write_bytes(b"not the verified TIFF") + os.replace(replacement, path_string) + return real_lstat(path, *args, **kwargs) + + monkeypatch.setattr( + roll_service.os, + "lstat", + swap_before_final_identity_check, + ) + output = RollScanningService( + exact_color_builder=_ExactStage1Builder(), + exact_color_evaluator=_ExactColorEvaluator(), + ).write_frame( + _tier_frame(fake_coolscanpy), + str(tmp_path), + '{{ "%03d" % seq }}', + write_unrepaired=True, + write_repaired=True, + write_positive=True, + positive_mode="nikon-exact", + builder_receipt=_builder_receipt(), + ) + + assert positive_lstats >= 2 + assert output.rgb_path is not None and Path(output.rgb_path).is_file() + assert output.repaired_rgb_path is not None + assert output.positive_path is None + positive = self._receipt(output.receipt_path)["outputs"]["positive"] + assert "changed while it was verified" in positive["status"] + assert not any(path.name.endswith("_positive.tif") for path in tmp_path.iterdir()) + + @pytest.mark.skipif(not hasattr(os, "mkfifo"), reason="POSIX FIFO test") + def test_exact_positive_regular_to_fifo_swap_never_blocks( + self, + fake_coolscanpy, + fake_repair_engine, + tmp_path, + monkeypatch, + ) -> None: + real_open = roll_service.os.open + builder_receipt = _builder_receipt() + swapped = False + + def swap_before_open(path, flags, *args, **kwargs): + nonlocal swapped + path_string = os.fspath(path) + if not swapped and path_string.endswith("_positive.tif"): + swapped = True + os.unlink(path_string) + os.mkfifo(path_string) + return real_open(path, flags, *args, **kwargs) + + monkeypatch.setattr(roll_service.os, "open", swap_before_open) + started = time.monotonic() + output = RollScanningService( + exact_color_builder=_ExactStage1Builder(), + exact_color_evaluator=_ExactColorEvaluator(), + ).write_frame( + _tier_frame(fake_coolscanpy), + str(tmp_path), + '{{ "%03d" % seq }}', + write_unrepaired=True, + write_repaired=True, + write_positive=True, + positive_mode="nikon-exact", + builder_receipt=builder_receipt, + ) + + assert swapped is True + assert time.monotonic() - started < 1.0 + assert output.positive_path is None + assert not any(path.name.endswith("_positive.tif") for path in tmp_path.iterdir()) + + def test_omitted_c41_positive_mode_defaults_to_fail_closed_nikon_exact( + self, fake_coolscanpy, fake_repair_engine, tmp_path, monkeypatch + ) -> None: + monkeypatch.setattr( + roll_service.roll_positive, + "render_positive", + lambda *_args, **_kwargs: pytest.fail("approximate color must require explicit selection"), + ) + + output = RollScanningService().write_frame( + _tier_frame(fake_coolscanpy), + str(tmp_path), + '{{ "%03d" % seq }}', + write_unrepaired=False, + write_positive=True, + ) + + assert output.positive_path is None + entry = self._receipt(output.receipt_path)["outputs"]["positive"] + assert entry["written"] is False + assert entry["color_mode"] == "nikon-exact" + assert "frame has no native builder evidence" in entry["status"] + + def test_exact_nikon_positive_fails_closed_when_embedded_icc_identity_is_invalid( + self, fake_coolscanpy, fake_repair_engine, tmp_path, monkeypatch + ) -> None: + monkeypatch.setattr( + roll_service.roll_nikon_icc, + "nikon_adobe_rgb_profile", + MagicMock(side_effect=roll_service.roll_nikon_icc.NikonICCProfileError("profile hash mismatch")), + ) + + output = RollScanningService( + exact_color_builder=_ExactStage1Builder(), + exact_color_evaluator=_ExactColorEvaluator(), + ).write_frame( + _tier_frame(fake_coolscanpy), + str(tmp_path), + '{{ "%03d" % seq }}', + write_unrepaired=False, + write_positive=True, + builder_receipt=_builder_receipt(), + ) + + assert output.positive_path is None + entry = self._receipt(output.receipt_path)["outputs"]["positive"] + assert entry["written"] is False + assert entry["color_mode"] == "nikon-exact" + assert "profile hash mismatch" in entry["status"] + + def test_exact_nikon_positive_is_unavailable_without_portable_evaluator( + self, fake_coolscanpy, fake_repair_engine, tmp_path, monkeypatch + ) -> None: + builder_receipt = _builder_receipt() + monkeypatch.setattr( + roll_service.roll_positive, + "render_positive", + lambda *_args, **_kwargs: pytest.fail("approximate renderer must not satisfy exact mode"), + ) + + output = RollScanningService(exact_color_builder=_ExactStage1Builder()).write_frame( + _tier_frame(fake_coolscanpy), + str(tmp_path), + '{{ "%03d" % seq }}', + write_unrepaired=False, + write_positive=True, + positive_mode="nikon-exact", + builder_receipt=builder_receipt, + ) + + assert output.positive_path is None + entry = self._receipt(output.receipt_path)["outputs"]["positive"] + assert entry["written"] is False + assert entry["color_mode"] == "nikon-exact" + assert "verified portable CMS evaluator is not supplied" in entry["status"] + + def test_exact_nikon_positive_fails_closed_when_replay_evidence_cannot_be_retained( + self, fake_coolscanpy, fake_repair_engine, tmp_path, monkeypatch + ) -> None: + monkeypatch.setattr( + roll_service, + "_atomic_write_bytes", + MagicMock(side_effect=OSError("read-only evidence volume")), + ) + + output = RollScanningService( + exact_color_builder=_ExactStage1Builder(), + exact_color_evaluator=_ExactColorEvaluator(), + ).write_frame( + _tier_frame(fake_coolscanpy), + str(tmp_path), + '{{ "%03d" % seq }}', + write_unrepaired=False, + write_positive=True, + positive_mode="nikon-exact", + builder_receipt=_builder_receipt(), + ) + + assert output.positive_path is None + assert not (tmp_path / "011_positive.tif").exists() + entry = self._receipt(output.receipt_path)["outputs"]["positive"] + assert "cannot retain Stage-3 replay evidence" in entry["status"] + + def test_exact_nikon_positive_is_unavailable_without_stage1_builder(self, fake_coolscanpy, fake_repair_engine, tmp_path) -> None: + evaluator = _ExactColorEvaluator() + + output = RollScanningService(exact_color_evaluator=evaluator).write_frame( + _tier_frame(fake_coolscanpy), + str(tmp_path), + '{{ "%03d" % seq }}', + write_unrepaired=False, + write_positive=True, + positive_mode="nikon-exact", + builder_receipt=_builder_receipt(), + ) + + assert output.positive_path is None + assert evaluator.calls == [] + entry = self._receipt(output.receipt_path)["outputs"]["positive"] + assert "verified Stage-1 builder applicator is not supplied" in entry["status"] + + def test_exact_nikon_positive_is_unavailable_without_validated_builder_receipt( + self, fake_coolscanpy, fake_repair_engine, tmp_path + ) -> None: + evaluator = _ExactColorEvaluator() + + output = RollScanningService( + exact_color_builder=_ExactStage1Builder(), + exact_color_evaluator=evaluator, + ).write_frame( + _tier_frame(fake_coolscanpy), + str(tmp_path), + '{{ "%03d" % seq }}', + write_unrepaired=False, + write_positive=True, + positive_mode="nikon-exact", + ) + + assert output.positive_path is None + assert evaluator.calls == [] + entry = self._receipt(output.receipt_path)["outputs"]["positive"] + assert "validated Stage-3 builder receipt is not supplied" in entry["status"] + + def test_exact_nikon_positive_rejects_tampered_builder_receipt(self, fake_coolscanpy, fake_repair_engine, tmp_path) -> None: + evaluator = _ExactColorEvaluator() + receipt = _builder_receipt() + tampered = dataclasses.replace(receipt, payload=receipt.payload + b" ") + + output = RollScanningService( + exact_color_builder=_ExactStage1Builder(), + exact_color_evaluator=evaluator, + ).write_frame( + _tier_frame(fake_coolscanpy), + str(tmp_path), + '{{ "%03d" % seq }}', + write_unrepaired=False, + write_positive=True, + positive_mode="nikon-exact", + builder_receipt=tampered, + ) + + assert output.positive_path is None + assert evaluator.calls == [] + entry = self._receipt(output.receipt_path)["outputs"]["positive"] + assert "builder receipt payload does not match its SHA-256" in entry["status"] + + def test_exact_nikon_positive_rejects_malformed_cms_receipt(self, fake_coolscanpy, fake_repair_engine, tmp_path) -> None: + builder_receipt = _builder_receipt() + frame = _tier_frame(fake_coolscanpy) + valid_result = _ExactColorEvaluator().evaluate(frame.rgb, builder_receipt=builder_receipt) + malformed_payload = b"this is not a JSON receipt" + evaluator = MagicMock() + evaluator.evaluate.return_value = dataclasses.replace( + valid_result, + cms_receipt=_replace_cms_payload(valid_result.cms_receipt, malformed_payload), + ) + + output = RollScanningService( + exact_color_builder=_ExactStage1Builder(), + exact_color_evaluator=evaluator, + ).write_frame( + frame, + str(tmp_path), + '{{ "%03d" % seq }}', + write_unrepaired=False, + write_positive=True, + positive_mode="nikon-exact", + builder_receipt=builder_receipt, + ) + + assert output.positive_path is None + entry = self._receipt(output.receipt_path)["outputs"]["positive"] + assert "CMS receipt is not valid JSON" in entry["status"] + + def test_exact_nikon_positive_rejects_cms_receipt_input_mismatch(self, fake_coolscanpy, fake_repair_engine, tmp_path) -> None: + builder_receipt = _builder_receipt() + frame = _tier_frame(fake_coolscanpy) + valid_result = _ExactColorEvaluator().evaluate(frame.rgb, builder_receipt=builder_receipt) + cms = exact_color.receipt_payload(valid_result.cms_receipt) + cms["input_rgb_sha256"] = "0" * 64 + evaluator = MagicMock() + evaluator.evaluate.return_value = dataclasses.replace( + valid_result, + cms_receipt=_replace_cms_payload(valid_result.cms_receipt, cms), + ) + + output = RollScanningService( + exact_color_builder=_ExactStage1Builder(), + exact_color_evaluator=evaluator, + ).write_frame( + frame, + str(tmp_path), + '{{ "%03d" % seq }}', + write_unrepaired=False, + write_positive=True, + positive_mode="nikon-exact", + builder_receipt=builder_receipt, + ) + + assert output.positive_path is None + entry = self._receipt(output.receipt_path)["outputs"]["positive"] + assert "CMS receipt does not bind its builder, input, and output" in entry["status"] + + def test_exact_nikon_positive_rejects_self_attested_xor_evaluator_even_with_full_cms_contract( + self, fake_coolscanpy, fake_repair_engine, tmp_path + ) -> None: + builder_receipt = _builder_receipt() + frame = _tier_frame(fake_coolscanpy) + valid_result = _ExactColorEvaluator().evaluate(frame.rgb, builder_receipt=builder_receipt) + xor_output = np.bitwise_xor(frame.rgb, np.uint16(0xFFFF)) + xor_output_hash = exact_color.rgb16_content_sha256(xor_output) + forged_payload = production_cms_payload( + builder_receipt_sha256=builder_receipt.sha256, + input_rgb_sha256=valid_result.input_rgb_sha256, + output_rgb_sha256=xor_output_hash, + ) + evaluator = MagicMock() + evaluator.evaluate.return_value = dataclasses.replace( + valid_result, + rgb=xor_output, + output_rgb_sha256=xor_output_hash, + cms_receipt=_self_attested_cms_receipt(forged_payload), + ) + + output = RollScanningService( + exact_color_builder=_ExactStage1Builder(), + exact_color_evaluator=evaluator, + ).write_frame( + frame, + str(tmp_path), + '{{ "%03d" % seq }}', + write_unrepaired=False, + write_positive=True, + positive_mode="nikon-exact", + builder_receipt=builder_receipt, + ) + + assert output.positive_path is None + entry = self._receipt(output.receipt_path)["outputs"]["positive"] + assert "trusted portable CMS adapter" in entry["status"] + + @pytest.mark.parametrize( + "mutate", + [ + pytest.param(lambda cms: cms.update(kind="wrong"), id="kind"), + pytest.param(lambda cms: cms.update(version=True), id="version-type"), + pytest.param(lambda cms: cms.update(algorithm="unverified"), id="algorithm"), + pytest.param(lambda cms: cms["assets"].pop(next(iter(cms["assets"])), None), id="nine-assets"), + pytest.param(lambda cms: cms["oracle_source"].update(sha256="0" * 64), id="oracle-source"), + pytest.param(lambda cms: cms["validation"].update(mismatched_u16=1), id="validation"), + pytest.param(lambda cms: cms.update(scope="expanded"), id="scope"), + pytest.param(lambda cms: cms.update(stage_order=["stage2", "stage1"]), id="stage-order"), + pytest.param(lambda cms: cms.update(dll_free=False), id="dll-free"), + pytest.param(lambda cms: cms.update(upstream_builder_included=True), id="builder-scope"), + pytest.param(lambda cms: cms.update(chunk_pixels=0), id="chunk-size"), + ], + ) + def test_exact_nikon_positive_requires_every_cms_contract_field(self, fake_coolscanpy, fake_repair_engine, tmp_path, mutate) -> None: + builder_receipt = _builder_receipt() + frame = _tier_frame(fake_coolscanpy) + valid_result = _ExactColorEvaluator().evaluate(frame.rgb, builder_receipt=builder_receipt) + cms = exact_color.receipt_payload(valid_result.cms_receipt) + mutate(cms) + evaluator = MagicMock() + evaluator.evaluate.return_value = dataclasses.replace( + valid_result, + cms_receipt=_replace_cms_payload(valid_result.cms_receipt, cms), + ) + + output = RollScanningService( + exact_color_builder=_ExactStage1Builder(), + exact_color_evaluator=evaluator, + ).write_frame( + frame, + str(tmp_path), + '{{ "%03d" % seq }}', + write_unrepaired=False, + write_positive=True, + positive_mode="nikon-exact", + builder_receipt=builder_receipt, + ) + + assert output.positive_path is None + assert self._receipt(output.receipt_path)["outputs"]["positive"]["written"] is False + + def test_exact_nikon_positive_rejects_input_identity_mismatch(self, fake_coolscanpy, fake_repair_engine, tmp_path) -> None: + builder_receipt = _builder_receipt() + frame = _tier_frame(fake_coolscanpy) + valid_result = _ExactColorEvaluator().evaluate(frame.rgb, builder_receipt=builder_receipt) + evaluator = MagicMock() + evaluator.evaluate.return_value = dataclasses.replace( + valid_result, + input_rgb_sha256="0" * 64, + ) + + output = RollScanningService( + exact_color_builder=_ExactStage1Builder(), + exact_color_evaluator=evaluator, + ).write_frame( + frame, + str(tmp_path), + '{{ "%03d" % seq }}', + write_unrepaired=False, + write_positive=True, + positive_mode="nikon-exact", + builder_receipt=builder_receipt, + ) + + assert output.positive_path is None + entry = self._receipt(output.receipt_path)["outputs"]["positive"] + assert "input hash does not match the Stage-1 input content" in entry["status"] + + def test_exact_nikon_positive_rejects_unbound_builder_output(self, fake_coolscanpy, fake_repair_engine, tmp_path) -> None: + builder_receipt = _builder_receipt() + honest_builder = _ExactStage1Builder() + builder = MagicMock() + + def unbound(rgb, *, builder_receipt): + valid = honest_builder.apply(rgb, builder_receipt=builder_receipt) + return dataclasses.replace(valid, stage1_input_rgb_sha256="0" * 64) + + builder.apply.side_effect = unbound + evaluator = _ExactColorEvaluator() + + output = RollScanningService( + exact_color_builder=builder, + exact_color_evaluator=evaluator, + ).write_frame( + _tier_frame(fake_coolscanpy), + str(tmp_path), + '{{ "%03d" % seq }}', + write_unrepaired=False, + write_positive=True, + positive_mode="nikon-exact", + builder_receipt=builder_receipt, + ) + + assert output.positive_path is None + assert evaluator.calls == [] + entry = self._receipt(output.receipt_path)["outputs"]["positive"] + assert "builder output hash does not match the Stage-1 input content" in entry["status"] + + def test_exact_nikon_positive_rejects_unbound_output_hash(self, fake_coolscanpy, fake_repair_engine, tmp_path) -> None: + builder_receipt = _builder_receipt() + frame = _tier_frame(fake_coolscanpy) + valid_result = _ExactColorEvaluator().evaluate(frame.rgb, builder_receipt=builder_receipt) + evaluator = MagicMock() + evaluator.evaluate.return_value = dataclasses.replace( + valid_result, + output_rgb_sha256="f" * 64, + ) + + output = RollScanningService( + exact_color_builder=_ExactStage1Builder(), + exact_color_evaluator=evaluator, + ).write_frame( + frame, + str(tmp_path), + '{{ "%03d" % seq }}', + write_unrepaired=False, + write_positive=True, + positive_mode="nikon-exact", + builder_receipt=builder_receipt, + ) + + assert output.positive_path is None + assert not (tmp_path / "011_positive.tif").exists() + entry = self._receipt(output.receipt_path)["outputs"]["positive"] + assert "output hash does not match the returned RGB content" in entry["status"] + def test_repaired_naming_does_not_collide_with_unrepaired(self, fake_coolscanpy, fake_repair_engine, tmp_path) -> None: frame = _tier_frame(fake_coolscanpy) service = RollScanningService() @@ -286,7 +2330,10 @@ def test_repaired_receipt_records_engine_provenance(self, fake_coolscanpy, fake_ assert entry["written"] is True assert entry["engine"] == "test-repair-engine" assert entry["engine_version"] == "0.0.1-test" - assert entry["mode"] == "hybrid" + assert entry["mode_requested"] == "hybrid" + assert entry["mode_resolved"] == "exact" + assert entry["degraded"] is True + assert "no hybrid runtime" in entry["reason"] assert entry["rgb_path"] == output.repaired_rgb_path def test_repair_engine_failure_degrades_without_losing_unrepaired(self, fake_coolscanpy, fake_repair_engine, tmp_path) -> None: @@ -321,20 +2368,34 @@ def test_frame_without_infrared_degrades_repaired_and_positive(self, fake_coolsc payload = self._receipt(output.receipt_path) assert "no infrared plane" in payload["outputs"]["repaired"]["status"] - def test_invalid_repair_mode_string_falls_back_to_exact(self, fake_coolscanpy, fake_repair_engine, tmp_path) -> None: + def test_invalid_repair_mode_fails_before_writing_or_repair(self, fake_coolscanpy, fake_repair_engine, tmp_path) -> None: frame = _tier_frame(fake_coolscanpy) service = RollScanningService() - service.write_frame(frame, str(tmp_path), '{{ "%03d" % seq }}', write_repaired=True, repair_mode="bogus-mode") + with pytest.raises(ValueError, match="unknown repair mode"): + service.write_frame( + frame, + str(tmp_path), + '{{ "%03d" % seq }}', + write_repaired=True, + repair_mode="bogus-mode", + ) - assert len(fake_repair_engine.calls) == 1 - assert fake_repair_engine.calls[0][2] == roll_service.RepairMode.EXACT + assert fake_repair_engine.calls == [] + assert list(tmp_path.iterdir()) == [] def test_positive_requested_without_write_repaired_still_repairs_in_memory(self, fake_coolscanpy, fake_repair_engine, tmp_path) -> None: frame = _tier_frame(fake_coolscanpy) service = RollScanningService() - output = service.write_frame(frame, str(tmp_path), '{{ "%03d" % seq }}', write_unrepaired=False, write_positive=True) + output = service.write_frame( + frame, + str(tmp_path), + '{{ "%03d" % seq }}', + write_unrepaired=False, + write_positive=True, + positive_mode="negpy-approximate", + ) assert len(fake_repair_engine.calls) == 1 # repair ran to feed Tier 3... assert output.repaired_rgb_path is None # ...but Tier 2 itself was never written @@ -365,7 +2426,14 @@ def _spy(rgb_u16, *, processor): monkeypatch.setattr(roll_positive_module, "render_positive", _spy) - output = service.write_frame(frame, str(tmp_path), '{{ "%03d" % seq }}', write_unrepaired=False, write_positive=True) + output = service.write_frame( + frame, + str(tmp_path), + '{{ "%03d" % seq }}', + write_unrepaired=False, + write_positive=True, + positive_mode="negpy-approximate", + ) np.testing.assert_array_equal(captured["rgb_u16"], fake_repair_engine.transform(frame.rgb)) assert not np.array_equal(captured["rgb_u16"], frame.rgb) @@ -378,12 +2446,21 @@ def test_positive_receipt_records_inversion_and_repair_provenance(self, fake_coo frame = _tier_frame(fake_coolscanpy) service = RollScanningService() - output = service.write_frame(frame, str(tmp_path), '{{ "%03d" % seq }}', write_unrepaired=False, write_positive=True) + output = service.write_frame( + frame, + str(tmp_path), + '{{ "%03d" % seq }}', + write_unrepaired=False, + write_positive=True, + positive_mode="negpy-approximate", + ) payload = self._receipt(output.receipt_path) entry = payload["outputs"]["positive"] assert entry["written"] is True assert entry["rgb_path"] == output.positive_path + assert entry["color_mode"] == "negpy-approximate" + assert entry["exact_nikon_color"] is False assert entry["inversion_path"] == "negpy.services.rendering.image_processor.ImageProcessor.run_pipeline" assert entry["render_intent"] == "print" assert entry["process_mode"] == "C41" @@ -392,12 +2469,22 @@ def test_positive_receipt_records_inversion_and_repair_provenance(self, fake_coo assert entry["repair_engine"] == "test-repair-engine" assert entry["repair_engine_version"] == "0.0.1-test" assert entry["repair_mode"] == "exact" + assert "icc_profile" not in entry + with tifffile.TiffFile(output.positive_path) as approximate_tiff: + assert 34675 not in approximate_tiff.pages[0].tags def test_positive_filename_has_no_infrared_companion(self, fake_coolscanpy, fake_repair_engine, tmp_path) -> None: frame = _tier_frame(fake_coolscanpy) service = RollScanningService() - output = service.write_frame(frame, str(tmp_path), '{{ "%03d" % seq }}', write_unrepaired=False, write_positive=True) + output = service.write_frame( + frame, + str(tmp_path), + '{{ "%03d" % seq }}', + write_unrepaired=False, + write_positive=True, + positive_mode="negpy-approximate", + ) assert output.positive_path is not None assert output.positive_path.endswith("_positive.tif") @@ -414,7 +2501,13 @@ def test_inversion_unavailable_degrades_positive_but_keeps_unrepaired_and_repair service = RollScanningService() output = service.write_frame( - frame, str(tmp_path), '{{ "%03d" % seq }}', write_unrepaired=True, write_repaired=True, write_positive=True + frame, + str(tmp_path), + '{{ "%03d" % seq }}', + write_unrepaired=True, + write_repaired=True, + write_positive=True, + positive_mode="negpy-approximate", ) assert output.rgb_path is not None and os.path.exists(output.rgb_path) @@ -423,7 +2516,11 @@ def test_inversion_unavailable_degrades_positive_but_keeps_unrepaired_and_repair payload = self._receipt(output.receipt_path) assert payload["outputs"]["unrepaired"]["written"] is True assert payload["outputs"]["repaired"]["written"] is True - assert payload["outputs"]["positive"] == {"written": False, "status": "unavailable: inversion path not available"} + assert payload["outputs"]["positive"] == { + "written": False, + "status": "unavailable: inversion path not available", + "color_mode": "negpy-approximate", + } def test_inversion_failure_degrades_positive_only(self, fake_coolscanpy, fake_repair_engine, tmp_path, monkeypatch) -> None: from negpy.services.roll import positive as roll_positive_module @@ -436,7 +2533,13 @@ def _boom(rgb_u16, *, processor): service = RollScanningService() output = service.write_frame( - frame, str(tmp_path), '{{ "%03d" % seq }}', write_unrepaired=True, write_repaired=True, write_positive=True + frame, + str(tmp_path), + '{{ "%03d" % seq }}', + write_unrepaired=True, + write_repaired=True, + write_positive=True, + positive_mode="negpy-approximate", ) assert output.rgb_path is not None and os.path.exists(output.rgb_path) @@ -454,12 +2557,21 @@ def test_all_three_tiers_together(self, fake_coolscanpy, fake_repair_engine, tmp service = RollScanningService() output = service.write_frame( - frame, str(tmp_path), '{{ "%03d" % seq }}', write_unrepaired=True, write_repaired=True, write_positive=True + frame, + str(tmp_path), + '{{ "%03d" % seq }}', + write_unrepaired=True, + write_repaired=True, + write_positive=True, + positive_mode="negpy-approximate", ) for path in (output.rgb_path, output.ir_path, output.repaired_rgb_path, output.repaired_ir_path, output.positive_path): assert path is not None and os.path.exists(path) assert len({output.rgb_path, output.ir_path, output.repaired_rgb_path, output.repaired_ir_path, output.positive_path}) == 5 + for untagged_path in (output.rgb_path, output.repaired_rgb_path, output.positive_path): + with tifffile.TiffFile(untagged_path) as untagged_tiff: + assert 34675 not in untagged_tiff.pages[0].tags payload = self._receipt(output.receipt_path) assert all(payload["outputs"][tier]["written"] for tier in ("unrepaired", "repaired", "positive")) diff --git a/tests/roll/test_sidebar.py b/tests/roll/test_sidebar.py index 2e4248ed..eee11316 100644 --- a/tests/roll/test_sidebar.py +++ b/tests/roll/test_sidebar.py @@ -8,12 +8,14 @@ from __future__ import annotations import sys +from types import SimpleNamespace import numpy as np from PyQt6.QtWidgets import QApplication from unittest.mock import MagicMock from negpy.desktop.view.sidebar.coolscan_roll import CoolscanRollSidebar +from negpy.infrastructure.roll import coolscanpy_roll if not QApplication.instance(): _app = QApplication(sys.argv) @@ -61,8 +63,12 @@ def test_sidebar_builds_with_all_controls(self) -> None: ): assert hasattr(w, attr), attr - def test_missing_coolscanpy_disables_preview_and_shows_hint(self) -> None: - w = _sidebar() # no fake_coolscanpy fixture -- coolscanpy genuinely absent here + def test_missing_coolscanpy_disables_preview_and_shows_hint(self, monkeypatch) -> None: + # The optional group may be installed by this test run, so exercise + # the sidebar's unavailable branch at its adapter seam instead of + # assuming an interpreter-level package state. + monkeypatch.setattr(coolscanpy_roll, "available", lambda: False) + w = _sidebar() assert w.preview_btn.isEnabled() is False assert "install coolscanpy" in w.gate_hint.text() assert not w._setup_hint.isHidden() @@ -100,14 +106,14 @@ def test_preview_click_starts_request_for_selected_device(self, fake_coolscanpy) from negpy.desktop.workers.roll_worker import RollPreviewRequest - req = w.controller.start_roll_preview.call_args[0][0] + req = w.controller.start_coolscan_roll_preview.call_args[0][0] assert isinstance(req, RollPreviewRequest) assert req.device_id == "dev-42" def test_preview_click_without_a_device_is_a_noop(self) -> None: w = _sidebar() w._on_preview_clicked() - w.controller.start_roll_preview.assert_not_called() + w.controller.start_coolscan_roll_preview.assert_not_called() def test_preview_ready_populates_the_contact_sheet(self, fake_coolscanpy) -> None: w = _sidebar() @@ -230,6 +236,192 @@ def test_finished_clears_scanning_state(self, fake_coolscanpy) -> None: assert w._scanning is False assert "Scanned 0" in w.status_label.text() + def test_finished_reports_hybrid_to_exact_degradation_as_an_issue( + self, + fake_coolscanpy, + tmp_path, + ) -> None: + from negpy.desktop.workers.roll_worker import RollBatchScanRequest + + w = _sidebar() + w._active_scan_request = RollBatchScanRequest( + device_id="ls5000-usb-001", + slots=(1,), + output_folder=str(tmp_path), + filename_pattern='{{ "%03d" % seq }}', + write_unrepaired=True, + write_repaired=True, + write_positive=False, + repair_mode="hybrid", + ) + w.set_scanning(True) + + w._on_finished( + [ + SimpleNamespace( + slot=1, + rgb_path="001.tif", + repaired_rgb_path="001_repaired.tif", + positive_path=None, + native_synthesis_mask_path=None, + hybrid_receipt_path=None, + ) + ] + ) + + assert w._scanning is False + assert "Completed with issues" in w.status_label.text() + assert "Hybrid repair degraded or unavailable" in w.status_label.text() + assert "Scanned 1 frame" not in w.status_label.text() + + def test_finished_reports_unavailable_requested_exact_positive_as_an_issue( + self, + fake_coolscanpy, + tmp_path, + ) -> None: + from negpy.desktop.workers.roll_worker import RollBatchScanRequest + + w = _sidebar() + w._active_scan_request = RollBatchScanRequest( + device_id="ls5000-usb-001", + slots=(1,), + output_folder=str(tmp_path), + filename_pattern='{{ "%03d" % seq }}', + write_unrepaired=True, + write_repaired=False, + write_positive=True, + repair_mode="exact", + positive_mode="nikon-exact", + ) + w.set_scanning(True) + + w._on_finished( + [ + SimpleNamespace( + slot=1, + rgb_path="001.tif", + repaired_rgb_path=None, + positive_path=None, + native_synthesis_mask_path=None, + hybrid_receipt_path=None, + ) + ] + ) + + assert "Completed with issues" in w.status_label.text() + assert "Nikon exact positive unavailable" in w.status_label.text() + assert "Scanned 1 frame" not in w.status_label.text() + + def test_finished_reports_missing_requested_slot_as_an_issue( + self, + fake_coolscanpy, + tmp_path, + ) -> None: + from negpy.desktop.workers.roll_worker import RollBatchScanRequest + + w = _sidebar() + w._active_scan_request = RollBatchScanRequest( + device_id="ls5000-usb-001", + slots=(1, 2), + output_folder=str(tmp_path), + filename_pattern='{{ "%03d" % seq }}', + write_unrepaired=True, + write_repaired=False, + write_positive=False, + repair_mode="exact", + ) + + w._on_finished( + [ + SimpleNamespace( + slot=1, + rgb_path="001.tif", + repaired_rgb_path=None, + positive_path=None, + native_synthesis_mask_path=None, + hybrid_receipt_path=None, + ) + ] + ) + + assert "Completed with issues" in w.status_label.text() + assert "slot(s) 2 did not complete" in w.status_label.text() + assert "Scanned 1 frame" not in w.status_label.text() + + def test_finished_reports_each_missing_requested_file_tier_as_an_issue( + self, + fake_coolscanpy, + tmp_path, + ) -> None: + from negpy.desktop.workers.roll_worker import RollBatchScanRequest + + w = _sidebar() + w._active_scan_request = RollBatchScanRequest( + device_id="ls5000-usb-001", + slots=(1,), + output_folder=str(tmp_path), + filename_pattern='{{ "%03d" % seq }}', + write_unrepaired=True, + write_repaired=True, + write_positive=False, + repair_mode="exact", + ) + + w._on_finished( + [ + SimpleNamespace( + slot=1, + rgb_path=None, + repaired_rgb_path=None, + positive_path=None, + native_synthesis_mask_path=None, + hybrid_receipt_path=None, + ) + ] + ) + + status = w.status_label.text() + assert "Completed with issues" in status + assert "requested unrepaired output unavailable" in status + assert "requested repaired output unavailable" in status + assert "Scanned 1 frame" not in status + + def test_finished_reports_full_success_only_when_hybrid_and_exact_exist( + self, + fake_coolscanpy, + tmp_path, + ) -> None: + from negpy.desktop.workers.roll_worker import RollBatchScanRequest + + w = _sidebar() + w._active_scan_request = RollBatchScanRequest( + device_id="ls5000-usb-001", + slots=(1,), + output_folder=str(tmp_path), + filename_pattern='{{ "%03d" % seq }}', + write_unrepaired=True, + write_repaired=True, + write_positive=True, + repair_mode="hybrid", + positive_mode="nikon-exact", + ) + w.set_scanning(True) + + w._on_finished( + [ + SimpleNamespace( + slot=1, + rgb_path="001.tif", + repaired_rgb_path="001_repaired.tif", + positive_path="001_positive.tif", + native_synthesis_mask_path="native-mask.png", + hybrid_receipt_path="hybrid-receipt.json", + ) + ] + ) + + assert w.status_label.text() == "Scanned 1 frame(s)." + def test_safe_stop_disables_itself_and_forwards_to_controller(self, fake_coolscanpy) -> None: w = _sidebar() w.set_scanning(True) @@ -314,9 +506,13 @@ def test_sidebar_builds_with_tier_controls(self) -> None: def test_defaults_match_settings_defaults(self) -> None: w = _sidebar() assert w.write_unrepaired_check.isChecked() is True - assert w.write_repaired_check.isChecked() is False - assert w.write_positive_check.isChecked() is False - assert w.repair_mode_combo.currentData() == "exact" + assert w.write_repaired_check.isChecked() is True + assert w.write_positive_check.isChecked() is True + assert w.repair_mode_combo.currentData() == "hybrid" + assert "generative" in w.repair_mode_combo.currentText().lower() + assert "not bit-deterministic" in w.repair_mode_combo.toolTip() + assert w.positive_mode_combo.currentData() == "nikon-exact" + assert w.positive_mode_combo.currentText() == "Nikon C-41 exact (parity)" assert w.tier_hint.isHidden() is True # Tier 1 is on by default -- nothing to warn about def test_tier_hint_shown_when_unrepaired_is_turned_off(self) -> None: @@ -342,7 +538,9 @@ def test_scan_blocked_when_no_tier_is_selected(self, fake_coolscanpy, tmp_path) w.folder_edit.setText(str(tmp_path)) w._on_preview_ready([_thumb(fake_coolscanpy, 1)]) w.contact_sheet.item(0).setSelected(True) - w.write_unrepaired_check.setChecked(False) # the only tier on by default + w.write_unrepaired_check.setChecked(False) + w.write_repaired_check.setChecked(False) + w.write_positive_check.setChecked(False) assert "select at least one output tier" in w._missing_for_scan() assert w.scan_btn.isEnabled() is False @@ -357,6 +555,7 @@ def test_scan_click_forwards_tier_flags_and_repair_mode(self, fake_coolscanpy, t w.write_repaired_check.setChecked(True) w.write_positive_check.setChecked(True) w.repair_mode_combo.setCurrentIndex(w.repair_mode_combo.findData("hybrid")) + w.positive_mode_combo.setCurrentIndex(w.positive_mode_combo.findData("negpy-approximate")) w._on_scan_clicked() @@ -365,6 +564,7 @@ def test_scan_click_forwards_tier_flags_and_repair_mode(self, fake_coolscanpy, t assert req.write_repaired is True assert req.write_positive is True assert req.repair_mode == "hybrid" + assert req.positive_mode == "negpy-approximate" def test_scan_click_without_any_tier_selected_is_a_noop(self, fake_coolscanpy, tmp_path) -> None: w = _sidebar() @@ -373,6 +573,8 @@ def test_scan_click_without_any_tier_selected_is_a_noop(self, fake_coolscanpy, t w._on_preview_ready([_thumb(fake_coolscanpy, 1)]) w.contact_sheet.item(0).setSelected(True) w.write_unrepaired_check.setChecked(False) + w.write_repaired_check.setChecked(False) + w.write_positive_check.setChecked(False) w._on_scan_clicked() @@ -383,6 +585,7 @@ def test_tier_settings_round_trip_through_the_repo(self) -> None: w.write_unrepaired_check.setChecked(False) w.write_repaired_check.setChecked(True) w.repair_mode_combo.setCurrentIndex(w.repair_mode_combo.findData("hybrid")) + w.positive_mode_combo.setCurrentIndex(w.positive_mode_combo.findData("negpy-approximate")) w._update_settings_from_ui() @@ -391,6 +594,7 @@ def test_tier_settings_round_trip_through_the_repo(self) -> None: assert saved[1]["write_unrepaired"] is False assert saved[1]["write_repaired"] is True assert saved[1]["repair_mode"] == "hybrid" + assert saved[1]["positive_mode"] == "negpy-approximate" def test_load_settings_restores_tier_choices(self) -> None: ctrl = MagicMock() @@ -399,6 +603,7 @@ def test_load_settings_restores_tier_choices(self) -> None: "write_repaired": True, "write_positive": True, "repair_mode": "hybrid", + "positive_mode": "negpy-approximate", } w = CoolscanRollSidebar(ctrl) @@ -406,6 +611,24 @@ def test_load_settings_restores_tier_choices(self) -> None: assert w.write_repaired_check.isChecked() is True assert w.write_positive_check.isChecked() is True assert w.repair_mode_combo.currentData() == "hybrid" + assert w.positive_mode_combo.currentData() == "negpy-approximate" + + def test_existing_saved_exact_and_disabled_choices_override_new_defaults(self) -> None: + ctrl = MagicMock() + ctrl.session.repo.get_global_setting.return_value = { + "write_unrepaired": True, + "write_repaired": False, + "write_positive": False, + "repair_mode": "exact", + "positive_mode": "nikon-exact", + } + + w = CoolscanRollSidebar(ctrl) + + assert w.write_unrepaired_check.isChecked() is True + assert w.write_repaired_check.isChecked() is False + assert w.write_positive_check.isChecked() is False + assert w.repair_mode_combo.currentData() == "exact" class TestActivation: diff --git a/tests/roll/test_stage3_replay_loader.py b/tests/roll/test_stage3_replay_loader.py new file mode 100644 index 00000000..c797760f --- /dev/null +++ b/tests/roll/test_stage3_replay_loader.py @@ -0,0 +1,115 @@ +"""Adversarial contracts for the file-backed Stage-3 replay bridge.""" + +from __future__ import annotations + +import dataclasses +import json +import os +from pathlib import Path + +import numpy as np +import pytest + +from negpy.services.roll import exact_color +from tests.roll._exact_fixtures import write_stage3_replay_fixture + + +def _arrays() -> tuple[np.ndarray, np.ndarray, np.ndarray]: + identity = np.arange(65_536, dtype=np.uint16) + return identity, 65_535 - identity, identity // 3 + + +def _rewrite_report(path: Path, mutate) -> None: + report = json.loads(path.read_bytes()) + mutate(report) + path.write_bytes(json.dumps(report, sort_keys=True, separators=(",", ":")).encode()) + + +def test_trusted_loader_builds_a_bound_replay_receipt(tmp_path: Path) -> None: + report_path = write_stage3_replay_fixture(tmp_path / "capture", _arrays()) + + receipt = exact_color.load_stage3_replay_builder_receipt(report_path) + + payload = exact_color.builder_receipt_payload(receipt) + assert receipt.attested is True + assert payload["scope"] == exact_color.STAGE3_REPLAY_SCOPE + assert payload["native_per_acquisition_builder"] is False + assert receipt.stage3_receipt == report_path.read_bytes() + + +@pytest.mark.parametrize( + ("mutate", "message"), + [ + (lambda report: report.update(errors=[{"code": "hidden"}]), "errors"), + (lambda report: report["provenance"].pop("observer_source"), "provenance"), + (lambda report: report["provenance"]["observer_executable"].pop("sha256"), "observer executable"), + (lambda report: report["provenance"]["module"].update(sha256="0" * 64), "module provenance"), + (lambda report: report["provenance"]["resource"].update(bytes=1_023), "resource provenance"), + (lambda report: report["artifacts"].pop(), "exact required file set"), + (lambda report: report["artifacts"].append(dict(report["artifacts"][0])), "repeats"), + ], +) +def test_report_provenance_and_inventory_mutations_fail_closed( + tmp_path: Path, + mutate, + message: str, +) -> None: + report_path = write_stage3_replay_fixture(tmp_path / "capture", _arrays()) + _rewrite_report(report_path, mutate) + + with pytest.raises(exact_color.ExactColorIntegrityError, match=message): + exact_color.load_stage3_replay_builder_receipt(report_path) + + +def test_pre_f_file_must_match_the_report_inventory(tmp_path: Path) -> None: + report_path = write_stage3_replay_fixture(tmp_path / "capture", _arrays()) + path = report_path.parent / "builder-preF-g.bin" + payload = bytearray(path.read_bytes()) + payload[4_096] ^= 1 + path.write_bytes(payload) + + with pytest.raises(exact_color.ExactColorIntegrityError, match="does not bind"): + exact_color.load_stage3_replay_builder_receipt(report_path) + + +def test_symlinked_evidence_is_rejected(tmp_path: Path) -> None: + report_path = write_stage3_replay_fixture(tmp_path / "capture", _arrays()) + target = report_path.parent / "builder-preF-b.bin" + real = report_path.parent / "real-b.bin" + target.replace(real) + target.symlink_to(real) + + with pytest.raises(exact_color.ExactColorUnavailable, match="non-symlink"): + exact_color.load_stage3_replay_builder_receipt(report_path) + + +def test_report_swap_between_lstat_and_open_is_detected( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + report_path = write_stage3_replay_fixture(tmp_path / "capture", _arrays()) + replacement = tmp_path / "replacement.json" + replacement.write_bytes(report_path.read_bytes() + b" ") + real_open = os.open + swapped = False + + def swap_then_open(path, flags, *args, **kwargs): + nonlocal swapped + if not swapped and Path(path) == report_path: + swapped = True + os.replace(replacement, report_path) + return real_open(path, flags, *args, **kwargs) + + monkeypatch.setattr(exact_color.os, "open", swap_then_open) + + with pytest.raises(exact_color.ExactColorIntegrityError, match="changed while being read"): + exact_color.load_stage3_replay_builder_receipt(report_path) + + +def test_callers_cannot_self_attest_a_builder_receipt(tmp_path: Path) -> None: + report_path = write_stage3_replay_fixture(tmp_path / "capture", _arrays()) + receipt = exact_color.load_stage3_replay_builder_receipt(report_path) + forged = dataclasses.replace(receipt, _factory_token=object()) + + with pytest.raises(exact_color.ExactColorIntegrityError, match="trusted Stage-3 replay file loader"): + exact_color.builder_receipt_payload(forged) diff --git a/tests/roll/test_worker.py b/tests/roll/test_worker.py index 09dac661..7c1b54b3 100644 --- a/tests/roll/test_worker.py +++ b/tests/roll/test_worker.py @@ -7,16 +7,61 @@ from __future__ import annotations import os +import threading +from pathlib import Path +from unittest.mock import MagicMock import numpy as np +import pytest from negpy.desktop.workers.roll_worker import RollBatchScanRequest, RollPreviewRequest, RollWorker +from negpy.infrastructure.roll import repair as roll_repair +from negpy.services.repair.fauxice_hybrid_runner import HybridRuntimeConfig +from negpy.services.roll import service as roll_service def _frame(fake_coolscanpy, *, slot, ir=None): rgb = np.random.randint(0, 65535, (4, 6, 3), dtype=np.uint16) receipt = fake_coolscanpy.Receipt(version=1, slot=slot, dpi=4000, depth=16, device_id="usb:1:2", transport_smear_verdict="clean") - return fake_coolscanpy.Frame(slot=slot, rgb=rgb, ir=ir, ir_validity=None, receipt=receipt) + acquisition = None + validity = None + meter = None + if ir is not None: + storage_rgbi = np.dstack((rgb, ir)) + native_rgbi = np.ascontiguousarray(np.rot90(storage_rgbi, k=-1, axes=(0, 1))) + validity = np.ones(ir.shape, dtype=np.bool_) + native_validity = np.ascontiguousarray(np.rot90(validity, k=-1, axes=(0, 1))) + meter = np.full((2, 2, 4), 500, dtype=np.uint16) + reservation_id = f"reservation-{slot:03d}" + capture_attempt_id = f"fine-slot-{slot}-attempt-001" + acquisition_id, evidence_sha256 = roll_service._derive_digital_ice_producer_binding( + slot=slot, + reservation_id=reservation_id, + capture_attempt_id=capture_attempt_id, + main_rgbi=native_rgbi, + prepass_rgbi=meter, + ir_validity=native_validity, + ) + acquisition = roll_repair.RepairAcquisition.from_arrays( + acquisition_id=acquisition_id, + slot=slot, + reservation_id=reservation_id, + capture_attempt_id=capture_attempt_id, + storage_transform=roll_repair.DIGITAL_ICE_STORAGE_TRANSFORM, + evidence_sha256=evidence_sha256, + main_rgbi=native_rgbi, + prepass_rgbi=meter, + ir_validity=native_validity, + ) + return fake_coolscanpy.Frame( + slot=slot, + rgb=rgb, + ir=ir, + ir_validity=validity, + receipt=receipt, + meter_rgbi=meter, + digital_ice_acquisition=acquisition, + ) def _open(fake_coolscanpy, roll=None): @@ -27,6 +72,24 @@ def _open(fake_coolscanpy, roll=None): class TestListDevices: + def test_explicit_hybrid_runtime_is_injected_into_scanning_service(self, tmp_path: Path) -> None: + runtime = HybridRuntimeConfig( + hybrid_python=tmp_path / "hybrid" / "bin" / "python", + executable=tmp_path / "hybrid" / "bin" / "fauxce-hybrid", + core_source_manifest_sha256="c" * 64, + hybrid_source_manifest_sha256="d" * 64, + iopaint_python=tmp_path / "iopaint" / "bin" / "python", + iopaint_executable=tmp_path / "iopaint" / "bin" / "iopaint", + iopaint_source_manifest_sha256="a" * 64, + model_dir=tmp_path / "models", + model_weights=tmp_path / "models" / "big-lama.pt", + model_weights_sha256="b" * 64, + ) + + worker = RollWorker(hybrid_runtime=runtime) + + assert worker._service._hybrid_runtime is runtime + def test_success_emits_devices(self, fake_coolscanpy) -> None: fake_coolscanpy.state["devices"] = ["device-a", "device-b"] worker = RollWorker() @@ -135,6 +198,26 @@ def test_close_roll_forgets_the_open_device(self, fake_coolscanpy) -> None: assert roll.closed is True assert worker._open_device_id is None + def test_close_roll_failure_retains_identity_and_does_not_emit_closed( + self, + fake_coolscanpy, + ) -> None: + _open(fake_coolscanpy) + worker = RollWorker() + worker.run_preview(RollPreviewRequest(device_id="ls5000-usb-001")) + closed = [] + errors = [] + worker.closed.connect(lambda: closed.append(True)) + worker.error.connect(errors.append) + failure = RuntimeError("ownership remains uncertain") + worker._service.close = MagicMock(side_effect=failure) + + assert worker.close_roll() is False + + assert worker._open_device_id == "ls5000-usb-001" + assert closed == [] + assert errors and "ownership remains uncertain" in errors[-1] + class TestSpacingAndApproval: def test_set_spacing_offset_echoes_the_applied_value(self, fake_coolscanpy) -> None: @@ -193,7 +276,9 @@ def test_writes_every_frame_and_reports_progress(self, fake_coolscanpy, tmp_path worker.progress.connect(lambda frac, msg: progress.append((frac, msg))) worker.run_batch_scan( - RollBatchScanRequest(device_id="ls5000-usb-001", slots=(1, 2, 3), output_folder=str(tmp_path), filename_pattern='{{ "%03d" % seq }}') + RollBatchScanRequest( + device_id="ls5000-usb-001", slots=(1, 2, 3), output_folder=str(tmp_path), filename_pattern='{{ "%03d" % seq }}' + ) ) assert [w.slot for w in written] == [1, 2, 3] @@ -226,7 +311,9 @@ def test_safe_stop_mid_batch_is_reported_as_cancelled_not_error(self, fake_cools worker.error.connect(errors.append) worker.run_batch_scan( - RollBatchScanRequest(device_id="ls5000-usb-001", slots=(1, 2), output_folder=str(tmp_path), filename_pattern='{{ "%03d" % seq }}') + RollBatchScanRequest( + device_id="ls5000-usb-001", slots=(1, 2), output_folder=str(tmp_path), filename_pattern='{{ "%03d" % seq }}' + ) ) assert len(written) == 1 # slot 1 finished and was written before the stop landed @@ -246,13 +333,105 @@ def test_genuine_failure_mid_batch_is_reported_as_error_not_cancelled(self, fake worker.error.connect(errors.append) worker.run_batch_scan( - RollBatchScanRequest(device_id="ls5000-usb-001", slots=(1, 2), output_folder=str(tmp_path), filename_pattern='{{ "%03d" % seq }}') + RollBatchScanRequest( + device_id="ls5000-usb-001", slots=(1, 2), output_folder=str(tmp_path), filename_pattern='{{ "%03d" % seq }}' + ) ) assert len(written) == 1 assert cancelled == [] assert errors and "transport jam" in errors[-1] + def test_repair_cancellation_aborts_frame_transaction_and_batch( + self, + fake_coolscanpy, + fake_repair_engine, + tmp_path, + ) -> None: + frame = _frame( + fake_coolscanpy, + slot=1, + ir=np.full((4, 6), 2_000, dtype=np.uint16), + ) + roll = fake_coolscanpy.Roll(frames=[frame]) + _open(fake_coolscanpy, roll) + started = threading.Event() + + def blocking_repair( + acquisition, + mode, + *, + hybrid_runtime=None, + progress=None, + cancel=None, + ): + started.set() + assert cancel is not None + assert cancel.wait(timeout=5) + raise roll_repair.RepairCancelled("repair cancelled by test") + + fake_repair_engine.repair = blocking_repair + worker = RollWorker() + written, finished, cancelled, errors = [], [], [], [] + worker.frame_written.connect(written.append) + worker.finished.connect(finished.append) + worker.cancelled.connect(lambda: cancelled.append(True)) + worker.error.connect(errors.append) + request = RollBatchScanRequest( + device_id="ls5000-usb-001", + slots=(1,), + output_folder=str(tmp_path), + filename_pattern='{{ "%03d" % seq }}', + write_unrepaired=True, + write_repaired=True, + ) + worker.prepare_batch() + stop_thread = threading.Thread( + target=lambda: ( + started.wait(timeout=5), + worker.safe_stop(), + ) + ) + stop_thread.start() + worker.run_batch_scan(request) + stop_thread.join(timeout=5) + + assert not stop_thread.is_alive() + assert written == [] + assert finished == [] + assert cancelled == [True] + assert errors == [] + assert not any(path.name.endswith(".tif") for path in tmp_path.iterdir()) + assert not any(path.name.endswith("_receipt.json") for path in tmp_path.iterdir()) + assert not any(path.name.startswith(".negpy-frame-stage-") for path in tmp_path.iterdir()) + + def test_stop_after_batch_is_queued_is_not_cleared_at_first_scan( + self, + fake_coolscanpy, + tmp_path, + ) -> None: + frame = _frame(fake_coolscanpy, slot=1) + roll = fake_coolscanpy.Roll(frames=[frame]) + _open(fake_coolscanpy, roll) + worker = RollWorker() + cancelled, finished = [], [] + worker.cancelled.connect(lambda: cancelled.append(True)) + worker.finished.connect(finished.append) + request = RollBatchScanRequest( + device_id="ls5000-usb-001", + slots=(1,), + output_folder=str(tmp_path), + filename_pattern='{{ "%03d" % seq }}', + ) + + worker.prepare_batch() + worker.safe_stop() + worker.run_batch_scan(request) + + assert cancelled == [True] + assert finished == [] + assert not any(tmp_path.iterdir()) + def test_writes_ir_sidecar_when_frame_carries_one(self, fake_coolscanpy, tmp_path) -> None: ir = np.random.randint(0, 65535, (4, 6), dtype=np.uint16) _open(fake_coolscanpy, fake_coolscanpy.Roll(frames=[_frame(fake_coolscanpy, slot=1, ir=ir)])) @@ -292,6 +471,37 @@ def test_shutdown_stops_and_closes(self, fake_coolscanpy) -> None: assert roll.safe_stop_called is True assert roll.closed is True + def test_shutdown_close_failure_is_raised_and_retains_open_identity( + self, + fake_coolscanpy, + ) -> None: + _open(fake_coolscanpy) + worker = RollWorker() + worker.run_preview(RollPreviewRequest(device_id="ls5000-usb-001")) + failure = RuntimeError("ownership remains uncertain") + worker._service.close = MagicMock(side_effect=failure) + + with pytest.raises(RuntimeError) as raised: + worker.shutdown(timeout_seconds=0) + + assert raised.value is failure + assert worker._open_device_id == "ls5000-usb-001" + + def test_queued_preview_cannot_reopen_after_shutdown_was_proven( + self, + fake_coolscanpy, + ) -> None: + _open(fake_coolscanpy) + worker = RollWorker() + errors = [] + worker.error.connect(errors.append) + + worker.shutdown(timeout_seconds=0) + worker.run_preview(RollPreviewRequest(device_id="ls5000-usb-001")) + + assert worker._open_device_id is None + assert errors and "no new operation may begin" in errors[-1] + def test_shutdown_without_any_roll_open_does_not_raise(self) -> None: RollWorker().shutdown() @@ -302,18 +512,23 @@ class TestBatchScanOutputTiers: matrix (naming, receipt provenance, every degrade path); these tests only pin the plumbing between the request and the service call.""" - def test_request_defaults_write_only_tier_1(self) -> None: - """A request built without naming the tier fields (every pre-existing - test in this file builds one this way) matches RollScanSettings - .defaults() -- unchanged from before Tier 2/3 existed.""" + def test_request_defaults_match_the_complete_parity_workflow(self) -> None: + """An unnamed request follows the new-user Color + DICE target.""" req = RollBatchScanRequest(device_id="d", slots=(1,), output_folder="/tmp/x", filename_pattern="p") assert req.write_unrepaired is True - assert req.write_repaired is False - assert req.write_positive is False - assert req.repair_mode == "exact" - - def test_tier_flags_are_forwarded_to_write_frame(self, fake_coolscanpy, tmp_path, monkeypatch) -> None: + assert req.write_repaired is True + assert req.write_positive is True + assert req.repair_mode == "hybrid" + assert req.positive_mode == "nikon-exact" + + def test_tier_flags_are_forwarded_to_write_frame( + self, + fake_coolscanpy, + no_repair_engine, + tmp_path, + monkeypatch, + ) -> None: _open(fake_coolscanpy, fake_coolscanpy.Roll(frames=[_frame(fake_coolscanpy, slot=1)])) worker = RollWorker() captured = {} @@ -335,17 +550,20 @@ def _spy(frame, output_folder, filename_pattern, **kwargs): write_repaired=True, write_positive=True, repair_mode="hybrid", + positive_mode="negpy-approximate", ) ) # write_repaired/write_positive=True with no repair engine registered degrades # gracefully (see test_service.py) rather than raising, so this exercises the # real write_frame end to end without needing a fake engine here. + assert callable(captured.pop("on_repair_progress")) assert captured == { "write_unrepaired": False, "write_repaired": True, "write_positive": True, "repair_mode": "hybrid", + "positive_mode": "negpy-approximate", } def test_repaired_tier_writes_through_the_worker_with_a_registered_engine(self, fake_coolscanpy, fake_repair_engine, tmp_path) -> None: diff --git a/tests/test_contact_sheet.py b/tests/test_contact_sheet.py index 593d8990..f9bdbd99 100644 --- a/tests/test_contact_sheet.py +++ b/tests/test_contact_sheet.py @@ -89,9 +89,7 @@ def test_labels_increase_sheet_height(self): tiles = [_tile(100, 150) for _ in range(4)] labels = ["a.jpg", "b.jpg", "c.jpg", "d.jpg"] without = ContactSheetService.build_sheets(tiles, show_labels=False, cell_px=100, gap=4, margin=8) - with_labels = ContactSheetService.build_sheets( - tiles, labels=labels, show_labels=True, cell_px=100, gap=4, margin=8 - ) + with_labels = ContactSheetService.build_sheets(tiles, labels=labels, show_labels=True, cell_px=100, gap=4, margin=8) self.assertGreater(np.asarray(with_labels[0]).shape[0], np.asarray(without[0]).shape[0]) def test_caption_band_present(self): diff --git a/tests/test_contact_sheet_templates.py b/tests/test_contact_sheet_templates.py index 9b880139..7a1673e9 100644 --- a/tests/test_contact_sheet_templates.py +++ b/tests/test_contact_sheet_templates.py @@ -84,8 +84,7 @@ def test_template_parses_label_and_color_fields(tmp_path, monkeypatch): monkeypatch.setattr(APP_CONFIG, "contact_sheet_templates_dir", str(tmp_path)) _write( os.path.join(tmp_path, "labeled.toml"), - 'name = "Labeled"\n\n[layout]\ncell_px = 400\nshow_labels = false\n' - 'background_color = "#112233"\nlabel_color = "#aabbcc"\n', + 'name = "Labeled"\n\n[layout]\ncell_px = 400\nshow_labels = false\nbackground_color = "#112233"\nlabel_color = "#aabbcc"\n', ) layout = ContactSheetTemplates.get_layout("Labeled") assert layout.show_labels is False diff --git a/tests/test_controller.py b/tests/test_controller.py index d00d014f..9246dedb 100644 --- a/tests/test_controller.py +++ b/tests/test_controller.py @@ -180,6 +180,25 @@ def test_start_roll_preview_prepares_worker_and_emits_preview_only(self): # No "started": a preview must not flip the main scan UI into scanning state. self.assertEqual(events, ["prepare", ("preview", request)]) + def test_coolscan_roll_preview_uses_its_separate_worker_lane(self): + from negpy.desktop.workers.roll_worker import RollPreviewRequest + + events: list[object] = [] + request = RollPreviewRequest(device_id="usb:1:2", slots=(1, 2)) + controller = SimpleNamespace( + _ensure_roll_thread=lambda: events.append("ensure-coolscan-thread"), + roll_preview_requested=SimpleNamespace( + emit=lambda value: events.append(("coolscan-preview", value)) + ), + ) + + AppController.start_coolscan_roll_preview(controller, request) + + self.assertEqual( + events, + ["ensure-coolscan-thread", ("coolscan-preview", request)], + ) + def test_thumbnail_refreshes_on_config_changed_settle(self): """Filmstrip thumbnail is re-captured on every settled render whose config differs from the last capture (covers in-place edits and reset), but not on a diff --git a/tests/test_crosstalk_profiles.py b/tests/test_crosstalk_profiles.py index 182205d5..e892aa6b 100644 --- a/tests/test_crosstalk_profiles.py +++ b/tests/test_crosstalk_profiles.py @@ -1,4 +1,6 @@ import os +import subprocess +import sys import pytest @@ -51,6 +53,48 @@ def test_malformed_skipped(tmp_path, monkeypatch): assert CrosstalkProfiles.list_profiles() == ["Default"] +@pytest.mark.skipif(not hasattr(os, "mkfifo"), reason="named pipes are not available on this platform") +def test_list_profiles_ignores_special_files_and_symlinks_without_blocking(tmp_path): + user_dir = tmp_path / "user" + crosstalk_dir = user_dir / "crosstalk" + crosstalk_dir.mkdir(parents=True) + linked_target = tmp_path / "outside.toml" + _write( + linked_target, + 'name = "Unsafe Symlink"\nmatrix = [[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]]\n', + ) + os.symlink(linked_target, crosstalk_dir / "linked.toml") + os.mkfifo(crosstalk_dir / "stale.toml") + + env = os.environ.copy() + env["NEGPY_USER_DIR"] = str(user_dir) + completed = subprocess.run( + [ + sys.executable, + "-c", + ("from negpy.services.assets.crosstalk import CrosstalkProfiles; print(CrosstalkProfiles.list_profiles())"), + ], + cwd=os.getcwd(), + env=env, + capture_output=True, + text=True, + timeout=3, + check=True, + ) + + assert "Unsafe Symlink" not in completed.stdout + + +def test_list_profiles_ignores_unreasonably_large_profile(tmp_path, monkeypatch): + monkeypatch.setattr(APP_CONFIG, "crosstalk_dir", str(tmp_path)) + _write( + tmp_path / "oversized.toml", + "#" + ("x" * (64 * 1024)) + '\nname = "Oversized"\nmatrix = [[1, 0, 0], [0, 1, 0], [0, 0, 1]]\n', + ) + + assert "Oversized" not in CrosstalkProfiles.list_profiles() + + def test_ensure_user_dir_creates_directory(tmp_path, monkeypatch): target = tmp_path / "nested" / "crosstalk" monkeypatch.setattr(APP_CONFIG, "crosstalk_dir", str(target)) diff --git a/tests/test_ls5000_frozen_packaging.py b/tests/test_ls5000_frozen_packaging.py new file mode 100644 index 00000000..4e29a65f --- /dev/null +++ b/tests/test_ls5000_frozen_packaging.py @@ -0,0 +1,880 @@ +"""Offline contracts for the frozen LS-5000 capture entry point.""" + +import ast +import hashlib +import os +import platform +import plistlib +import runpy +import subprocess +import sys +import types +from pathlib import Path +from types import SimpleNamespace + +import pytest +from PyInstaller.utils.hooks import collect_all + +from coolscanpy.protocol.ls5000_single_pass.capture_process import ( + CAPTURE_HELPER_FLAG, +) + + +REPO = Path(__file__).resolve().parents[1] + + +def test_desktop_script_dispatches_frozen_auxiliary_before_gui_import( + monkeypatch: pytest.MonkeyPatch, +) -> None: + """The real entry point must not import Qt/desktop code for helper mode.""" + + dispatched: list[list[str]] = [] + frozen_entry = types.ModuleType("negpy.desktop.frozen_entry") + frozen_entry.dispatch_frozen_auxiliary = lambda argv: dispatched.append(argv) or True + monkeypatch.setitem(sys.modules, "negpy.desktop.frozen_entry", frozen_entry) + monkeypatch.delitem(sys.modules, "negpy.desktop.main", raising=False) + real_import = __import__ + + def reject_gui_import(name, *args, **kwargs): + if name == "negpy.desktop.main": + raise AssertionError("GUI desktop imported during frozen auxiliary dispatch") + return real_import(name, *args, **kwargs) + + monkeypatch.setattr("builtins.__import__", reject_gui_import) + monkeypatch.setattr(sys, "argv", ["NegPy", "--negpy-packaging-smoke"]) + + runpy.run_path(str(REPO / "desktop.py"), run_name="__main__") + + assert dispatched == [["NegPy", "--negpy-packaging-smoke"]] + + +def test_live_acceptance_dispatch_forwards_exact_arguments_without_qt( + monkeypatch: pytest.MonkeyPatch, +) -> None: + """The private live runner must enter before the frozen Qt desktop.""" + + from negpy.desktop import frozen_entry + + forwarded: list[list[str]] = [] + live_acceptance = types.ModuleType("negpy.services.roll.live_acceptance") + live_acceptance.main = lambda argv: forwarded.append(argv) or 0 + monkeypatch.setitem( + sys.modules, + "negpy.services.roll.live_acceptance", + live_acceptance, + ) + real_import = __import__ + + def reject_gui_import(name, *args, **kwargs): + if name == "negpy.desktop.main" or name.startswith("PyQt6"): + raise AssertionError("Qt desktop imported during live acceptance dispatch") + return real_import(name, *args, **kwargs) + + monkeypatch.setattr("builtins.__import__", reject_gui_import) + arguments = [ + "--device-id", + "usb:2:7", + "--confirm-live", + ] + + handled = frozen_entry.dispatch_frozen_auxiliary( + ["NegPy", frozen_entry.LIVE_ACCEPTANCE_FLAG, *arguments] + ) + + assert handled is True + assert forwarded == [arguments] + assert "LIVE_ACCEPTANCE_FLAG" in frozen_entry.__all__ + assert "dispatch_live_acceptance" in frozen_entry.__all__ + + +def test_live_acceptance_dispatch_propagates_failure_exit_code( + monkeypatch: pytest.MonkeyPatch, +) -> None: + from negpy.desktop import frozen_entry + + live_acceptance = types.ModuleType("negpy.services.roll.live_acceptance") + live_acceptance.main = lambda _argv: 23 + monkeypatch.setitem( + sys.modules, + "negpy.services.roll.live_acceptance", + live_acceptance, + ) + + with pytest.raises(SystemExit) as stopped: + frozen_entry.dispatch_frozen_auxiliary( + ["NegPy", frozen_entry.LIVE_ACCEPTANCE_FLAG] + ) + + assert stopped.value.code == 23 + + +def test_capture_helper_dispatch_runs_the_real_worker_without_usb(capsys) -> None: + from negpy.desktop.main import _dispatch_capture_helper + + handled = _dispatch_capture_helper( + [ + "NegPy", + CAPTURE_HELPER_FLAG, + "--frame", + "18", + "--boundary-offset-rows", + "0", + "--confirm-full-capture", + ] + ) + + assert handled is True + output = capsys.readouterr().out + assert "619458560 bytes" in output + assert "dry run only; scanner was not accessed" in output + + +def test_desktop_main_stops_before_bootstrap_for_capture_helper( + monkeypatch: pytest.MonkeyPatch, +) -> None: + import negpy.desktop.main as desktop_main + + monkeypatch.setattr(desktop_main, "_dispatch_capture_helper", lambda _argv: True) + + def fail_if_bootstrapped(*_args, **_kwargs): + raise AssertionError("desktop bootstrap ran in capture-helper mode") + + monkeypatch.setattr(desktop_main, "load_override", fail_if_bootstrapped) + monkeypatch.setattr( + desktop_main, + "_macos_frozen_documents_handoff", + lambda: (_ for _ in ()).throw(AssertionError("handoff ran in capture-helper mode")), + ) + + desktop_main.main() + + +class _FakeMacApplication: + events: list[str] = [] + + @staticmethod + def instance(): + return None + + def __init__(self, _argv) -> None: + self.events.append("application") + + def processEvents(self) -> None: + self.events.append("events") + + +class _FakeMacProgress: + window_flags: list[tuple[object, bool]] = [] + + def __init__(self, *_args) -> None: + _FakeMacApplication.events.append("progress") + + def setWindowTitle(self, _title: str) -> None: + pass + + def setCancelButton(self, _button) -> None: + pass + + def setAutoClose(self, _enabled: bool) -> None: + pass + + def setAutoReset(self, _enabled: bool) -> None: + pass + + def setWindowFlag(self, _flag, _enabled: bool) -> None: + self.window_flags.append((_flag, _enabled)) + + def setWindowModality(self, _modality) -> None: + pass + + def show(self) -> None: + _FakeMacApplication.events.append("show") + + def close(self) -> None: + _FakeMacApplication.events.append("close") + + +def _configure_frozen_macos_handoff(monkeypatch: pytest.MonkeyPatch, desktop_main) -> None: + _FakeMacApplication.events = [] + _FakeMacProgress.window_flags = [] + monkeypatch.setattr(sys, "platform", "darwin") + monkeypatch.setattr(sys, "frozen", True, raising=False) + monkeypatch.delenv(desktop_main._MACOS_DOCUMENTS_READY_ENV, raising=False) # noqa: SLF001 - startup seam + monkeypatch.setattr(desktop_main, "QApplication", _FakeMacApplication) + monkeypatch.setattr(desktop_main, "_MacOSDocumentsHandoff", _FakeMacProgress) + + +def test_macos_frozen_handoff_shows_ui_before_probe_and_reexecs( + monkeypatch: pytest.MonkeyPatch, + tmp_path: Path, +) -> None: + import negpy.desktop.main as desktop_main + + _configure_frozen_macos_handoff(monkeypatch, desktop_main) + monkeypatch.setattr(desktop_main, "BASE_USER_DIR", str(tmp_path)) + + def probe(_path: str) -> None: + _FakeMacApplication.events.append("probe") + + class ReexecCalled(Exception): + pass + + def reexec(executable: str, argv: list[str], env: dict[str, str]) -> None: + _FakeMacApplication.events.append("reexec") + assert executable == str(Path(sys.executable).absolute()) + assert argv[0] == executable + assert env[desktop_main._MACOS_DOCUMENTS_READY_ENV] == "1" # noqa: SLF001 - startup seam + raise ReexecCalled + + monkeypatch.setattr(desktop_main, "_probe_user_data_access", probe) + monkeypatch.setattr(desktop_main.os, "execve", reexec) + + with pytest.raises(ReexecCalled): + desktop_main._macos_frozen_documents_handoff() # noqa: SLF001 - startup seam + + assert _FakeMacApplication.events.index("application") < _FakeMacApplication.events.index("probe") + assert _FakeMacApplication.events.index("show") < _FakeMacApplication.events.index("probe") + assert _FakeMacApplication.events[-1] == "reexec" + assert (desktop_main.Qt.WindowType.WindowCloseButtonHint, False) in _FakeMacProgress.window_flags + + +def test_macos_frozen_handoff_consumes_its_one_process_sentinel( + monkeypatch: pytest.MonkeyPatch, +) -> None: + import negpy.desktop.main as desktop_main + + _configure_frozen_macos_handoff(monkeypatch, desktop_main) + monkeypatch.setenv(desktop_main._MACOS_DOCUMENTS_READY_ENV, "1") # noqa: SLF001 - startup seam + monkeypatch.setattr( + desktop_main, + "_probe_user_data_access", + lambda _path: (_ for _ in ()).throw(AssertionError("child probed Documents again")), + ) + + assert desktop_main._macos_frozen_documents_handoff() is False # noqa: SLF001 - startup seam + assert desktop_main._MACOS_DOCUMENTS_READY_ENV not in os.environ # noqa: SLF001 - startup seam + assert _FakeMacApplication.events == [] + + +def test_macos_frozen_handoff_denial_stops_startup_without_bootstrap( + monkeypatch: pytest.MonkeyPatch, +) -> None: + import negpy.desktop.main as desktop_main + + _configure_frozen_macos_handoff(monkeypatch, desktop_main) + errors: list[tuple] = [] + monkeypatch.setattr(desktop_main, "_dispatch_packaging_smoke", lambda _argv: False) + monkeypatch.setattr(desktop_main, "_dispatch_capture_helper", lambda _argv: False) + monkeypatch.setattr(desktop_main, "_probe_user_data_access", lambda _path: (_ for _ in ()).throw(PermissionError("denied"))) + monkeypatch.setattr( + desktop_main, + "QMessageBox", + SimpleNamespace(critical=lambda *args: errors.append(args)), + ) + monkeypatch.setattr( + desktop_main, + "load_override", + lambda _path: (_ for _ in ()).throw(AssertionError("override loaded after denial")), + ) + monkeypatch.setattr( + desktop_main, + "_bootstrap_environment", + lambda: (_ for _ in ()).throw(AssertionError("bootstrap ran after denial")), + ) + + desktop_main.main() + + assert errors + assert "Documents access" in errors[0][1] + assert "progress" in _FakeMacApplication.events + + +def test_macos_handoff_ignores_close_events(qapp) -> None: + from PyQt6.QtGui import QCloseEvent + + from negpy.desktop.main import _MacOSDocumentsHandoff + + handoff = _MacOSDocumentsHandoff("Opening workspace", None, 0, 0) + event = QCloseEvent() + handoff.closeEvent(event) + + assert not event.isAccepted() + + +def test_macos_access_probe_removes_its_empty_file(tmp_path: Path) -> None: + from negpy.desktop.main import _MACOS_ACCESS_PROBE_PREFIX, _probe_user_data_access + + _probe_user_data_access(str(tmp_path)) + + assert not list(tmp_path.glob(f"{_MACOS_ACCESS_PROBE_PREFIX}*")) + + +def test_desktop_packaging_smoke_dispatches_before_bootstrap( + monkeypatch: pytest.MonkeyPatch, + capsys: pytest.CaptureFixture[str], +) -> None: + from negpy.desktop import frozen_entry + + checked: list[bool] = [] + monkeypatch.setattr( + frozen_entry, + "run_packaging_smoke_checks", + lambda: checked.append(True), + ) + + handled = frozen_entry.dispatch_packaging_smoke(["NegPy", frozen_entry.PACKAGING_SMOKE_FLAG]) + + assert handled is True + assert checked == [True] + assert frozen_entry.PACKAGING_SMOKE_OK in capsys.readouterr().out + + +def test_pyinstaller_collects_the_frozen_scanner_and_dice_runtimes() -> None: + build_source = (REPO / "build.py").read_text(encoding="utf-8") + frozen_entry_source = (REPO / "negpy/desktop/frozen_entry.py").read_text(encoding="utf-8") + + required_options = { + "--collect-all=coolscanpy", + "--hidden-import=coolscanpy.protocol.ls5000_single_pass.worker", + "--hidden-import=negpy.services.roll.live_acceptance", + "--collect-all=portable_digital_ice", + } + assert all(option in build_source for option in required_options) + assert 'LIVE_ACCEPTANCE_FLAG = "--ls5000-live-acceptance"' in build_source + assert "--add-data=negpy/assets/portable_cms:negpy/assets/portable_cms" in build_source + # This constructor performs frozen hash/schema validation of the packaged + # 12-event receipt as well as the nine transform binaries. + smoke_function = next( + node + for node in ast.parse(frozen_entry_source).body + if isinstance(node, ast.FunctionDef) and node.name == "run_packaging_smoke_checks" + ) + smoke_calls = {node.func.id for node in ast.walk(smoke_function) if isinstance(node, ast.Call) and isinstance(node.func, ast.Name)} + assert "check_jit_execution" in smoke_calls + assert "PortableCMSOnEvaluator" in smoke_calls + assert "_require_registered_repair_engine" in smoke_calls + assert "_require_pinned_hybrid_runtime" in smoke_calls + assert "--codesign-identity=" in build_source + + +@pytest.mark.skipif(sys.platform != "darwin", reason="macOS bundle identity contract") +def test_macos_build_uses_a_stable_reverse_dns_bundle_identifier() -> None: + import build + + assert build.MACOS_BUNDLE_IDENTIFIER == "org.negpy.NegPy" + assert f"--osx-bundle-identifier={build.MACOS_BUNDLE_IDENTIFIER}" in build.params + + +@pytest.mark.skipif(sys.platform != "darwin", reason="macOS hardened-runtime contract") +def test_macos_build_uses_only_the_llvmlite_executable_memory_entitlement() -> None: + import build + + entitlements_path = Path(build.MACOS_ENTITLEMENTS_FILE) + entitlements = plistlib.loads(entitlements_path.read_bytes()) + + assert entitlements == { + "com.apple.security.cs.allow-unsigned-executable-memory": True, + } + assert f"--osx-entitlements-file={entitlements_path}" in build.params + + +@pytest.mark.skipif(sys.platform != "darwin", reason="macOS hardened-runtime contract") +def test_macos_embedded_entitlements_are_read_back_as_xml( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + import build + + app_path = tmp_path / "NegPy.app" + embedded = {"com.apple.security.cs.allow-unsigned-executable-memory": True} + calls: list[list[str]] = [] + + def emit_entitlements(argv, **_kwargs): + calls.append(list(argv)) + return subprocess.CompletedProcess(argv, 0, stdout=plistlib.dumps(embedded), stderr=b"") + + monkeypatch.setattr(build.subprocess, "run", emit_entitlements) + + assert build.verify_macos_runtime_entitlements(str(app_path)) == embedded + assert calls == [ + [ + "/usr/bin/codesign", + "--display", + "--entitlements", + "-", + "--xml", + str(app_path), + ] + ] + + +@pytest.mark.skipif(sys.platform != "darwin", reason="macOS hardened-runtime contract") +def test_macos_developer_id_signature_exposes_the_hardened_runtime_flag( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + import build + + app_path = tmp_path / "NegPy.app" + calls: list[list[str]] = [] + + def emit_signature_details(argv, **_kwargs): + calls.append(list(argv)) + return subprocess.CompletedProcess(argv, 0, stdout="", stderr="CodeDirectory v=20500 flags=0x10000(runtime)\n") + + monkeypatch.setattr(build.subprocess, "run", emit_signature_details) + + assert build.verify_macos_hardened_runtime(str(app_path)) == 0x10000 + assert calls == [["/usr/bin/codesign", "--display", "--verbose=4", str(app_path)]] + + +@pytest.mark.skipif(sys.platform != "darwin", reason="macOS hardened-runtime contract") +def test_macos_developer_id_signature_rejects_a_non_runtime_seal( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + import build + + monkeypatch.setattr( + build.subprocess, + "run", + lambda argv, **_kwargs: subprocess.CompletedProcess( + argv, + 0, + stdout="", + stderr="CodeDirectory v=20400 flags=0x2(adhoc)\n", + ), + ) + + with pytest.raises(RuntimeError, match="missing the hardened-runtime"): + build.verify_macos_hardened_runtime(str(tmp_path / "NegPy.app")) + + +@pytest.mark.skipif(sys.platform != "darwin", reason="macOS hardened-runtime contract") +@pytest.mark.parametrize( + "embedded", + [ + {}, + { + "com.apple.security.cs.allow-unsigned-executable-memory": True, + "com.apple.security.cs.allow-jit": True, + }, + { + "com.apple.security.cs.allow-unsigned-executable-memory": True, + "com.apple.security.cs.disable-library-validation": True, + }, + ], +) +def test_macos_embedded_entitlement_verification_fails_closed( + embedded: dict[str, bool], + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + import build + + def emit_entitlements(argv, **_kwargs): + return subprocess.CompletedProcess(argv, 0, stdout=plistlib.dumps(embedded), stderr=b"") + + monkeypatch.setattr(build.subprocess, "run", emit_entitlements) + + with pytest.raises(RuntimeError, match="unexpected hardened-runtime entitlements"): + build.verify_macos_runtime_entitlements(str(tmp_path / "NegPy.app")) + + +def test_frozen_smoke_rejects_an_unregistered_dice_bridge( + monkeypatch: pytest.MonkeyPatch, +) -> None: + from negpy.desktop import frozen_entry + from negpy.infrastructure.roll import repair as roll_repair + + monkeypatch.setattr(roll_repair, "available", lambda: False) + + with pytest.raises(RuntimeError, match="Digital ICE repair bridge"): + frozen_entry._require_registered_repair_engine() # noqa: SLF001 - frozen acceptance seam + + +def test_frozen_smoke_rehashes_the_pinned_hybrid_model(tmp_path: Path) -> None: + from negpy.desktop import frozen_entry + + model = tmp_path / "big-lama.pt" + model.write_bytes(b"offline pinned model fixture") + expected = hashlib.sha256(model.read_bytes()).hexdigest() + validated: list[bool] = [] + runtime = SimpleNamespace( + model_weights=model, + model_weights_sha256=expected, + validate_files=lambda: validated.append(True), + ) + + actual = frozen_entry._require_pinned_hybrid_runtime( # noqa: SLF001 - frozen acceptance seam + loader=lambda: runtime, + ) + + assert actual == expected + assert validated == [True] + + +@pytest.mark.parametrize("failure", ["missing-runtime", "wrong-model", "symlink-model"]) +def test_frozen_smoke_rejects_an_unpinned_hybrid_runtime( + failure: str, + tmp_path: Path, +) -> None: + from negpy.desktop import frozen_entry + + model = tmp_path / "big-lama.pt" + model.write_bytes(b"offline pinned model fixture") + expected = hashlib.sha256(model.read_bytes()).hexdigest() + runtime = SimpleNamespace( + model_weights=model, + model_weights_sha256=expected, + validate_files=lambda: None, + ) + if failure == "missing-runtime": + runtime_to_load = None + else: + runtime_to_load = runtime + if failure == "wrong-model": + runtime.model_weights_sha256 = "0" * 64 + elif failure == "symlink-model": + alias = tmp_path / "model-link.pt" + alias.symlink_to(model) + runtime.model_weights = alias + + def loader(): + return runtime_to_load + + with pytest.raises(RuntimeError, match="hybrid runtime|model weights"): + frozen_entry._require_pinned_hybrid_runtime( # noqa: SLF001 - frozen acceptance seam + loader=loader, + ) + + +def test_normal_desktop_startup_rejects_model_corruption_after_manifest_load( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + from negpy.desktop import main as desktop_main + from negpy.services.repair.fauxice_hybrid_runner import HybridRuntimeConfig + + executables = [tmp_path / name for name in ("python", "fauxce-hybrid", "iopaint-python", "iopaint")] + for executable in executables: + executable.write_bytes(b"offline executable fixture") + executable.chmod(0o755) + model_dir = tmp_path / "models" + model_dir.mkdir() + model = model_dir / "big-lama.pt" + model.write_bytes(b"corrupted after the manifest was pinned") + runtime = HybridRuntimeConfig( + hybrid_python=executables[0], + executable=executables[1], + core_source_manifest_sha256="1" * 64, + hybrid_source_manifest_sha256="2" * 64, + iopaint_python=executables[2], + iopaint_executable=executables[3], + iopaint_source_manifest_sha256="3" * 64, + model_dir=model_dir, + model_weights=model, + model_weights_sha256="0" * 64, + ) + monkeypatch.setattr(desktop_main, "load_default_hybrid_runtime_manifest", lambda: runtime) + + assert desktop_main._load_desktop_hybrid_runtime() is None # noqa: SLF001 - startup gate + + +def test_pyinstaller_hooks_find_capture_assets_and_dice_backends() -> None: + coolscan_data, _coolscan_binaries, coolscan_modules = collect_all("coolscanpy") + _dice_data, _dice_binaries, dice_modules = collect_all("portable_digital_ice") + + captured_names = {Path(source).name for source, _destination in coolscan_data} + assert { + "replay-first-rgbi4-manifest.json", + "replay-first-rgbi4-plan.jsonl", + "replay-next-rgbi4-plan.json", + } <= captured_names + assert "coolscanpy.protocol.ls5000_single_pass.worker" in coolscan_modules + assert { + "portable_digital_ice.backend", + "portable_digital_ice.fast_cpu.engine", + "portable_digital_ice.metal_backend.engine", + } <= set(dice_modules) + + +def test_build_preflight_rejects_a_stale_coolscan_api( + monkeypatch: pytest.MonkeyPatch, +) -> None: + import build + + real_import = build.importlib.import_module + + def import_without_exact_builder(module_name: str): + if module_name == "coolscanpy.protocol.ls5000_single_pass.density": + return SimpleNamespace( + build_nikon_density_evidence=lambda: None, + build_nikon_exact_builder_evidence=lambda: None, + ) + return real_import(module_name) + + monkeypatch.setattr(build.importlib, "import_module", import_without_exact_builder) + + with pytest.raises(RuntimeError, match="NikonExactBuilderEvidence"): + build.preflight_coolscan_runtime() + + +def test_build_preflight_accepts_the_current_sealed_capture_bundle() -> None: + import build + from coolscanpy.protocol.ls5000_single_pass.bundle import CAPTURE_BUNDLE_SHA256 + + assert build.preflight_coolscan_runtime() == CAPTURE_BUNDLE_SHA256 + + +@pytest.mark.skipif(sys.platform != "darwin", reason="macOS dylib packaging contract") +def test_macos_build_declares_a_present_arch_compatible_libusb() -> None: + import build + + source = Path(build.MACOS_LIBUSB_SOURCE) + + assert source.is_absolute() + assert source.is_file() + assert source.name == build.COOLSCAN_LIBUSB_FILENAME + assert f"--add-binary={source}:coolscanpy/_native" in build.params + architecture = subprocess.run( + ["/usr/bin/lipo", str(source), "-verify_arch", platform.machine()], + check=False, + capture_output=True, + text=True, + ) + assert architecture.returncode == 0, architecture.stderr + + +@pytest.mark.skipif(sys.platform != "darwin", reason="macOS signing contract") +def test_macos_build_signs_libusb_before_resealing_the_app( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + import build + + app_path = tmp_path / "NegPy.app" + bundled_libusb = app_path / "Contents" / "Frameworks" / build.COOLSCAN_LIBUSB_DESTINATION / build.COOLSCAN_LIBUSB_FILENAME + bundled_libusb.parent.mkdir(parents=True) + bundled_libusb.write_bytes(b"offline signing fixture") + calls: list[list[str]] = [] + entitlement_checks: list[str] = [] + + def record_run(argv, **_kwargs): + calls.append(list(argv)) + return subprocess.CompletedProcess(argv, 0) + + monkeypatch.setattr(build.subprocess, "run", record_run) + monkeypatch.setattr( + build, + "verify_macos_runtime_entitlements", + lambda path: entitlement_checks.append(path), + ) + monkeypatch.setenv("NEGPY_CODESIGN_IDENTITY", "-") + + signed_path = build.sign_macos_scanner_runtime(str(app_path)) + + assert signed_path == str(bundled_libusb) + signing_targets = [call[-1] for call in calls if "--sign" in call] + assert signing_targets == [str(bundled_libusb), str(app_path)] + verification_targets = [call[-1] for call in calls if "--verify" in call] + assert verification_targets == [str(bundled_libusb), str(app_path)] + assert entitlement_checks == [str(app_path)] + + +@pytest.mark.skipif(sys.platform != "darwin", reason="macOS signing contract") +def test_macos_reseal_reapplies_the_exact_pyinstaller_entitlements( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + import build + + app_path = tmp_path / "NegPy.app" + bundled_libusb = app_path / "Contents" / "Frameworks" / build.COOLSCAN_LIBUSB_DESTINATION / build.COOLSCAN_LIBUSB_FILENAME + bundled_libusb.parent.mkdir(parents=True) + bundled_libusb.write_bytes(b"offline signing fixture") + calls: list[list[str]] = [] + + def record_run(argv, **_kwargs): + calls.append(list(argv)) + return subprocess.CompletedProcess(argv, 0) + + monkeypatch.setattr(build.subprocess, "run", record_run) + monkeypatch.setattr(build, "verify_macos_runtime_entitlements", lambda _path: None) + monkeypatch.setenv("NEGPY_CODESIGN_IDENTITY", "-") + + build.sign_macos_scanner_runtime(str(app_path)) + + app_sign = next(call for call in calls if "--sign" in call and call[-1] == str(app_path)) + entitlement_index = app_sign.index("--entitlements") + assert app_sign[entitlement_index + 1] == build.MACOS_ENTITLEMENTS_FILE + preserve_option = next(option for option in app_sign if option.startswith("--preserve-metadata=")) + assert "entitlements" not in preserve_option.split("=", 1)[1].split(",") + + +@pytest.mark.skipif(sys.platform != "darwin", reason="macOS signing contract") +def test_macos_developer_id_reseal_verifies_hardened_runtime( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + import build + + app_path = tmp_path / "NegPy.app" + bundled_libusb = app_path / "Contents" / "Frameworks" / build.COOLSCAN_LIBUSB_DESTINATION / build.COOLSCAN_LIBUSB_FILENAME + bundled_libusb.parent.mkdir(parents=True) + bundled_libusb.write_bytes(b"offline signing fixture") + runtime_checks: list[str] = [] + + monkeypatch.setattr( + build.subprocess, + "run", + lambda argv, **_kwargs: subprocess.CompletedProcess(argv, 0), + ) + monkeypatch.setattr(build, "verify_macos_runtime_entitlements", lambda _path: None) + monkeypatch.setattr( + build, + "verify_macos_hardened_runtime", + lambda path: runtime_checks.append(path), + ) + monkeypatch.setenv("NEGPY_CODESIGN_IDENTITY", "Developer ID Application: NegPy Test") + + build.sign_macos_scanner_runtime(str(app_path)) + + assert runtime_checks == [str(app_path)] + + +@pytest.mark.skipif(sys.platform != "darwin", reason="macOS app smoke contract") +def test_macos_postbuild_smoke_uses_only_offline_frozen_entry_points( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + import build + + app_path = tmp_path / "NegPy.app" + executable = app_path / "Contents" / "MacOS" / "NegPy" + executable.parent.mkdir(parents=True) + executable.write_bytes(b"offline executable fixture") + executable.chmod(0o755) + bundled_libusb = app_path / "Contents" / "Frameworks" / build.COOLSCAN_LIBUSB_DESTINATION / build.COOLSCAN_LIBUSB_FILENAME + bundled_libusb.parent.mkdir(parents=True) + bundled_libusb.write_bytes(b"offline dylib fixture") + + class LoadedLibusb: + libusb_init = object() + libusb_exit = object() + + monkeypatch.setattr(build.ctypes, "CDLL", lambda _path: LoadedLibusb()) + commands: list[list[str]] = [] + environments: list[dict[str, str]] = [] + ambient_loader_variables = { + "DYLD_FALLBACK_FRAMEWORK_PATH", + "DYLD_FALLBACK_LIBRARY_PATH", + "DYLD_FRAMEWORK_PATH", + "DYLD_INSERT_LIBRARIES", + "DYLD_LIBRARY_PATH", + } + for variable in ambient_loader_variables: + monkeypatch.setenv(variable, "/untrusted/host/path") + + def complete(argv, **kwargs): + command = list(argv) + commands.append(command) + environments.append(kwargs["env"]) + if build.PACKAGING_SMOKE_FLAG in command: + stdout = build.PACKAGING_SMOKE_OK + "\n" + else: + stdout = "validated RGBI4x plan: 619458560 bytes\ndry run only; scanner was not accessed\n" + return subprocess.CompletedProcess(command, 0, stdout, "") + + monkeypatch.setattr(build.subprocess, "run", complete) + + result = build.smoke_macos_scanner_runtime(str(app_path)) + + assert result["libusb"] == str(bundled_libusb) + assert [command[1] for command in commands] == [ + build.PACKAGING_SMOKE_FLAG, + build.CAPTURE_HELPER_FLAG, + ] + assert all("--live" not in command for command in commands) + assert all(build.LIVE_ACCEPTANCE_FLAG not in command for command in commands) + assert all(ambient_loader_variables.isdisjoint(environment) for environment in environments) + + +@pytest.mark.skipif(sys.platform != "darwin", reason="macOS app smoke contract") +def test_macos_postbuild_smoke_refuses_the_live_acceptance_flag( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + import build + + app_path = tmp_path / "NegPy.app" + executable = app_path / "Contents" / "MacOS" / "NegPy" + executable.parent.mkdir(parents=True) + executable.write_bytes(b"offline executable fixture") + executable.chmod(0o755) + bundled_libusb = app_path / "Contents" / "Frameworks" / build.COOLSCAN_LIBUSB_DESTINATION / build.COOLSCAN_LIBUSB_FILENAME + bundled_libusb.parent.mkdir(parents=True) + bundled_libusb.write_bytes(b"offline dylib fixture") + + class LoadedLibusb: + libusb_init = object() + libusb_exit = object() + + monkeypatch.setattr(build.ctypes, "CDLL", lambda _path: LoadedLibusb()) + monkeypatch.setattr(build, "PACKAGING_SMOKE_FLAG", build.LIVE_ACCEPTANCE_FLAG) + monkeypatch.setattr( + build.subprocess, + "run", + lambda *_args, **_kwargs: (_ for _ in ()).throw( + AssertionError("live acceptance reached subprocess") + ), + ) + + with pytest.raises(RuntimeError, match="attempted to enable live acceptance"): + build.smoke_macos_scanner_runtime(str(app_path)) + + +@pytest.mark.skipif(sys.platform != "darwin", reason="macOS app smoke contract") +def test_macos_postbuild_smoke_resolves_a_relative_bundle_before_chdir( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + """A build invoked from its project root must not resolve dist twice.""" + import build + + app_path = tmp_path / "dist" / "NegPy.app" + executable = app_path / "Contents" / "MacOS" / "NegPy" + executable.parent.mkdir(parents=True) + executable.write_bytes(b"offline executable fixture") + executable.chmod(0o755) + bundled_libusb = app_path / "Contents" / "Frameworks" / build.COOLSCAN_LIBUSB_DESTINATION / build.COOLSCAN_LIBUSB_FILENAME + bundled_libusb.parent.mkdir(parents=True) + bundled_libusb.write_bytes(b"offline dylib fixture") + + class LoadedLibusb: + libusb_init = object() + libusb_exit = object() + + monkeypatch.chdir(tmp_path) + monkeypatch.setattr(build.ctypes, "CDLL", lambda _path: LoadedLibusb()) + commands: list[list[str]] = [] + working_directories: list[str] = [] + + def complete(argv, **kwargs): + command = list(argv) + commands.append(command) + working_directories.append(kwargs["cwd"]) + stdout = ( + build.PACKAGING_SMOKE_OK + "\n" + if build.PACKAGING_SMOKE_FLAG in command + else "validated RGBI4x plan: 619458560 bytes\ndry run only; scanner was not accessed\n" + ) + return subprocess.CompletedProcess(command, 0, stdout, "") + + monkeypatch.setattr(build.subprocess, "run", complete) + + result = build.smoke_macos_scanner_runtime("dist/NegPy.app") + + assert result["executable"] == str(executable.resolve()) + assert all(command[0] == str(executable.resolve()) for command in commands) + assert working_directories == [str(app_path.parent.resolve())] * 2 diff --git a/tests/test_main_window.py b/tests/test_main_window.py index 3e75f23a..f40f477c 100644 --- a/tests/test_main_window.py +++ b/tests/test_main_window.py @@ -2,6 +2,7 @@ from unittest.mock import patch import numpy as np +from PyQt6.QtCore import QEvent from negpy.desktop.view.main_window import _display_buffer_for_canvas @@ -55,5 +56,52 @@ def test_drop_auto_opens_like_add_dialogs(self): stub.controller.request_asset_discovery.assert_called_once_with(["/tmp/scan.dng"], auto_open=True) +class TestCloseEvent(unittest.TestCase): + def test_unresolved_scanner_close_ignores_window_close(self): + from types import SimpleNamespace + from unittest.mock import MagicMock + + from negpy.desktop.view.main_window import MainWindow + + controller = MagicMock() + controller.request_shutdown.return_value = False + controller.shutdown_block_reason = "ownership remains uncertain" + status_bar = MagicMock() + stub = SimpleNamespace( + controller=controller, + statusBar=lambda: status_bar, + show=MagicMock(), + raise_=MagicMock(), + activateWindow=MagicMock(), + ) + stub.request_shutdown_for_exit = lambda: MainWindow.request_shutdown_for_exit( + stub + ) + event = MagicMock() + + with patch( + "negpy.desktop.view.main_window.QMessageBox.critical" + ) as critical: + MainWindow.closeEvent(stub, event) + + event.ignore.assert_called_once_with() + controller.session.repo.save_global_setting.assert_not_called() + status_bar.showMessage.assert_called_once() + critical.assert_called_once() + + def test_application_quit_event_is_consumed_when_shutdown_is_blocked(self): + from unittest.mock import MagicMock + + from negpy.desktop.main import _ApplicationShutdownGate + + allow_shutdown = MagicMock(return_value=False) + gate = _ApplicationShutdownGate(allow_shutdown) + event = MagicMock() + event.type.return_value = QEvent.Type.Quit + + assert gate.eventFilter(None, event) is True + allow_shutdown.assert_called_once_with() + + if __name__ == "__main__": unittest.main() diff --git a/tests/test_roll_controller.py b/tests/test_roll_controller.py index de979ebe..71c31f13 100644 --- a/tests/test_roll_controller.py +++ b/tests/test_roll_controller.py @@ -9,7 +9,10 @@ from types import SimpleNamespace from unittest.mock import MagicMock +import pytest + from negpy.desktop.controller import AppController +from negpy.services.roll.service import RollScanningError def _output(slot: int, rgb_path: str): @@ -71,3 +74,52 @@ def test_batch_with_no_rgb_paths_at_all_does_not_request_discovery(): AppController._on_roll_scan_finished(controller, outputs) controller.request_asset_discovery.assert_not_called() + + +def test_request_shutdown_blocks_when_scanner_close_is_unresolved(): + worker = MagicMock() + worker.shutdown.side_effect = RuntimeError("ownership remains uncertain") + controller = SimpleNamespace( + _roll_shutdown_complete=False, + _shutdown_block_reason=None, + roll_worker=worker, + roll_status=MagicMock(), + ) + + assert AppController.request_shutdown(controller) is False + + assert controller._roll_shutdown_complete is False + assert controller._shutdown_block_reason == "ownership remains uncertain" + controller.roll_status.emit.assert_called_once() + + +def test_request_shutdown_is_idempotent_after_verified_close(): + worker = MagicMock() + controller = SimpleNamespace( + _roll_shutdown_complete=False, + _shutdown_block_reason="old failure", + roll_worker=worker, + roll_status=MagicMock(), + ) + + assert AppController.request_shutdown(controller) is True + assert AppController.request_shutdown(controller) is True + + worker.shutdown.assert_called_once_with() + assert controller._shutdown_block_reason is None + + +def test_cleanup_refuses_to_quit_worker_threads_before_scanner_close(): + roll_thread = MagicMock() + controller = SimpleNamespace( + _cleaned_up=False, + _shutdown_block_reason="ownership remains uncertain", + request_shutdown=lambda: False, + roll_thread=roll_thread, + ) + + with pytest.raises(RollScanningError, match="ownership remains uncertain"): + AppController.cleanup(controller) + + assert controller._cleaned_up is False + roll_thread.quit.assert_not_called() diff --git a/tests/test_user_dir_resolution.py b/tests/test_user_dir_resolution.py index 202dfede..7eeed2f7 100644 --- a/tests/test_user_dir_resolution.py +++ b/tests/test_user_dir_resolution.py @@ -42,6 +42,20 @@ def test_env_override_wins(monkeypatch, tmp_path): assert get_default_user_dir() == os.path.abspath(str(tmp_path / "custom")) +def test_macos_resolution_is_path_only_before_the_visible_startup_handoff(monkeypatch, tmp_path): + """Resolving the frozen app's default cannot itself touch protected Documents.""" + home = tmp_path / "home" + + monkeypatch.delenv("NEGPY_USER_DIR", raising=False) + monkeypatch.setattr("sys.platform", "darwin") + monkeypatch.setattr(os.path, "expanduser", lambda p: str(home) if p == "~" else p) + monkeypatch.setattr(os.path, "isdir", lambda _path: (_ for _ in ()).throw(AssertionError("resolution touched Documents"))) + monkeypatch.setattr(os, "makedirs", lambda *_args, **_kwargs: (_ for _ in ()).throw(AssertionError("resolution created Documents"))) + + assert get_default_user_dir() == str((home / "Documents" / "NegPy").absolute()) + assert not home.exists() + + def test_broken_documents_falls_back_to_home(monkeypatch, tmp_path): home = tmp_path / "home" home.mkdir() diff --git a/tests/test_wheel_packaging.py b/tests/test_wheel_packaging.py new file mode 100644 index 00000000..0d5f27aa --- /dev/null +++ b/tests/test_wheel_packaging.py @@ -0,0 +1,176 @@ +"""Regression coverage for the installable NegPy wheel boundary.""" + +from __future__ import annotations + +import hashlib +import os +import site as site_module +import subprocess +import sys +import zipfile +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] +ROLL_MODULES = { + f"negpy/services/roll/{name}" + for name in ( + "__init__.py", + "exact_color.py", + "nikon_icc.py", + "portable_builder.py", + "portable_cms.py", + "portable_oracle_evaluator.py", + "positive.py", + "service.py", + ) +} + + +def _run( + command: list[str], + *, + cwd: Path, + env: dict[str, str] | None = None, +) -> subprocess.CompletedProcess[str]: + completed = subprocess.run( + command, + cwd=cwd, + env=env, + check=False, + capture_output=True, + text=True, + ) + assert completed.returncode == 0, ( + f"command failed ({completed.returncode}): {' '.join(command)}\nstdout:\n{completed.stdout}\nstderr:\n{completed.stderr}" + ) + return completed + + +def test_wheel_contains_and_loads_all_negpy_packages_and_portable_assets(tmp_path: Path) -> None: + dist = tmp_path / "dist" + _run(["uv", "build", "--wheel", "--out-dir", str(dist)], cwd=ROOT) + wheels = list(dist.glob("*.whl")) + assert len(wheels) == 1 + wheel = wheels[0] + + expected_python = {path.relative_to(ROOT).as_posix() for path in (ROOT / "negpy").rglob("*.py")} + expected_assets = { + path.relative_to(ROOT).as_posix(): hashlib.sha256(path.read_bytes()).hexdigest() + for directory in ( + ROOT / "negpy/assets/portable_builder", + ROOT / "negpy/assets/portable_cms", + ) + for path in directory.iterdir() + if path.suffix in {".bin", ".json"} + } + assert len(expected_assets) == 11 + assert sum(name.endswith(".bin") for name in expected_assets) == 10 + assert sum(name.endswith(".json") for name in expected_assets) == 1 + + with zipfile.ZipFile(wheel) as archive: + packaged = set(archive.namelist()) + packaged_python = {name for name in packaged if name.startswith("negpy/") and name.endswith(".py")} + packaged_assets = { + name + for name in packaged + if name.startswith("negpy/assets/portable_") + and Path(name).suffix in {".bin", ".json"} + } + assert packaged_python == expected_python + assert ROLL_MODULES <= packaged + assert packaged_assets == set(expected_assets) + assert {name: hashlib.sha256(archive.read(name)).hexdigest() for name in expected_assets} == expected_assets + + venv = tmp_path / "venv" + _run(["uv", "venv", "--python", sys.executable, str(venv)], cwd=tmp_path) + venv_python = venv / ("Scripts/python.exe" if os.name == "nt" else "bin/python") + _run( + ["uv", "pip", "install", "--python", str(venv_python), "--no-deps", str(wheel)], + cwd=tmp_path, + ) + venv_site = Path( + _run( + [str(venv_python), "-c", "import site; print(site.getsitepackages()[0])"], + cwd=tmp_path, + ).stdout.strip() + ) + dependency_site = next(Path(path) for path in site_module.getsitepackages() if (Path(path) / "numpy").is_dir()) + (venv_site / "negpy-test-dependencies.pth").write_text( + str(dependency_site) + "\n", + encoding="utf-8", + ) + smoke = """ +import importlib +from pathlib import Path +import negpy.services.roll.exact_color as exact_color +import negpy.services.roll.nikon_icc as nikon_icc +import negpy.services.roll.portable_builder as portable_builder +import negpy.services.roll.portable_cms as portable_cms +import negpy.services.roll.service as service + +site = Path(__import__('sys').argv[1]).resolve() +roll_modules = [ + importlib.import_module(name) + for name in ( + 'negpy.services.roll', + 'negpy.services.roll.exact_color', + 'negpy.services.roll.nikon_icc', + 'negpy.services.roll.portable_builder', + 'negpy.services.roll.portable_cms', + 'negpy.services.roll.positive', + 'negpy.services.roll.service', + ) +] +assert len(roll_modules) == 7 +for module in roll_modules: + assert Path(module.__file__).resolve().is_relative_to(site), module.__file__ +builder = portable_builder.PortableStage1Builder() +cms = portable_cms.PortableCMSOnEvaluator() +assert 'negpy.services.roll.portable_oracle_evaluator' not in __import__('sys').modules +assert len(list(builder.assets_dir.glob('*.bin'))) == 1 +assert len(list(cms.assets_dir.glob('*.bin'))) == 9 +validation = cms.assets_dir / portable_cms.VALIDATION_RECEIPT_FILENAME +assert validation.is_file() +assert validation.stat().st_size == portable_cms.VALIDATION_RECEIPT_BYTES +assert __import__('hashlib').sha256(validation.read_bytes()).hexdigest() == portable_cms.VALIDATION_RECEIPT_SHA256 +profile = nikon_icc.nikon_adobe_rgb_profile() +assert len(profile) == 492 +assert __import__('hashlib').sha256(profile).hexdigest() == nikon_icc.NIKON_ADOBE_RGB_PROFILE_SHA256 +""" + env = os.environ.copy() + env.pop("PYTHONPATH", None) + _run([str(venv_python), "-c", smoke, str(venv_site)], cwd=tmp_path, env=env) + + validation_path = ( + venv_site + / "negpy/assets/portable_cms/portable-oracle-receipt.json" + ) + validation_payload = validation_path.read_bytes() + for failure, expected in ( + ("missing", "portable CMS validation receipt is unavailable"), + ("one-byte", "portable CMS validation receipt hash mismatch"), + ): + if failure == "missing": + validation_path.unlink() + else: + tampered = bytearray(validation_payload) + tampered[len(tampered) // 2] ^= 1 + validation_path.write_bytes(tampered) + try: + completed = subprocess.run( + [ + str(venv_python), + "-c", + "from negpy.services.roll.portable_cms import PortableCMSOnEvaluator; PortableCMSOnEvaluator()", + ], + cwd=tmp_path, + env=env, + check=False, + capture_output=True, + text=True, + ) + assert completed.returncode != 0 + assert expected in completed.stderr + finally: + validation_path.write_bytes(validation_payload) diff --git a/uv.lock b/uv.lock index 11aa69d2..85f5f46e 100644 --- a/uv.lock +++ b/uv.lock @@ -76,8 +76,8 @@ wheels = [ [[package]] name = "coolscanpy" -version = "0.1.2" -source = { registry = "https://pypi.org/simple" } +version = "0.1.3" +source = { git = "https://github.com/rohanpandula/coolscanpy.git?rev=733151d25b8e962e3d5cb6ac1a6989315dc4e1ed#733151d25b8e962e3d5cb6ac1a6989315dc4e1ed" } dependencies = [ { name = "imagecodecs" }, { name = "jinja2" }, @@ -86,10 +86,6 @@ dependencies = [ { name = "pyusb" }, { name = "tifffile" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/c6/d0/2ab023bcd243daa927f20e3cdaac0db36a8a4b5df02d3424640c7863e7bd/coolscanpy-0.1.2.tar.gz", hash = "sha256:1ff98ea9689b19ad765bb7109ac18ab9d71509d266e87cbdf8434fa14577808e", size = 289030, upload-time = "2026-07-19T02:58:42.101Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f9/66/18473737fcba32a57fbef3d2668b6f15d94c869810d1a7dbeb4fff57a7bd/coolscanpy-0.1.2-py3-none-any.whl", hash = "sha256:a780eb3613b30e1299913c3c5f33f6b3255382ac4c3309e46691a071ec477a24", size = 296086, upload-time = "2026-07-19T02:58:40.819Z" }, -] [[package]] name = "coverage" @@ -436,7 +432,7 @@ requires-dist = [ [package.metadata.requires-dev] camera = [{ name = "gphoto2", marker = "sys_platform != 'win32'", specifier = ">=2.5" }] -coolscan-roll = [{ name = "coolscanpy", specifier = ">=0.1.2" }] +coolscan-roll = [{ name = "coolscanpy", git = "https://github.com/rohanpandula/coolscanpy.git?rev=733151d25b8e962e3d5cb6ac1a6989315dc4e1ed" }] dev = [ { name = "pyinstaller", specifier = ">=6.18.0" }, { name = "pytest", specifier = "==9.0.2" }, From 5b4eba85a687b44a26a8ad15a5a59ee098a2a7ce Mon Sep 17 00:00:00 2001 From: Rohan Pandula Date: Wed, 22 Jul 2026 18:22:07 -0700 Subject: [PATCH 23/48] feat(ls5000): bind retained live capture evidence --- .github/workflows/ci.yml | 7 +- build.py | 15 + docs/COOLSCANPY_ROLL_SCANNING.md | 49 +- negpy/infrastructure/roll/coolscanpy_roll.py | 13 +- negpy/services/roll/live_acceptance.py | 283 +++++++- negpy/services/roll/live_evidence.py | 652 +++++++++++++++++++ negpy/services/roll/service.py | 14 +- pyproject.toml | 2 +- tests/roll/test_coolscanpy_roll.py | 30 + tests/roll/test_live_acceptance.py | 643 +++++++++++++++++- tests/roll/test_live_evidence.py | 591 +++++++++++++++++ tests/roll/test_service.py | 19 + tests/test_ls5000_frozen_packaging.py | 30 + uv.lock | 4 +- 14 files changed, 2299 insertions(+), 53 deletions(-) create mode 100644 negpy/services/roll/live_evidence.py create mode 100644 tests/roll/test_live_evidence.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 622f8d34..58754c3a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,7 +30,12 @@ jobs: sudo apt-get install -y libegl1 libxkbcommon-x11-0 libdbus-1-3 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-xinerama0 libxcb-xfixes0 x11-utils libgl1 - name: Install Dependencies - run: uv sync --group dev + run: >- + uv sync --locked + --group dev + --group coolscan-roll + --group fauxice + --group fauxice-hybrid - name: Run Linting (Ruff) run: uv run ruff check . diff --git a/build.py b/build.py index 8dd55419..6668e534 100644 --- a/build.py +++ b/build.py @@ -2,6 +2,7 @@ import glob import importlib import importlib.util +import inspect import os import platform import plistlib @@ -129,6 +130,7 @@ def resolve_macos_libusb_dylib() -> str: _REQUIRED_COOLSCAN_API = { "coolscanpy": ( + "Device", "DigitalIceAcquisition", "DigitalIceAcquisitionEvidence", "build_digital_ice_acquisition_evidence", @@ -155,6 +157,19 @@ def preflight_coolscan_runtime() -> str: if missing: raise RuntimeError("Coolscan build API is stale or incomplete: " + ", ".join(missing)) + device_type = getattr(loaded["coolscanpy"], "Device") + roll_parameters = inspect.signature(device_type.roll).parameters + attempts_root = roll_parameters.get("attempts_root") + if ( + attempts_root is None + or attempts_root.kind is not inspect.Parameter.KEYWORD_ONLY + or attempts_root.default is not None + ): + raise RuntimeError( + "Coolscan build API is stale or incomplete: " + "coolscanpy.Device.roll must expose optional keyword-only attempts_root" + ) + bundle_module = loaded["coolscanpy.protocol.ls5000_single_pass.bundle"] verify_capture_bundle = getattr(bundle_module, "verify_capture_bundle") bundle_sha256 = verify_capture_bundle(require_python_sources=True) diff --git a/docs/COOLSCANPY_ROLL_SCANNING.md b/docs/COOLSCANPY_ROLL_SCANNING.md index 249360fe..7a9a8a05 100644 --- a/docs/COOLSCANPY_ROLL_SCANNING.md +++ b/docs/COOLSCANPY_ROLL_SCANNING.md @@ -38,11 +38,14 @@ uv sync --group coolscan-roll ``` That group pins the reviewed immutable commit -`733151d25b8e962e3d5cb6ac1a6989315dc4e1ed`, built on the merged streaming +`c9cc36d4890f1e013d0e51b5e67dffb6dc1b98ef`, built on the merged streaming finalization from [PR #1](https://github.com/rohanpandula/coolscanpy/pull/1). The pin includes exact USB-topology ownership, six-strip leading-edge handling, Nikon density/exact-builder evidence, and Digital ICE acquisition -evidence. Its complete Coolscan suite passed before the pin moved, and +evidence, retained caller-owned attempt evidence, terminal-tail-safe +short-strip mapping, and complete continuation-frame meter-layout receipts +(required for multi-frame Nikon-exact publication and for NegPy's +six-frame capture-evidence validation). Its complete Coolscan suite passed before the pin moved, and `build.py` independently rejects a stale API or capture-bundle hash before packaging. The pin can move to a tagged release once those APIs are published. @@ -69,7 +72,7 @@ so a signed build fails before release if the JIT exception is absent. If you manage your own environment instead, install the package directly: ``` -pip install coolscanpy +pip install "coolscanpy @ git+https://github.com/rohanpandula/coolscanpy.git@c9cc36d4890f1e013d0e51b5e67dffb6dc1b98ef" ``` The accepted color-negative roll workflow has no SANE dependency. @@ -94,6 +97,35 @@ LS-5000-only where it does not have to be, but nothing beyond that one combination has scanned real film. Windows has never run the roll engine against hardware. +## Durable live-acceptance evidence + +The six-frame command-line acceptance requires `--attempts-root`. Point it at +an existing, empty, non-symlink directory owned by this one run. It must be +different from `--output-dir`, and the run receipt must live outside both +directories. NegPy passes that directory to +`coolscanpy.Device.roll(..., attempts_root=...)`; unlike Coolscan's default +temporary workspace, its worker journals and durable acquisition evidence +survive a successful close and remain available for audit. Fine-scan +`capture.bin` streams are verified scratch: Coolscan hashes and finalizes each +one, then intentionally deletes it after the frame has been consumed. + +The final run receipt records these files under `capture_evidence`. NegPy +hashes a complete path/size/SHA-256 inventory, caps its canonical manifest at +128 KiB, and binds it to exactly one completed slots-1-through-6 batch. A +successful binding requires the hashed batch job, the session journal's +`batch_job_sha256`, all six frame journals, every fine-capture hash and byte +count recorded before scratch deletion, all six +durable meter sidecars and parent acknowledgements, the first-frame preview, +transport table, and frame map, exact USB topology, and the sealed +plan/engine/bundle identities. Extra attempt trees, incomplete or inconsistent +frame evidence, mixed sessions, and post-hash mutations are fatal. If evidence +changes during success or failure finalization, the receipt is downgraded +truthfully to `retained: false` instead of preserving an earlier success claim. + +This caller-owned attempt tree is scanner-transport evidence for the whole +live run. It is separate from the per-frame movable Digital ICE acquisition +archive described below; neither archive substitutes for the other. + ## Structure The camera capture route (Scanlight light source plus a tethered camera, @@ -265,15 +297,14 @@ between the frame and its public receipt makes exact color unavailable. There is deliberately no generic Roll/session evidence cache that can be promoted later. -In the current local reviewed overlay, a C-41 frame also carries +The pinned Coolscan release gives each C-41 frame `nikon_exact_builder_evidence`, which binds its settled 285-dpi analyzer raster and final exposure triplet to the same ownership pair. `Frame` construction revalidates the ownership, density, builder, and Digital ICE -bindings before NegPy can consume them. The published streaming-only Git pin -does not yet produce these fields; a clean build therefore fails its Coolscan -API preflight instead of silently publishing an app that cannot complete the -native exact path. The Stage-3 replay route remains independently usable with -an explicit validated replay receipt. +bindings before NegPy can consume them. A clean build verifies that API and +the sealed Coolscan capture-bundle hash before packaging, so a stale pin cannot +silently publish an app that lacks the native exact path. The Stage-3 replay +route remains independently usable with an explicit validated replay receipt. The Stage-3 replay bridge is deliberately not presented as the production builder architecture. The macOS-native path derives fresh pre-F LUTs for each diff --git a/negpy/infrastructure/roll/coolscanpy_roll.py b/negpy/infrastructure/roll/coolscanpy_roll.py index 82cb4bb4..95bf77cd 100644 --- a/negpy/infrastructure/roll/coolscanpy_roll.py +++ b/negpy/infrastructure/roll/coolscanpy_roll.py @@ -35,6 +35,7 @@ from __future__ import annotations import importlib.util +from pathlib import Path from typing import TYPE_CHECKING, Iterable, Iterator if TYPE_CHECKING: @@ -153,7 +154,12 @@ def list_devices() -> "list[coolscanpy.DeviceInfo]": return coolscanpy.get_devices() -def open_roll(device_id: str | None = None, *, material: "coolscanpy.Material | None" = None) -> RollHandle: +def open_roll( + device_id: str | None = None, + *, + material: "coolscanpy.Material | None" = None, + attempts_root: str | Path | None = None, +) -> RollHandle: """Open one LS-5000 and its roll-feeder extension. `device_id` follows `coolscanpy.open()`: omitted (`None`) picks "the one @@ -173,7 +179,10 @@ def open_roll(device_id: str | None = None, *, material: "coolscanpy.Material | raise _translate(error) from error try: - roll = device.roll(material=resolved_material) + roll_kwargs = {"material": resolved_material} + if attempts_root is not None: + roll_kwargs["attempts_root"] = Path(attempts_root) + roll = device.roll(**roll_kwargs) except Exception as error: device.close() raise _translate(error) from error diff --git a/negpy/services/roll/live_acceptance.py b/negpy/services/roll/live_acceptance.py index deb9c1fd..d3377383 100644 --- a/negpy/services/roll/live_acceptance.py +++ b/negpy/services/roll/live_acceptance.py @@ -10,7 +10,9 @@ import math import os import re +import signal import stat +import threading import uuid from collections.abc import Callable from datetime import UTC, datetime @@ -33,6 +35,12 @@ FixedOutputLease, ReservationConflict, ) +from negpy.services.roll.live_evidence import ( + CaptureEvidenceSnapshot, + bind_capture_evidence_inventory, + snapshot_capture_evidence, + validate_six_frame_batch_capture_evidence, +) from negpy.services.roll.live_review import ( ValidatedReviewedApproval, approval_payload, @@ -50,6 +58,8 @@ _SESSION_MAX_BYTES = 64 * 1024 _FRAME_RECEIPT_MAX_BYTES = 16 * 1024 * 1024 _SHA256_RE = re.compile(r"[0-9a-f]{64}") +# The frozen app enumerates attached units with direct usb:BUS:ADDRESS ids. +_DIRECT_DEVICE_ID_RE = re.compile(r"usb:[0-9]{1,4}:[0-9]{1,4}") _MAX_TELEMETRY_ERRORS = 16 @@ -70,6 +80,7 @@ class LiveAcceptanceRequest: reviewed_approval_sha256: str output_dir: Path run_receipt_path: Path + attempts_root_path: Path confirm_live: bool hybrid_runtime_manifest_path: Path | None = None @@ -183,7 +194,11 @@ def _default_review_loader( ) -def _preflight_paths(request: LiveAcceptanceRequest) -> tuple[Path, Path]: +def _paths_overlap(left: Path, right: Path) -> bool: + return left == right or left.is_relative_to(right) or right.is_relative_to(left) + + +def _preflight_paths(request: LiveAcceptanceRequest) -> tuple[Path, Path, Path]: output = request.output_dir.resolve(strict=True) linked_output = request.output_dir.lstat() if stat.S_ISLNK(linked_output.st_mode) or not stat.S_ISDIR(linked_output.st_mode): @@ -195,7 +210,15 @@ def _preflight_paths(request: LiveAcceptanceRequest) -> tuple[Path, Path]: receipt = receipt_parent / receipt.name if receipt == output or receipt.is_relative_to(output): raise ValueError("run receipt must be outside the output directory") - return output, receipt + attempts = request.attempts_root_path.resolve(strict=True) + linked_attempts = request.attempts_root_path.lstat() + if stat.S_ISLNK(linked_attempts.st_mode) or not stat.S_ISDIR(linked_attempts.st_mode): + raise ValueError("attempts root must be an existing non-symlink directory") + if _paths_overlap(attempts, output): + raise ValueError("attempts root must be disjoint from the output directory") + if receipt == attempts or receipt.is_relative_to(attempts): + raise ValueError("run receipt must be outside the attempts root") + return output, receipt, attempts def _require_artifact(path_value: str | None, *, output_dir: Path, label: str) -> str: @@ -367,6 +390,7 @@ def run_live_acceptance( batch_validator: Callable[..., dict[str, Any]] = validate_six_frame_batch, receipt_reserver: Callable[..., Any] = ExclusiveReceiptReservation.reserve, output_lease_factory: Callable[..., Any] = FixedOutputLease.acquire, + evidence_lease_factory: Callable[..., Any] = FixedOutputLease.acquire, emit: Callable[[dict[str, Any]], None] = _json_event, ) -> dict[str, Any]: """Run one exact six-slot batch; any discrepancy ends it without retry.""" @@ -395,6 +419,7 @@ def run_live_acceptance( runtime: Any = None deep_batch: dict[str, Any] | None = None output_dir: Path | None = None + attempts_root: Path | None = None receipt_path = request.run_receipt_path.absolute() receipt_reservation: Any = None receipt_inode: tuple[int, int] | None = None @@ -403,6 +428,14 @@ def run_live_acceptance( lease_acquired = False lease_release_attempted = False lease_released = False + evidence_lease: Any = None + evidence_lease_acquired = False + evidence_lease_release_attempted = False + evidence_lease_released = False + evidence_snapshot: CaptureEvidenceSnapshot | None = None + evidence_inventory: Any = None + capture_evidence: dict[str, object] | None = None + capture_evidence_error: dict[str, str] | None = None inventory_snapshot: Any = None owned_files: set[Path] = set() service: Any = None @@ -491,6 +524,20 @@ def receipt_document( ), }, "output_dir": str(output_dir or request.output_dir.absolute()), + "capture_evidence": ( + capture_evidence + if capture_evidence is not None + else { + "path": str(attempts_root or request.attempts_root_path.absolute()), + "retained": False, + "manifest_sha256": None, + "file_count": None, + "directory_count": None, + "total_bytes": None, + "files": None, + "snapshot_error": capture_evidence_error, + } + ), "run_receipt": str(receipt_path), "receipt_reservation": { "reserved": receipt_reserved, @@ -502,6 +549,12 @@ def receipt_document( "release_attempted": lease_release_attempted, "released": lease_released, }, + "capture_evidence_lease": { + "acquired": evidence_lease_acquired, + "lock_name": OUTPUT_LOCK_NAME, + "release_attempted": evidence_lease_release_attempted, + "released": evidence_lease_released, + }, "slots": list(SLOTS), "approved_slots": list(approved_slots), "operation_state": operation_state(), @@ -551,7 +604,13 @@ def on_scan_progress(progress: object) -> None: raise ValueError("--confirm-live is required") if not request.device_id.strip(): raise ValueError("device id must be non-empty") - output_dir, receipt_path = _preflight_paths(request) + if _DIRECT_DEVICE_ID_RE.fullmatch(request.device_id) is None: + raise ValueError( + "device id must be the direct usb:BUS:ADDRESS form reported by " + "this app's own enumeration (for example usb:2:7); SANE-style " + f"identifiers are not accepted here, got {request.device_id!r}" + ) + output_dir, receipt_path, attempts_root = _preflight_paths(request) phase = "reserving_receipt" receipt_reservation = receipt_reserver( @@ -574,6 +633,19 @@ def on_scan_progress(progress: object) -> None: ) lease_acquired = True inventory_snapshot = output_lease.assert_inventory(()) + + phase = "reserving_capture_evidence" + evidence_lease = evidence_lease_factory( + attempts_root, + { + "schema": "negpy.ls5000-live-capture-evidence-lock.v1", + "run_id": run_id, + "run_receipt": str(receipt_path), + "created_at": started_at, + }, + require_empty=True, + ) + evidence_lease_acquired = True phase = "output_reserved" receipt_reservation.publish(receipt_document("in_progress")) @@ -611,7 +683,10 @@ def on_scan_progress(progress: object) -> None: previous=inventory_snapshot, ) phase = "opening_roll" - service.open_roll(request.device_id) + service.open_roll( + request.device_id, + attempts_root=attempts_root, + ) roll_opened = True safe_emit({"event": "roll_opened", "device_id": request.device_id}) @@ -792,6 +867,24 @@ def on_repair_progress( if iterator_created and not batch_exhausted: transport_may_have_advanced_beyond_yielded = True + if evidence_lease is not None: + try: + if roll_opened and not close_succeeded: + raise RuntimeError("capture evidence cannot be sealed while scanner ownership is uncertain") + if attempts_root is None: + raise RuntimeError("capture evidence path was not resolved") + evidence_snapshot = snapshot_capture_evidence(attempts_root) + evidence_inventory = evidence_lease.assert_inventory(evidence_snapshot.files) + capture_evidence = bind_capture_evidence_inventory( + evidence_snapshot, + evidence_inventory, + ) + capture_evidence["snapshot_error"] = None + except BaseException as evidence_error: + capture_evidence_error = _error_payload(evidence_error) + if operation_error is None: + operation_error = evidence_error + if operation_error is None: try: phase = "validating_six_frame_batch" @@ -799,6 +892,12 @@ def on_repair_progress( raise RuntimeError("live acceptance state is incomplete") if not close_succeeded: raise RuntimeError("scanner was not closed before deep acceptance") + if evidence_snapshot is None or evidence_inventory is None or capture_evidence is None: + raise RuntimeError("capture evidence was not sealed before deep acceptance") + batch_binding = validate_six_frame_batch_capture_evidence(evidence_snapshot) + if review is None or (batch_binding.get("reviewed_fingerprint_sha256") != review.reviewed_fingerprint_sha256): + raise RuntimeError("capture evidence batch does not match the reviewed fingerprint") + capture_evidence["batch_binding"] = batch_binding receipt_reservation.publish(receipt_document("in_progress")) deep_batch = batch_validator( outputs, @@ -868,42 +967,79 @@ def on_repair_progress( operation_error = validation_error receipt: dict[str, Any] | None = None + failed_receipt_published = False if operation_error is None: if output_lease is None or inventory_snapshot is None: operation_error = RuntimeError("output lease state is incomplete at finalization") elif receipt_reservation is None: operation_error = RuntimeError("run receipt was never reserved") + elif evidence_lease is None or evidence_snapshot is None or evidence_inventory is None: + operation_error = RuntimeError("capture evidence lease state is incomplete at finalization") if operation_error is None: lease_release_attempted = True phase = "finalizing_success" safe_emit({"event": "run_finalizing", "status": "succeeded"}) - def publish_success_while_directory_is_held() -> None: - nonlocal lease_released, phase, receipt - # release_verified invokes this only after unlinking the fixed - # lock, while retaining the directory descriptor for a second - # pathname + inventory check after publication. - lease_released = True - phase = "succeeded" - safe_emit( - { - "event": "run_finished", - "status": "succeeded", - "run_receipt": str(receipt_path), - } - ) - receipt = receipt_document( - "succeeded", - finished_at=_utc_now(), - ) - receipt_reservation.publish(receipt) + def publish_success_while_output_directory_is_held() -> None: + nonlocal capture_evidence + nonlocal capture_evidence_error + nonlocal evidence_lease_release_attempted + nonlocal evidence_lease_released + nonlocal lease_released + nonlocal phase + nonlocal receipt + + evidence_lease_release_attempted = True + + def publish_success_while_both_directories_are_held() -> None: + nonlocal evidence_lease_released + nonlocal lease_released + nonlocal phase + nonlocal receipt + + # Both fixed locks have been unlinked, but both directory + # descriptors remain held. Each lease performs a second exact + # inventory check after this durable receipt publication. + evidence_lease_released = True + lease_released = True + phase = "succeeded" + safe_emit( + { + "event": "run_finished", + "status": "succeeded", + "run_receipt": str(receipt_path), + } + ) + receipt = receipt_document( + "succeeded", + finished_at=_utc_now(), + ) + receipt_reservation.publish(receipt) + + try: + evidence_lease.release_verified( + evidence_snapshot.files, + previous=evidence_inventory, + finalize=publish_success_while_both_directories_are_held, + ) + except BaseException as evidence_lease_error: + capture_evidence_error = _error_payload(evidence_lease_error) + if capture_evidence is not None: + capture_evidence = { + **capture_evidence, + "retained": False, + "snapshot_error": capture_evidence_error, + } + raise + finally: + evidence_lease_released = bool(evidence_lease.released) try: output_lease.release_verified( owned_files, previous=inventory_snapshot, - finalize=publish_success_while_directory_is_held, + finalize=publish_success_while_output_directory_is_held, ) except BaseException as lease_error: operation_error = _combine_error( @@ -936,18 +1072,71 @@ def publish_success_while_directory_is_held() -> None: "run_receipt": str(receipt_path), } ) - receipt = receipt_document( - "failed", - finished_at=_utc_now(), - error=operation_error, - ) + if evidence_lease is not None and not evidence_lease.released: + evidence_lease_release_attempted = True + if evidence_snapshot is not None and evidence_inventory is not None: + + def publish_failure_while_evidence_directory_is_held() -> None: + nonlocal evidence_lease_released + nonlocal failed_receipt_published + nonlocal receipt + + evidence_lease_released = True + receipt = receipt_document( + "failed", + finished_at=_utc_now(), + error=operation_error, + ) + if receipt_reservation is not None: + receipt_reservation.publish(receipt) + failed_receipt_published = True + + try: + evidence_lease.release_verified( + evidence_snapshot.files, + previous=evidence_inventory, + finalize=publish_failure_while_evidence_directory_is_held, + ) + except BaseException as evidence_lease_error: + operation_error = _combine_error( + operation_error, + evidence_lease_error, + label="verified capture evidence release failed", + ) + capture_evidence_error = _error_payload(evidence_lease_error) + if capture_evidence is not None: + capture_evidence = { + **capture_evidence, + "retained": False, + "snapshot_error": capture_evidence_error, + } + failed_receipt_published = False + finally: + evidence_lease_released = bool(evidence_lease.released) + else: + try: + evidence_lease.release() + except BaseException as evidence_lease_error: + operation_error = _combine_error( + operation_error, + evidence_lease_error, + label="capture evidence lease cleanup failed", + ) + finally: + evidence_lease_released = bool(evidence_lease.released) + if not failed_receipt_published: + receipt = receipt_document( + "failed", + finished_at=_utc_now(), + error=operation_error, + ) assert receipt is not None if receipt_reservation is None: message = _error_payload(operation_error or RuntimeError("run receipt reservation failed"))["message"] raise LiveAcceptanceError(message, receipt=receipt) from operation_error - if receipt.get("status") == "failed": + if receipt.get("status") == "failed" and not failed_receipt_published: try: receipt_reservation.publish(receipt) except BaseException as receipt_error: @@ -986,12 +1175,45 @@ def _parser() -> argparse.ArgumentParser: parser.add_argument("--reviewed-approval-sha256", required=True) parser.add_argument("--output-dir", required=True, type=Path) parser.add_argument("--run-receipt", required=True, type=Path) + parser.add_argument("--attempts-root", required=True, type=Path) parser.add_argument("--hybrid-runtime-manifest", type=Path) parser.add_argument("--confirm-live", action="store_true") return parser +class LiveAcceptanceInterrupted(BaseException): + """Raised at the interrupted call site when SIGINT/SIGTERM arrives. + + A BaseException on purpose: the acceptance's broad failure handling and + close/finalize path treat it exactly like any other fatal interruption, + producing a truthful failed receipt and a clean roll/service close. + """ + + +def install_interrupt_handlers() -> bool: + """Route SIGINT and SIGTERM through the truthful-failure path. + + The first signal raises LiveAcceptanceInterrupted at the interrupted call + site; later signals are ignored so close/finalize and receipt publication + cannot themselves be interrupted. Returns False outside the main thread, + where CPython forbids installing signal handlers. + """ + + if threading.current_thread() is not threading.main_thread(): + return False + + def _abort(signum: int, _frame: object) -> None: + signal.signal(signal.SIGINT, signal.SIG_IGN) + signal.signal(signal.SIGTERM, signal.SIG_IGN) + raise LiveAcceptanceInterrupted(f"received signal {signum}") + + signal.signal(signal.SIGINT, _abort) + signal.signal(signal.SIGTERM, _abort) + return True + + def main(argv: list[str] | None = None) -> int: + install_interrupt_handlers() args = _parser().parse_args(argv) request = LiveAcceptanceRequest( device_id=args.device_id, @@ -1001,6 +1223,7 @@ def main(argv: list[str] | None = None) -> int: reviewed_approval_sha256=args.reviewed_approval_sha256, output_dir=args.output_dir, run_receipt_path=args.run_receipt, + attempts_root_path=args.attempts_root, confirm_live=args.confirm_live, hybrid_runtime_manifest_path=args.hybrid_runtime_manifest, ) diff --git a/negpy/services/roll/live_evidence.py b/negpy/services/roll/live_evidence.py new file mode 100644 index 00000000..5ef30893 --- /dev/null +++ b/negpy/services/roll/live_evidence.py @@ -0,0 +1,652 @@ +"""Stable, receipt-ready inventory of retained Coolscan attempt evidence.""" + +from __future__ import annotations + +import dataclasses +import hashlib +import json +import os +import re +import stat +from pathlib import Path +from pathlib import PurePosixPath + +from negpy.services.roll.live_reservation import ( + OUTPUT_LOCK_NAME, + FileIdentity, + InventoryConflict, + InventorySnapshot, +) + + +CAPTURE_EVIDENCE_MANIFEST_SCHEMA = "negpy.coolscan-attempts-manifest.v1" +_MAX_FILES = 4_096 +_MAX_TOTAL_BYTES = 64 * 1024 * 1024 * 1024 +_MAX_MANIFEST_BYTES = 128 * 1024 +_MAX_BATCH_JSON_BYTES = 256 * 1024 +_READ_CHUNK_BYTES = 4 * 1024 * 1024 +_SIX_FRAME_SLOTS = (1, 2, 3, 4, 5, 6) +_SIX_FRAME_BATCH_ROOT_RE = re.compile(r"batch-slot01-slot06-[^/]+") +_SHA256_RE = re.compile(r"[0-9a-f]{64}") +# The batch worker mints every parent-ACK nonce with secrets.token_hex(16). +_ACK_NONCE_RE = re.compile(r"[0-9a-f]{32}") +_FIRST_PLAN_NAME = "replay-first-rgbi4-plan.jsonl" +_CONTINUATION_PLAN_NAME = "replay-next-rgbi4-plan.json" +_CAPTURE_MANIFEST_NAME = "replay-first-rgbi4-manifest.json" +_FINE_READS = 2_980 +_FINE_BYTES = 619_458_560 +_METER_PASS_BYTES = 1_088_000 +_METER_BYTES = _METER_PASS_BYTES * 3 +_PREVIEW_BYTES = 6_250_496 +_BATCH_JOB_KEYS = { + "apply_all_boundary_offsets_before_first_frame", + "capture_plan_sha256", + "continuation_plan_sha256", + "expected_usb_address", + "expected_usb_bus", + "frames", + "parent_ack_required_after_every_frame", + "release_once_after_last_frame", + "reviewed_roll_fingerprint", + "schema_version", + "session_id", + "session_contract", +} +_BATCH_FRAME_KEYS = { + "ack", + "boundary_offset_rows", + "journal", + "manual_review_approval", + "output", + "slot", +} +_ACK_KEYS = { + "ack_nonce", + "action", + "frame_index", + "schema_version", + "session_id", + "slot", +} +_METER_LAYOUT = { + "passes": 3, + "rows_per_pass": 425, + "columns": 281, + "decoded_raster_channel_order": ["R", "G", "B", "IR"], + "wire_window_color_order": [9, 1, 2, 3], + "wire_color_to_controller_channel": { + "9": "IR", + "1": "R", + "2": "G", + "3": "B", + }, + "sample_byte_order": "big-endian-u16", + "row_core_bytes": 2_248, + "row_stride_bytes": 2_560, + "row_tail_bytes": 312, +} + + +@dataclasses.dataclass(frozen=True) +class CaptureEvidenceSnapshot: + root: Path + files: tuple[Path, ...] + identities: tuple[tuple[str, FileIdentity], ...] + manifest: dict[str, object] + + +def _identity(metadata: os.stat_result) -> FileIdentity: + return FileIdentity.from_stat(metadata) + + +def _stable_file_digest(path: Path) -> tuple[int, str, FileIdentity]: + before = path.lstat() + if stat.S_ISLNK(before.st_mode) or not stat.S_ISREG(before.st_mode) or before.st_nlink != 1: + raise InventoryConflict(f"capture evidence must be an exclusively owned regular file: {path}") + flags = os.O_RDONLY | getattr(os, "O_CLOEXEC", 0) | getattr(os, "O_NOFOLLOW", 0) | getattr(os, "O_NONBLOCK", 0) + descriptor = os.open(path, flags) + try: + opened = os.fstat(descriptor) + if not stat.S_ISREG(opened.st_mode) or _identity(opened) != _identity(before): + raise InventoryConflict(f"capture evidence changed while opening: {path}") + digest = hashlib.sha256() + total = 0 + while True: + chunk = os.read(descriptor, _READ_CHUNK_BYTES) + if not chunk: + break + digest.update(chunk) + total += len(chunk) + if total > _MAX_TOTAL_BYTES: + raise InventoryConflict("capture evidence exceeds its safe size limit") + after_read = os.fstat(descriptor) + finally: + os.close(descriptor) + after_path = path.lstat() + opened_identity = _identity(opened) + if total != opened.st_size or opened_identity != _identity(after_read) or opened_identity != _identity(after_path): + raise InventoryConflict(f"capture evidence changed while hashing: {path}") + return total, digest.hexdigest(), opened_identity + + +def _stable_file_bytes(path: Path, *, maximum_bytes: int) -> bytes: + """Read one small evidence file without following or racing a pathname.""" + + before = path.lstat() + if stat.S_ISLNK(before.st_mode) or not stat.S_ISREG(before.st_mode) or before.st_nlink != 1: + raise InventoryConflict(f"capture evidence must be an exclusively owned regular file: {path}") + if before.st_size > maximum_bytes: + raise InventoryConflict(f"capture evidence JSON is too large: {path}") + flags = os.O_RDONLY | getattr(os, "O_CLOEXEC", 0) | getattr(os, "O_NOFOLLOW", 0) | getattr(os, "O_NONBLOCK", 0) + descriptor = os.open(path, flags) + try: + opened = os.fstat(descriptor) + if not stat.S_ISREG(opened.st_mode) or _identity(opened) != _identity(before): + raise InventoryConflict(f"capture evidence changed while opening: {path}") + chunks: list[bytes] = [] + total = 0 + while True: + chunk = os.read(descriptor, min(_READ_CHUNK_BYTES, maximum_bytes + 1 - total)) + if not chunk: + break + chunks.append(chunk) + total += len(chunk) + if total > maximum_bytes: + raise InventoryConflict(f"capture evidence JSON is too large: {path}") + after_read = os.fstat(descriptor) + finally: + os.close(descriptor) + after_path = path.lstat() + opened_identity = _identity(opened) + if total != opened.st_size or opened_identity != _identity(after_read) or opened_identity != _identity(after_path): + raise InventoryConflict(f"capture evidence changed while reading: {path}") + return b"".join(chunks) + + +def snapshot_capture_evidence(root: Path) -> CaptureEvidenceSnapshot: + """Hash one retained attempts tree without following links or special files.""" + + canonical = root.resolve(strict=True) + linked_root = root.lstat() + if stat.S_ISLNK(linked_root.st_mode) or not stat.S_ISDIR(linked_root.st_mode): + raise InventoryConflict("capture evidence root must be an existing non-symlink directory") + + paths: list[Path] = [] + for current_text, directory_names, file_names in os.walk( + canonical, + topdown=True, + followlinks=False, + ): + current = Path(current_text) + current_metadata = current.lstat() + if stat.S_ISLNK(current_metadata.st_mode) or not stat.S_ISDIR(current_metadata.st_mode): + raise InventoryConflict(f"capture evidence entry is not a real directory: {current}") + for name in directory_names: + child = current / name + metadata = child.lstat() + if stat.S_ISLNK(metadata.st_mode) or not stat.S_ISDIR(metadata.st_mode): + raise InventoryConflict(f"capture evidence entry is not a real directory: {child}") + for name in file_names: + path = current / name + if current == canonical and name == OUTPUT_LOCK_NAME: + continue + paths.append(path) + if len(paths) > _MAX_FILES: + raise InventoryConflict("capture evidence contains too many files") + + rows: list[dict[str, object]] = [] + identities: list[tuple[str, FileIdentity]] = [] + total_bytes = 0 + ordered_paths = sorted(paths, key=lambda item: item.relative_to(canonical).as_posix()) + for path in ordered_paths: + relative = path.relative_to(canonical).as_posix() + size, digest, identity = _stable_file_digest(path) + total_bytes += size + if total_bytes > _MAX_TOTAL_BYTES: + raise InventoryConflict("capture evidence exceeds its safe size limit") + rows.append({"path": relative, "bytes": size, "sha256": digest}) + identities.append((relative, identity)) + + manifest_document = { + "schema": CAPTURE_EVIDENCE_MANIFEST_SCHEMA, + "files": rows, + } + manifest_payload = ( + json.dumps( + manifest_document, + sort_keys=True, + separators=(",", ":"), + ensure_ascii=True, + allow_nan=False, + ).encode("utf-8") + + b"\n" + ) + if len(manifest_payload) > _MAX_MANIFEST_BYTES: + raise InventoryConflict("capture evidence manifest exceeds its receipt-safe size limit") + manifest = { + **manifest_document, + "manifest_sha256": hashlib.sha256(manifest_payload).hexdigest(), + "file_count": len(rows), + "total_bytes": total_bytes, + } + return CaptureEvidenceSnapshot( + root=canonical, + files=tuple(ordered_paths), + identities=tuple(identities), + manifest=manifest, + ) + + +def _snapshot_json( + snapshot: CaptureEvidenceSnapshot, + relative: str, +) -> dict[str, object]: + rows = snapshot.manifest.get("files") + if not isinstance(rows, list): + raise InventoryConflict("capture evidence manifest has no file inventory") + matches = [row for row in rows if isinstance(row, dict) and row.get("path") == relative] + if len(matches) != 1 or not isinstance(matches[0].get("sha256"), str): + raise InventoryConflict(f"capture evidence manifest does not bind {relative}") + payload = _stable_file_bytes( + snapshot.root / Path(relative), + maximum_bytes=_MAX_BATCH_JSON_BYTES, + ) + if hashlib.sha256(payload).hexdigest() != matches[0]["sha256"]: + raise InventoryConflict(f"capture evidence changed after hashing: {relative}") + def _reject_duplicate_keys(pairs: list[tuple[str, object]]) -> dict[str, object]: + merged: dict[str, object] = {} + for key, value in pairs: + if key in merged: + raise InventoryConflict( + f"capture evidence JSON repeats key {key!r}: {relative}" + ) + merged[key] = value + return merged + + try: + document = json.loads(payload, object_pairs_hook=_reject_duplicate_keys) + except (UnicodeDecodeError, json.JSONDecodeError) as error: + raise InventoryConflict(f"capture evidence JSON is invalid: {relative}") from error + if not isinstance(document, dict): + raise InventoryConflict(f"capture evidence JSON is not an object: {relative}") + return document + + +def _is_sha256(value: object) -> bool: + return isinstance(value, str) and _SHA256_RE.fullmatch(value) is not None + + +def _approval_binding(value: object) -> str | None: + if value is None: + return None + if not isinstance(value, dict): + raise InventoryConflict("accepted batch has a malformed manual approval") + binding = value.get("binding_sha256") + if not isinstance(binding, str) or not _is_sha256(binding): + raise InventoryConflict("accepted batch has a malformed manual approval") + return binding + + +def _usb_component(value: object, *, address: bool) -> int: + lower, upper = (1, 127) if address else (0, 999) + if isinstance(value, bool) or not isinstance(value, int) or not lower <= value <= upper: + label = "address" if address else "bus" + raise InventoryConflict(f"accepted batch USB {label} is malformed") + return value + + +def _artifact_row( + row_by_path: dict[str, dict[str, object]], + relative: str, +) -> dict[str, object]: + row = row_by_path.get(relative) + if not isinstance(row, dict) or type(row.get("bytes")) is not int or row["bytes"] < 0 or not _is_sha256(row.get("sha256")): + raise InventoryConflict(f"capture evidence manifest does not bind {relative}") + return row + + +def validate_six_frame_batch_capture_evidence( + snapshot: CaptureEvidenceSnapshot, +) -> dict[str, object]: + """Bind the durable post-finalization tree for one completed six-frame batch. + + Coolscan deletes each 619 MB ``capture.bin`` only after hashing, decoding, + and verifying its private completion outputs. The caller-owned tree keeps + the six journals, three-pass meter sidecars, parent ACKs, first-frame live + index artifacts, and sealed batch inputs. Those are the artifacts this + function requires and cross-binds; a deleted fine-stream scratch file is + deliberately not part of the retained contract. + """ + + rows = snapshot.manifest.get("files") + if not isinstance(rows, list): + raise InventoryConflict("capture evidence manifest has no file inventory") + typed_rows = [row for row in rows if isinstance(row, dict) and isinstance(row.get("path"), str)] + if len(typed_rows) != len(rows): + raise InventoryConflict("capture evidence manifest has a malformed file row") + relative_paths = {row["path"] for row in typed_rows} + if len(relative_paths) != len(typed_rows): + raise InventoryConflict("capture evidence manifest has duplicate file paths") + row_by_path = {row["path"]: row for row in typed_rows} + session_journals: list[PurePosixPath] = [] + for relative in relative_paths: + path = PurePosixPath(relative) + if len(path.parts) == 2 and path.name == "session-journal.json" and _SIX_FRAME_BATCH_ROOT_RE.fullmatch(path.parts[0]) is not None: + session_journals.append(path) + if len(session_journals) != 1: + raise InventoryConflict("capture evidence must contain exactly one slots-1-through-6 batch session") + + batch_root = session_journals[0].parent + if any(PurePosixPath(relative).parts[:1] != batch_root.parts for relative in relative_paths): + raise InventoryConflict("accepted batch capture evidence contains files outside the completed six-frame batch") + first_frame = batch_root / "frame-001" + required = { + str(batch_root / "batch-job.json"), + str(batch_root / _FIRST_PLAN_NAME), + str(batch_root / _CONTINUATION_PLAN_NAME), + str(batch_root / _CAPTURE_MANIFEST_NAME), + str(batch_root / "session-journal.json"), + str(batch_root / "stdout.txt"), + str(batch_root / "stderr.txt"), + str(first_frame / "capture-preview.bin"), + str(first_frame / "capture-008e.bin"), + str(first_frame / "capture-frame-map.json"), + *(str(batch_root / f"frame-{slot:03d}" / "journal.json") for slot in _SIX_FRAME_SLOTS), + *(str(batch_root / f"frame-{slot:03d}" / "capture-meter.bin") for slot in _SIX_FRAME_SLOTS), + *(str(batch_root / f"frame-{slot:03d}" / "parent-ack.json") for slot in _SIX_FRAME_SLOTS), + } + missing = sorted(required - relative_paths) + if missing: + raise InventoryConflict("accepted batch capture evidence is incomplete: " + ", ".join(missing)) + + job_path = str(batch_root / "batch-job.json") + session_path = str(batch_root / "session-journal.json") + job = _snapshot_json(snapshot, job_path) + session = _snapshot_json(snapshot, session_path) + if set(job) != _BATCH_JOB_KEYS: + raise InventoryConflict("accepted batch job has an unexpected schema") + + session_id = job.get("session_id") + if not isinstance(session_id, str) or session_id != batch_root.name: + raise InventoryConflict("accepted batch job session does not name its batch root") + if ( + job.get("schema_version") != 3 + or job.get("session_contract") != "one-process-one-reservation" + or job.get("apply_all_boundary_offsets_before_first_frame") is not True + or job.get("parent_ack_required_after_every_frame") is not True + or job.get("release_once_after_last_frame") is not True + ): + raise InventoryConflict("accepted batch job does not preserve the one-shot contract") + + expected_usb_bus = _usb_component(job.get("expected_usb_bus"), address=False) + expected_usb_address = _usb_component(job.get("expected_usb_address"), address=True) + reviewed_fingerprint = job.get("reviewed_roll_fingerprint") + reviewed_binding = reviewed_fingerprint.get("binding_sha256") if isinstance(reviewed_fingerprint, dict) else None + if not _is_sha256(reviewed_binding): + raise InventoryConflict("accepted batch job has no reviewed fingerprint") + + plan_path = str(batch_root / _FIRST_PLAN_NAME) + continuation_path = str(batch_root / _CONTINUATION_PLAN_NAME) + manifest_path = str(batch_root / _CAPTURE_MANIFEST_NAME) + plan_row = _artifact_row(row_by_path, plan_path) + continuation_row = _artifact_row(row_by_path, continuation_path) + plan_sha256 = job.get("capture_plan_sha256") + continuation_sha256 = job.get("continuation_plan_sha256") + if ( + not _is_sha256(plan_sha256) + or plan_sha256 != plan_row["sha256"] + or not _is_sha256(continuation_sha256) + or continuation_sha256 != continuation_row["sha256"] + ): + raise InventoryConflict("accepted batch job is not bound to its retained plans") + capture_manifest = _snapshot_json(snapshot, manifest_path) + if capture_manifest.get("plan_sha256") != plan_sha256: + raise InventoryConflict("accepted batch capture manifest is not bound to its plan") + + frames = job.get("frames") + if not isinstance(frames, list) or len(frames) != len(_SIX_FRAME_SLOTS): + raise InventoryConflict("accepted batch job does not contain exactly six frames") + frame_jobs: dict[int, dict[str, object]] = {} + approval_sha256_by_slot: dict[str, str | None] = {} + for expected_slot, frame in zip(_SIX_FRAME_SLOTS, frames, strict=True): + if not isinstance(frame, dict) or set(frame) != _BATCH_FRAME_KEYS: + raise InventoryConflict("accepted batch job has an invalid frame entry") + expected_directory = f"frame-{expected_slot:03d}" + offset = frame.get("boundary_offset_rows") + minimum_offset = 0 if expected_slot == 1 else -144 + if ( + frame.get("slot") != expected_slot + or frame.get("ack") != f"{expected_directory}/parent-ack.json" + or frame.get("journal") != f"{expected_directory}/journal.json" + or frame.get("output") != f"{expected_directory}/capture.bin" + or isinstance(offset, bool) + or not isinstance(offset, int) + or not minimum_offset <= offset <= 144 + ): + raise InventoryConflict("accepted batch job frame paths or order are invalid") + approval_sha256_by_slot[str(expected_slot)] = _approval_binding(frame.get("manual_review_approval")) + frame_jobs[expected_slot] = frame + + # session-journal.json and the frame journals are engine-owned documents: + # their full key sets belong to the pinned Coolscan engine, so they are + # deliberately not closed-world key-checked here (the NegPy-owned + # batch-job/frame/ACK documents above are). What pins their schema is the + # exact engine identity below — the session must carry the worker and + # bundle hashes of the coolscanpy actually imported by this process — plus + # duplicate-key rejection in _snapshot_json and strict per-field checks on + # every load-bearing key. + from coolscanpy.protocol.ls5000_single_pass.bundle import ( + CAPTURE_BUNDLE_SHA256, + CAPTURE_WORKER_SHA256, + ) + + job_sha256 = _artifact_row(row_by_path, job_path)["sha256"] + engine_sha256 = session.get("capture_engine_sha256") + bundle_sha256 = session.get("capture_bundle_sha256") + density_calibration = session.get("nikon_density_calibration") + if ( + session.get("status") != "complete" + or session.get("session_id") != session_id + or session.get("density_calibration_session_id") != session_id + or not isinstance(density_calibration, dict) + or density_calibration.get("session_id") != session_id + or session.get("batch_job_sha256") != job_sha256 + or session.get("selected_slots") != list(_SIX_FRAME_SLOTS) + or session.get("completed_slots") != list(_SIX_FRAME_SLOTS) + or session.get("plan_sha256") != plan_sha256 + or session.get("continuation_plan_sha256") != continuation_sha256 + or engine_sha256 != CAPTURE_WORKER_SHA256 + or bundle_sha256 != CAPTURE_BUNDLE_SHA256 + or session.get("manual_review_approval_sha256_by_slot") != approval_sha256_by_slot + or session.get("reviewed_roll_fingerprint_sha256") != reviewed_binding + or session.get("expected_usb_bus") != expected_usb_bus + or session.get("expected_usb_address") != expected_usb_address + or session.get("actual_usb_bus") != expected_usb_bus + or session.get("actual_usb_address") != expected_usb_address + or session.get("reservation_acquired") is not True + or session.get("unit_release_attempts") != 1 + or session.get("unit_released") is not True + or session.get("recovery_required") != "none" + or session.get("active_frame_index") is not None + or session.get("active_slot") is not None + ): + raise InventoryConflict("capture evidence is not bound to one completed six-frame batch") + + journal_sha256_by_slot: dict[str, object] = {} + output_sha256_by_slot: dict[str, object] = {} + meter_sha256_by_slot: dict[str, object] = {} + ack_sha256_by_slot: dict[str, object] = {} + first_journal: dict[str, object] | None = None + for frame_index, slot in enumerate(_SIX_FRAME_SLOTS, start=1): + frame_root = batch_root / f"frame-{slot:03d}" + journal_path = str(frame_root / "journal.json") + meter_path = str(frame_root / "capture-meter.bin") + ack_path = str(frame_root / "parent-ack.json") + journal = _snapshot_json(snapshot, journal_path) + frame_job = frame_jobs[slot] + batch_session = journal.get("batch_session") + output_sha256 = journal.get("output_sha256") + selection = journal.get("live_frame_selection") + roll_identity = selection.get("roll_identity") if isinstance(selection, dict) else None + selected_comparison = roll_identity.get("selected_slot_comparison") if isinstance(roll_identity, dict) else None + comparison = roll_identity.get("comparison") if isinstance(roll_identity, dict) else None + expected_output = str(snapshot.root / Path(str(batch_root / frame_job["output"]))) + if ( + journal.get("status") != "frame-complete" + or journal.get("requested_frame") != slot + or journal.get("requested_boundary_offset_rows") != frame_job["boundary_offset_rows"] + or journal.get("frame_complete") is not True + or journal.get("session_reservation_retained") is not True + or journal.get("unit_released") is not False + or journal.get("recovery_required") not in (None, "none") + or journal.get("capture_mode") != "full" + or journal.get("expected_reads") != _FINE_READS + or journal.get("completed_reads") != _FINE_READS + or journal.get("expected_bytes") != _FINE_BYTES + or journal.get("completed_bytes") != _FINE_BYTES + or journal.get("disk_bytes") != _FINE_BYTES + or journal.get("output") != expected_output + or not _is_sha256(output_sha256) + or journal.get("plan_sha256") != plan_sha256 + or journal.get("continuation_plan_sha256") != continuation_sha256 + or journal.get("capture_engine_sha256") != engine_sha256 + or journal.get("capture_bundle_sha256") != bundle_sha256 + or journal.get("manual_review_approval") != frame_job["manual_review_approval"] + or journal.get("reviewed_roll_fingerprint_sha256") != reviewed_binding + or journal.get("expected_usb_bus") != expected_usb_bus + or journal.get("expected_usb_address") != expected_usb_address + or journal.get("actual_usb_bus") != expected_usb_bus + or journal.get("actual_usb_address") != expected_usb_address + or journal.get("density_calibration_session_id") != session_id + or journal.get("nikon_density_calibration") != density_calibration + or not isinstance(batch_session, dict) + or batch_session.get("session_id") != session_id + or batch_session.get("frame_index") != frame_index + or batch_session.get("frame_total") != len(_SIX_FRAME_SLOTS) + or batch_session.get("selected_slots") != list(_SIX_FRAME_SLOTS) + or not isinstance(selection, dict) + or selection.get("frame") != slot + or selection.get("requested_boundary_offset_rows") != frame_job["boundary_offset_rows"] + or selection.get("applied_boundary_offset_rows") != frame_job["boundary_offset_rows"] + or not isinstance(roll_identity, dict) + or roll_identity.get("reviewed_fingerprint_sha256") != reviewed_binding + or not _is_sha256(roll_identity.get("fresh_fingerprint_sha256")) + or not isinstance(comparison, dict) + or comparison.get("matches") is not True + or comparison.get("reason") != "matched" + or not isinstance(selected_comparison, dict) + or selected_comparison.get("matches") is not True + or selected_comparison.get("reason") != "matched" + or selected_comparison.get("slot") != slot + ): + raise InventoryConflict(f"capture evidence frame {slot} journal is not bound to the completed batch") + + meter_row = _artifact_row(row_by_path, meter_path) + meter_evidence = journal.get("meter_evidence") + if ( + meter_row["bytes"] != _METER_BYTES + or not isinstance(meter_evidence, dict) + or meter_evidence.get("path") != str(snapshot.root / Path(str(frame_root / "capture-meter.bin"))) + or meter_evidence.get("bytes") != _METER_BYTES + or meter_evidence.get("sha256") != meter_row["sha256"] + or meter_evidence.get("complete") is not True + or meter_evidence.get("durable_completed_passes") != 3 + or journal.get("meter_evidence_persisted_before_fine_arm") is not True + or journal.get("meter_group_bytes") != [_METER_PASS_BYTES] * 3 + or journal.get("meter_group_offsets") != [0, _METER_PASS_BYTES, _METER_PASS_BYTES * 2] + or journal.get("meter_completed_reads") != 15 + or journal.get("meter_completed_bytes") != _METER_BYTES + or journal.get("meter_layout") != _METER_LAYOUT + ): + raise InventoryConflict(f"capture evidence frame {slot} meter sidecar is not durably bound") + + ack = _snapshot_json(snapshot, ack_path) + nonce = journal.get("ack_nonce") + if ( + set(ack) != _ACK_KEYS + or not isinstance(nonce, str) + or _ACK_NONCE_RE.fullmatch(nonce) is None + or ack.get("ack_nonce") != nonce + or ack.get("action") != "continue" + or ack.get("frame_index") != frame_index + or ack.get("schema_version") != 1 + or ack.get("session_id") != session_id + or ack.get("slot") != slot + ): + raise InventoryConflict(f"capture evidence frame {slot} parent ACK is not bound to its journal") + + journal_sha256_by_slot[str(slot)] = _artifact_row(row_by_path, journal_path)["sha256"] + output_sha256_by_slot[str(slot)] = output_sha256 + meter_sha256_by_slot[str(slot)] = meter_row["sha256"] + ack_sha256_by_slot[str(slot)] = _artifact_row(row_by_path, ack_path)["sha256"] + if slot == 1: + first_journal = journal + + assert first_journal is not None + preview_path = str(first_frame / "capture-preview.bin") + table_path = str(first_frame / "capture-008e.bin") + mapping_path = str(first_frame / "capture-frame-map.json") + preview_row = _artifact_row(row_by_path, preview_path) + table_row = _artifact_row(row_by_path, table_path) + mapping = _snapshot_json(snapshot, mapping_path) + live_index_artifacts = first_journal.get("live_index_artifacts") + live_index_evidence = first_journal.get("live_index_evidence") + if ( + preview_row["bytes"] != _PREVIEW_BYTES + or not isinstance(live_index_artifacts, dict) + or live_index_artifacts + != { + "preview": str(snapshot.root / Path(preview_path)), + "table": str(snapshot.root / Path(table_path)), + "mapping": str(snapshot.root / Path(mapping_path)), + } + or not isinstance(live_index_evidence, dict) + or live_index_evidence.get("status") != "persisted-before-frame-detection" + or live_index_evidence.get("preview_bytes") != preview_row["bytes"] + or live_index_evidence.get("preview_sha256") != preview_row["sha256"] + or live_index_evidence.get("table_bytes") != table_row["bytes"] + or live_index_evidence.get("table_sha256") != table_row["sha256"] + or mapping != first_journal.get("live_frame_selection") + ): + raise InventoryConflict("capture evidence live preview, transport table, or frame map is not bound") + + return { + "batch_root": str(batch_root), + "session_id": session_id, + "batch_job_sha256": job_sha256, + "capture_plan_sha256": plan_sha256, + "continuation_plan_sha256": continuation_sha256, + "capture_engine_sha256": engine_sha256, + "capture_bundle_sha256": bundle_sha256, + "expected_usb_bus": expected_usb_bus, + "expected_usb_address": expected_usb_address, + "selected_slots": list(_SIX_FRAME_SLOTS), + "reviewed_fingerprint_sha256": reviewed_binding, + "first_frame_directory": str(first_frame), + "session_journal_sha256": _artifact_row(row_by_path, session_path)["sha256"], + "frame_journal_sha256_by_slot": journal_sha256_by_slot, + "frame_output_sha256_by_slot": output_sha256_by_slot, + "frame_meter_sha256_by_slot": meter_sha256_by_slot, + "frame_ack_sha256_by_slot": ack_sha256_by_slot, + "preview_sha256": preview_row["sha256"], + "transport_table_sha256": table_row["sha256"], + } + + +def bind_capture_evidence_inventory( + snapshot: CaptureEvidenceSnapshot, + inventory: InventorySnapshot, +) -> dict[str, object]: + """Bind file hashes to the lease's exact post-close directory inventory.""" + + actual = dict(inventory.files) + expected = dict(snapshot.identities) + actual.pop(OUTPUT_LOCK_NAME, None) + if actual != expected: + raise InventoryConflict("capture evidence changed between hashing and lease inventory") + return { + "path": str(snapshot.root), + "retained": True, + "directory_count": len(inventory.directories), + **snapshot.manifest, + } diff --git a/negpy/services/roll/service.py b/negpy/services/roll/service.py index cdb26304..2f02a451 100644 --- a/negpy/services/roll/service.py +++ b/negpy/services/roll/service.py @@ -769,11 +769,21 @@ def __init__( def list_devices(self) -> "list[coolscanpy.DeviceInfo]": return coolscanpy_roll.list_devices() - def open_roll(self, device_id: str | None = None, *, material: "coolscanpy.Material | None" = None) -> None: + def open_roll( + self, + device_id: str | None = None, + *, + material: "coolscanpy.Material | None" = None, + attempts_root: str | os.PathLike[str] | None = None, + ) -> None: """Open a device and its roll extension. Call `close()` when done.""" if self._roll is not None: raise RollScanningError("a roll is already open on this service; call close() first") - self._roll = coolscanpy_roll.open_roll(device_id, material=material) + self._roll = coolscanpy_roll.open_roll( + device_id, + material=material, + attempts_root=attempts_root, + ) def close(self) -> None: """Idempotent. Ends the roll reservation and releases the device.""" diff --git a/pyproject.toml b/pyproject.toml index 76ce515f..97e096ed 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -60,7 +60,7 @@ coolscan-roll = [ # Pin the reviewed color-negative density/DICE/direct-USB implementation # until its first tagged release. # No SANE dependency for this workflow; see docs/COOLSCANPY_ROLL_SCANNING.md. - "coolscanpy @ git+https://github.com/rohanpandula/coolscanpy.git@733151d25b8e962e3d5cb6ac1a6989315dc4e1ed", + "coolscanpy @ git+https://github.com/rohanpandula/coolscanpy.git@c9cc36d4890f1e013d0e51b5e67dffb6dc1b98ef", ] fauxice = [ "portable-digital-ice @ https://github.com/rohanpandula/digital-fauxice/releases/download/v0.3.0/portable_digital_ice-0.3.0-py3-none-any.whl", diff --git a/tests/roll/test_coolscanpy_roll.py b/tests/roll/test_coolscanpy_roll.py index 5515230f..56392345 100644 --- a/tests/roll/test_coolscanpy_roll.py +++ b/tests/roll/test_coolscanpy_roll.py @@ -8,6 +8,7 @@ from __future__ import annotations import sys +from pathlib import Path import numpy as np import pytest @@ -56,6 +57,35 @@ def test_explicit_material_forwarded(self, fake_coolscanpy) -> None: assert device.roll_called_with == fake_coolscanpy.module.Material.BLACK_AND_WHITE_NEGATIVE + def test_caller_owned_attempts_root_is_forwarded( + self, + fake_coolscanpy, + tmp_path: Path, + ) -> None: + roll = fake_coolscanpy.Roll() + + class EvidenceDevice(fake_coolscanpy.Device): + def __init__(self) -> None: + super().__init__(roll) + self.attempts_root = None + + def roll(self, *, material=None, attempts_root=None): + self.roll_called_with = material + self.attempts_root = attempts_root + return self._roll + + device = EvidenceDevice() + fake_coolscanpy.state["open_device"] = device + evidence = tmp_path / "scanner-attempts" + + handle = coolscanpy_roll.open_roll( + "ls5000-usb-001", + attempts_root=evidence, + ) + + assert device.attempts_root == evidence + handle.close() + def test_device_not_found_translated_to_runtime_error(self, fake_coolscanpy) -> None: fake_coolscanpy.state["open_error"] = fake_coolscanpy.module.DeviceNotFound("no Coolscan LS-5000 unit is attached") diff --git a/tests/roll/test_live_acceptance.py b/tests/roll/test_live_acceptance.py index 22c8983d..69657a75 100644 --- a/tests/roll/test_live_acceptance.py +++ b/tests/roll/test_live_acceptance.py @@ -18,7 +18,7 @@ LiveAcceptanceRequest, run_live_acceptance, ) -from negpy.services.roll.live_reservation import OUTPUT_LOCK_NAME +from negpy.services.roll.live_reservation import FixedOutputLease, OUTPUT_LOCK_NAME from negpy.services.roll.live_review import ( REVIEW_BASIS, SCHEMA as REVIEW_SCHEMA, @@ -41,6 +41,27 @@ "native_synthesis_mask_path", "hybrid_receipt_path", ) +_METER_BYTES = 3_264_000 +_METER_SHA256 = "2d417c2ed40641cd243f33989601b5a06c7a7c5b893c092c1868e0b9addd03e1" +_PREVIEW_BYTES = 6_250_496 +_PREVIEW_SHA256 = "690563a295100f3bb51b5cedbfc3e4a3df467d171d96483420810fd63e75a380" +_METER_LAYOUT = { + "passes": 3, + "rows_per_pass": 425, + "columns": 281, + "decoded_raster_channel_order": ["R", "G", "B", "IR"], + "wire_window_color_order": [9, 1, 2, 3], + "wire_color_to_controller_channel": { + "9": "IR", + "1": "R", + "2": "G", + "3": "B", + }, + "sample_byte_order": "big-endian-u16", + "row_core_bytes": 2_248, + "row_stride_bytes": 2_560, + "row_tail_bytes": 312, +} class _Runtime: @@ -151,14 +172,17 @@ def _fixture( output_dir = tmp_path / "outputs" output_dir.mkdir() + attempts_root = tmp_path / "scanner-attempts" + attempts_root.mkdir() request = LiveAcceptanceRequest( - device_id="ls5000-usb-001", + device_id="usb:2:7", preview_session_path=session_path, preview_session_sha256=hashlib.sha256(session_bytes).hexdigest(), reviewed_approval_path=review_path, reviewed_approval_sha256=review.sha256, output_dir=output_dir, run_receipt_path=tmp_path / "run-receipt.json", + attempts_root_path=attempts_root, confirm_live=confirm_live, ) return _AcceptanceFixture( @@ -168,6 +192,225 @@ def _fixture( ) +def _write_sparse_zeros(path: Path, size: int) -> None: + with path.open("xb") as stream: + stream.truncate(size) + + +def _write_completed_attempt_evidence( + attempts_root: Path, + *, + reviewed_fingerprint: str, + approvals: dict[int, _Approval], +) -> Path: + """Mirror Coolscan's durable batch tree after fine-stream cleanup.""" + + batch = attempts_root / "batch-slot01-slot06-test" + batch.mkdir() + session_id = batch.name + fresh_fingerprint = "e" * 64 + from coolscanpy.protocol.ls5000_single_pass.bundle import ( + CAPTURE_BUNDLE_SHA256, + CAPTURE_WORKER_SHA256, + ) + + engine_sha256 = CAPTURE_WORKER_SHA256 + bundle_sha256 = CAPTURE_BUNDLE_SHA256 + plan_payload = b"pinned first-frame plan\n" + continuation_payload = b'{"kind":"pinned-continuation"}\n' + plan_sha256 = hashlib.sha256(plan_payload).hexdigest() + continuation_sha256 = hashlib.sha256(continuation_payload).hexdigest() + (batch / "replay-first-rgbi4-plan.jsonl").write_bytes(plan_payload) + (batch / "replay-next-rgbi4-plan.json").write_bytes(continuation_payload) + (batch / "replay-first-rgbi4-manifest.json").write_text( + json.dumps({"plan_sha256": plan_sha256}), + encoding="utf-8", + ) + (batch / "stdout.txt").write_bytes(b"") + (batch / "stderr.txt").write_bytes(b"") + + approval_payloads = {slot: approval.to_payload() for slot, approval in approvals.items()} + frames = [ + { + "ack": f"frame-{slot:03d}/parent-ack.json", + "boundary_offset_rows": 0, + "journal": f"frame-{slot:03d}/journal.json", + "manual_review_approval": approval_payloads.get(slot), + "output": f"frame-{slot:03d}/capture.bin", + "slot": slot, + } + for slot in _SLOTS + ] + job = { + "apply_all_boundary_offsets_before_first_frame": True, + "capture_plan_sha256": plan_sha256, + "continuation_plan_sha256": continuation_sha256, + "expected_usb_address": 7, + "expected_usb_bus": 3, + "frames": frames, + "parent_ack_required_after_every_frame": True, + "release_once_after_last_frame": True, + "reviewed_roll_fingerprint": { + "binding_sha256": reviewed_fingerprint, + }, + "schema_version": 3, + "session_id": session_id, + "session_contract": "one-process-one-reservation", + } + job_bytes = json.dumps( + job, + sort_keys=True, + separators=(",", ":"), + ).encode("utf-8") + (batch / "batch-job.json").write_bytes(job_bytes) + job_sha256 = hashlib.sha256(job_bytes).hexdigest() + density_calibration = {"session_id": session_id, "fixture": "validated"} + (batch / "session-journal.json").write_text( + json.dumps( + { + "status": "complete", + "session_id": session_id, + "density_calibration_session_id": session_id, + "nikon_density_calibration": density_calibration, + "selected_slots": list(_SLOTS), + "completed_slots": list(_SLOTS), + "active_frame_index": None, + "active_slot": None, + "batch_job_sha256": job_sha256, + "capture_engine_sha256": engine_sha256, + "capture_bundle_sha256": bundle_sha256, + "plan_sha256": plan_sha256, + "continuation_plan_sha256": continuation_sha256, + "manual_review_approval_sha256_by_slot": { + str(slot): (None if slot not in approval_payloads else approval_payloads[slot]["binding_sha256"]) for slot in _SLOTS + }, + "reviewed_roll_fingerprint_sha256": reviewed_fingerprint, + "expected_usb_bus": 3, + "expected_usb_address": 7, + "actual_usb_bus": 3, + "actual_usb_address": 7, + "reservation_acquired": True, + "unit_release_attempts": 1, + "unit_released": True, + "recovery_required": "none", + }, + sort_keys=True, + ), + encoding="utf-8", + ) + + for frame_index, slot in enumerate(_SLOTS, start=1): + frame = batch / f"frame-{slot:03d}" + frame.mkdir() + meter = frame / "capture-meter.bin" + _write_sparse_zeros(meter, _METER_BYTES) + nonce = f"{slot:032x}" + selection = { + "frame": slot, + "requested_boundary_offset_rows": 0, + "applied_boundary_offset_rows": 0, + "roll_identity": { + "reviewed_fingerprint_sha256": reviewed_fingerprint, + "fresh_fingerprint_sha256": fresh_fingerprint, + "comparison": {"matches": True, "reason": "matched"}, + "selected_slot_comparison": { + "matches": True, + "reason": "matched", + "slot": slot, + }, + }, + } + journal: dict[str, object] = { + "status": "frame-complete", + "frame_complete": True, + "session_reservation_retained": True, + "unit_released": False, + "recovery_required": None, + "batch_session": { + "session_id": session_id, + "frame_index": frame_index, + "frame_total": len(_SLOTS), + "selected_slots": list(_SLOTS), + }, + "capture_mode": "full", + "requested_frame": slot, + "requested_boundary_offset_rows": 0, + "expected_reads": 2_980, + "completed_reads": 2_980, + "expected_bytes": 619_458_560, + "completed_bytes": 619_458_560, + "disk_bytes": 619_458_560, + "output": str(frame / "capture.bin"), + "output_sha256": hashlib.sha256(f"capture-{slot}".encode()).hexdigest(), + "plan_sha256": plan_sha256, + "continuation_plan_sha256": continuation_sha256, + "capture_engine_sha256": engine_sha256, + "capture_bundle_sha256": bundle_sha256, + "manual_review_approval": approval_payloads.get(slot), + "reviewed_roll_fingerprint_sha256": reviewed_fingerprint, + "expected_usb_bus": 3, + "expected_usb_address": 7, + "actual_usb_bus": 3, + "actual_usb_address": 7, + "ack_nonce": nonce, + "density_calibration_session_id": session_id, + "nikon_density_calibration": density_calibration, + "live_frame_selection": selection, + "meter_evidence": { + "path": str(meter), + "bytes": _METER_BYTES, + "sha256": _METER_SHA256, + "complete": True, + "durable_completed_passes": 3, + }, + "meter_evidence_persisted_before_fine_arm": True, + "meter_group_bytes": [1_088_000, 1_088_000, 1_088_000], + "meter_group_offsets": [0, 1_088_000, 2_176_000], + "meter_completed_reads": 15, + "meter_completed_bytes": _METER_BYTES, + "meter_layout": _METER_LAYOUT, + } + if slot == 1: + preview = frame / "capture-preview.bin" + _write_sparse_zeros(preview, _PREVIEW_BYTES) + table_payload = b"six-strip-transport-table" + table = frame / "capture-008e.bin" + table.write_bytes(table_payload) + mapping = frame / "capture-frame-map.json" + mapping.write_text(json.dumps(selection, sort_keys=True), encoding="utf-8") + journal["live_index_artifacts"] = { + "preview": str(preview), + "table": str(table), + "mapping": str(mapping), + } + journal["live_index_evidence"] = { + "status": "persisted-before-frame-detection", + "preview_bytes": _PREVIEW_BYTES, + "preview_sha256": _PREVIEW_SHA256, + "table_bytes": len(table_payload), + "table_sha256": hashlib.sha256(table_payload).hexdigest(), + } + (frame / "journal.json").write_text( + json.dumps(journal, sort_keys=True), + encoding="utf-8", + ) + (frame / "parent-ack.json").write_text( + json.dumps( + { + "ack_nonce": nonce, + "action": "continue", + "frame_index": frame_index, + "schema_version": 1, + "session_id": session_id, + "slot": slot, + }, + sort_keys=True, + ), + encoding="utf-8", + ) + return batch + + class _Service: def __init__( self, @@ -182,6 +425,7 @@ def __init__( self.output_dir = fixture.request.output_dir self.thumbnails = fixture.thumbnails self.approvals = fixture.review.approvals + self.reviewed_fingerprint = fixture.review.reviewed_fingerprint_sha256 self.restored_slots = restored_slots self.approval_slots = approval_slots self.wrong_approval_slot = wrong_approval_slot @@ -193,8 +437,13 @@ def __init__( self.safe_stop_calls = 0 self.eject_calls = 0 - def open_roll(self, device_id: str) -> None: - self.calls.append(("open_roll", device_id)) + def open_roll(self, device_id: str, *, attempts_root=None) -> None: + self.calls.append(("open_roll", device_id, attempts_root)) + _write_completed_attempt_evidence( + Path(attempts_root), + reviewed_fingerprint=self.reviewed_fingerprint, + approvals=self.approvals, + ) def restore_preview_session(self, payload: str, slots=None): self.calls.append(("restore_preview_session", payload, slots)) @@ -363,6 +612,47 @@ def write_frame( ) +class _MutatingEvidenceLease: + def __init__(self, delegate: FixedOutputLease, target: Path) -> None: + self._delegate = delegate + self._target = target + + @property + def released(self) -> bool: + return self._delegate.released + + def assert_inventory(self, *args, **kwargs): + return self._delegate.assert_inventory(*args, **kwargs) + + def release(self) -> None: + self._delegate.release() + + def release_verified(self, owned_files, *, previous, finalize): + def finalize_then_mutate() -> None: + finalize() + self._target.write_bytes(b"mutated-after-receipt-publication") + + return self._delegate.release_verified( + owned_files, + previous=previous, + finalize=finalize_then_mutate, + ) + + +def _mutating_evidence_lease_factory(target: Path): + def acquire(root, lock_document, *, require_empty): + return _MutatingEvidenceLease( + FixedOutputLease.acquire( + root, + lock_document, + require_empty=require_empty, + ), + target, + ) + + return acquire + + def _output_paths(output: RollFrameOutput) -> list[Path]: paths = [Path(getattr(output, field)) for field in _OUTPUT_FIELDS] assert all(path.is_absolute() and path.is_file() for path in paths) @@ -427,7 +717,7 @@ def validate_batch( "output_dir": str(output_dir), "slots": list(_SLOTS), "approved_slots": list(_APPROVED_SLOTS), - "device_id": "ls5000-usb-001", + "device_id": "usb:2:7", "device_model": "Nikon LS-5000 ED 1.03", "reviewed_fingerprint_sha256": approval_payloads[1]["reviewed_fingerprint_sha256"], "manual_approval_bindings": [ @@ -510,7 +800,11 @@ def test_runs_one_six_slot_hybrid_nikon_exact_batch_and_records_truthful_state( assert receipt["deep_acceptance"]["status"] == "passed" assert runtime.validated is True assert service.calls[:5] == [ - ("open_roll", "ls5000-usb-001"), + ( + "open_roll", + "usb:2:7", + fixture.request.attempts_root_path.resolve(), + ), ("restore_preview_session", '{"version":1}', _SLOTS), ("approve", 1), ("approve", 6), @@ -551,6 +845,30 @@ def test_runs_one_six_slot_hybrid_nikon_exact_batch_and_records_truthful_state( "transport_may_have_advanced_beyond_yielded": False, } assert receipt["output_lease"]["released"] is True + assert receipt["capture_evidence_lease"]["released"] is True + evidence = receipt["capture_evidence"] + assert evidence["retained"] is True + assert evidence["file_count"] == 28 + assert evidence["snapshot_error"] is None + assert {Path(row["path"]).name for row in evidence["files"]} == { + "batch-job.json", + "capture-preview.bin", + "capture-008e.bin", + "capture-frame-map.json", + "capture-meter.bin", + "journal.json", + "parent-ack.json", + "replay-first-rgbi4-manifest.json", + "replay-first-rgbi4-plan.jsonl", + "replay-next-rgbi4-plan.json", + "session-journal.json", + "stderr.txt", + "stdout.txt", + } + assert not any(Path(row["path"]).name == "capture.bin" for row in evidence["files"]) + assert evidence["batch_binding"]["selected_slots"] == list(_SLOTS) + assert evidence["batch_binding"]["first_frame_directory"].endswith("/frame-001") + assert not (fixture.request.attempts_root_path / OUTPUT_LOCK_NAME).exists() assert not (fixture.request.output_dir / OUTPUT_LOCK_NAME).exists() assert json.loads(fixture.request.run_receipt_path.read_text(encoding="utf-8")) == receipt assert any(event["event"] == "scan_progress" for event in events) @@ -709,6 +1027,142 @@ def swapping_emit(event: dict[str, Any]) -> None: _assert_no_recovery_or_eject(service, raised.value.receipt) +def test_capture_evidence_mutation_after_success_publication_is_truthfully_failed( + tmp_path: Path, +) -> None: + fixture = _fixture(tmp_path) + runtime = _Runtime() + service = _Service(fixture) + harness = _ValidationHarness(service) + target = fixture.request.attempts_root_path / "batch-slot01-slot06-test" / "frame-001" / "capture-preview.bin" + + with pytest.raises(LiveAcceptanceError, match="previously owned output changed") as raised: + _invoke( + fixture, + service, + runtime, + frame_inventory_collector=harness.collect, + batch_validator=harness.validate_batch, + evidence_lease_factory=_mutating_evidence_lease_factory(target), + ) + + receipt = raised.value.receipt + assert receipt["status"] == "failed" + assert receipt["capture_evidence"]["retained"] is False + assert receipt["capture_evidence"]["snapshot_error"] is not None + assert json.loads(fixture.request.run_receipt_path.read_text(encoding="utf-8")) == receipt + _assert_no_recovery_or_eject(service, receipt) + + +def test_failed_scan_evidence_mutation_is_truthfully_failed( + tmp_path: Path, +) -> None: + fixture = _fixture(tmp_path) + runtime = _Runtime() + service = _Service(fixture, scan_error=RuntimeError("scan failed")) + target = fixture.request.attempts_root_path / "batch-slot01-slot06-test" / "frame-001" / "capture-preview.bin" + + with pytest.raises(LiveAcceptanceError, match="scan failed") as raised: + _invoke( + fixture, + service, + runtime, + evidence_lease_factory=_mutating_evidence_lease_factory(target), + ) + + receipt = raised.value.receipt + assert receipt["status"] == "failed" + assert receipt["capture_evidence"]["retained"] is False + assert receipt["capture_evidence"]["snapshot_error"] is not None + assert json.loads(fixture.request.run_receipt_path.read_text(encoding="utf-8")) == receipt + _assert_no_recovery_or_eject(service, receipt) + + +def test_required_capture_basenames_split_across_directories_never_pass( + tmp_path: Path, +) -> None: + fixture = _fixture(tmp_path) + runtime = _Runtime() + service = _Service(fixture) + harness = _ValidationHarness(service) + original_open = service.open_roll + + def open_with_split_evidence(device_id: str, *, attempts_root=None) -> None: + original_open(device_id, attempts_root=attempts_root) + frame = Path(attempts_root) / "batch-slot01-slot06-test" / "frame-001" + for index, name in enumerate(("capture-preview.bin", "capture-008e.bin", "capture-frame-map.json")): + decoy = Path(attempts_root) / f"decoy-{index}" + decoy.mkdir() + (frame / name).rename(decoy / name) + + service.open_roll = open_with_split_evidence # type: ignore[method-assign] + + with pytest.raises(LiveAcceptanceError, match="accepted batch capture evidence"): + _invoke( + fixture, + service, + runtime, + frame_inventory_collector=harness.collect, + batch_validator=harness.validate_batch, + ) + + receipt = json.loads(fixture.request.run_receipt_path.read_text(encoding="utf-8")) + assert receipt["status"] == "failed" + _assert_no_recovery_or_eject(service, receipt) + + +def test_missing_sixth_frame_journal_never_passes_completed_batch_evidence( + tmp_path: Path, +) -> None: + fixture = _fixture(tmp_path) + runtime = _Runtime() + service = _Service(fixture) + harness = _ValidationHarness(service) + original_open = service.open_roll + + def open_without_sixth_journal(device_id: str, *, attempts_root=None) -> None: + original_open(device_id, attempts_root=attempts_root) + missing = Path(attempts_root) / "batch-slot01-slot06-test" / "frame-006" / "journal.json" + missing.unlink() + + service.open_roll = open_without_sixth_journal # type: ignore[method-assign] + + with pytest.raises(LiveAcceptanceError, match="accepted batch capture evidence"): + _invoke( + fixture, + service, + runtime, + frame_inventory_collector=harness.collect, + batch_validator=harness.validate_batch, + ) + + +def test_session_journal_must_bind_hashed_batch_job(tmp_path: Path) -> None: + fixture = _fixture(tmp_path) + runtime = _Runtime() + service = _Service(fixture) + harness = _ValidationHarness(service) + original_open = service.open_roll + + def open_with_wrong_job_hash(device_id: str, *, attempts_root=None) -> None: + original_open(device_id, attempts_root=attempts_root) + session_path = Path(attempts_root) / "batch-slot01-slot06-test" / "session-journal.json" + session = json.loads(session_path.read_text(encoding="utf-8")) + session["batch_job_sha256"] = "0" * 64 + session_path.write_text(json.dumps(session), encoding="utf-8") + + service.open_roll = open_with_wrong_job_hash # type: ignore[method-assign] + + with pytest.raises(LiveAcceptanceError, match="completed six-frame batch"): + _invoke( + fixture, + service, + runtime, + frame_inventory_collector=harness.collect, + batch_validator=harness.validate_batch, + ) + + def _never_constructed_factory(calls: list[str]): def factory(*, hybrid_runtime): calls.append("constructed") @@ -800,6 +1254,66 @@ def test_preexisting_output_collision_is_preserved_without_opening_roll( assert receipt["operation_state"]["roll_opened"] is False +def test_preexisting_capture_evidence_is_preserved_without_opening_roll( + tmp_path: Path, +) -> None: + fixture = _fixture(tmp_path) + existing = fixture.request.attempts_root_path / "existing.bin" + existing.write_bytes(b"do not overwrite") + factory_calls: list[str] = [] + + with pytest.raises(LiveAcceptanceError, match="unexpected files"): + run_live_acceptance( + fixture.request, + service_factory=_never_constructed_factory(factory_calls), + emit=lambda _event: None, + ) + + assert factory_calls == [] + assert existing.read_bytes() == b"do not overwrite" + assert sorted(path.name for path in fixture.request.attempts_root_path.iterdir()) == ["existing.bin"] + receipt = json.loads(fixture.request.run_receipt_path.read_text(encoding="utf-8")) + assert receipt["status"] == "failed" + assert receipt["operation_state"]["roll_opened"] is False + + +def test_capture_evidence_root_must_not_overlap_output(tmp_path: Path) -> None: + fixture = _fixture(tmp_path) + request = dataclasses.replace( + fixture.request, + attempts_root_path=fixture.request.output_dir, + ) + factory_calls: list[str] = [] + + with pytest.raises(LiveAcceptanceError, match="disjoint"): + run_live_acceptance( + request, + service_factory=_never_constructed_factory(factory_calls), + emit=lambda _event: None, + ) + + assert factory_calls == [] + assert not request.run_receipt_path.exists() + + +def test_capture_evidence_root_must_not_be_a_symlink(tmp_path: Path) -> None: + fixture = _fixture(tmp_path) + linked = tmp_path / "linked-attempts" + linked.symlink_to(fixture.request.attempts_root_path, target_is_directory=True) + request = dataclasses.replace(fixture.request, attempts_root_path=linked) + factory_calls: list[str] = [] + + with pytest.raises(LiveAcceptanceError, match="non-symlink"): + run_live_acceptance( + request, + service_factory=_never_constructed_factory(factory_calls), + emit=lambda _event: None, + ) + + assert factory_calls == [] + assert not request.run_receipt_path.exists() + + def test_preexisting_run_receipt_is_never_overwritten_or_live_opened( tmp_path: Path, ) -> None: @@ -933,6 +1447,9 @@ def test_scan_exception_closes_without_retry_safe_stop_or_eject( assert state["batch_exhausted"] is False assert state["transport_may_have_advanced_beyond_yielded"] is True assert service.closed is True + assert raised.value.receipt["capture_evidence"]["retained"] is True + assert raised.value.receipt["capture_evidence"]["file_count"] == 28 + assert raised.value.receipt["capture_evidence_lease"]["released"] is True _assert_no_recovery_or_eject(service, raised.value.receipt) @@ -999,3 +1516,117 @@ def reject_batch(_outputs, **_kwargs): assert state["transport_may_have_advanced_beyond_yielded"] is False assert service.closed is True _assert_no_recovery_or_eject(service, raised.value.receipt) + + +def test_sane_style_device_id_fails_before_constructing_service( + tmp_path: Path, +) -> None: + fixture = _fixture(tmp_path) + request = dataclasses.replace( + fixture.request, + device_id="coolscan3:usb:libusb:002:007", + ) + factory_calls: list[str] = [] + + with pytest.raises(LiveAcceptanceError, match="usb:BUS:ADDRESS") as raised: + run_live_acceptance( + request, + service_factory=_never_constructed_factory(factory_calls), + emit=lambda _event: None, + ) + + assert factory_calls == [] + assert raised.value.receipt["operation_state"]["roll_opened"] is False + + +def test_receipt_inside_attempts_root_fails_before_constructing_service( + tmp_path: Path, +) -> None: + fixture = _fixture(tmp_path) + request = dataclasses.replace( + fixture.request, + run_receipt_path=fixture.request.attempts_root_path / "receipt.json", + ) + factory_calls: list[str] = [] + + with pytest.raises(LiveAcceptanceError, match="outside the attempts root"): + run_live_acceptance( + request, + service_factory=_never_constructed_factory(factory_calls), + emit=lambda _event: None, + ) + + assert factory_calls == [] + + +def test_parser_requires_attempts_root() -> None: + from negpy.services.roll import live_acceptance as module + + argv = [ + "--device-id", "usb:2:7", + "--preview-session", "session.json", + "--preview-session-sha256", "0" * 64, + "--reviewed-approval", "review.json", + "--reviewed-approval-sha256", "0" * 64, + "--output-dir", "outputs", + "--run-receipt", "receipt.json", + "--confirm-live", + ] + with pytest.raises(SystemExit): + module._parser().parse_args(argv) + parsed = module._parser().parse_args([*argv, "--attempts-root", "attempts"]) + assert parsed.attempts_root == Path("attempts") + + +def test_interrupt_handlers_route_signals_to_truthful_failure( + monkeypatch: pytest.MonkeyPatch, +) -> None: + import signal as signal_module + + from negpy.services.roll import live_acceptance as module + + recorded: dict[int, object] = {} + monkeypatch.setattr( + module.signal, + "signal", + lambda number, handler: recorded.__setitem__(number, handler), + ) + + assert module.install_interrupt_handlers() is True + assert set(recorded) == {signal_module.SIGINT, signal_module.SIGTERM} + + handler = recorded[signal_module.SIGTERM] + assert callable(handler) + with pytest.raises(module.LiveAcceptanceInterrupted, match="signal"): + handler(signal_module.SIGTERM, None) + assert recorded[signal_module.SIGINT] is signal_module.SIG_IGN + assert recorded[signal_module.SIGTERM] is signal_module.SIG_IGN + + +def test_interrupt_mid_scan_closes_and_writes_truthful_failed_receipt( + tmp_path: Path, +) -> None: + from negpy.services.roll.live_acceptance import LiveAcceptanceInterrupted + + fixture = _fixture(tmp_path) + runtime = _Runtime() + service = _Service( + fixture, + scan_error=LiveAcceptanceInterrupted("received signal 15"), + ) + harness = _ValidationHarness(service) + + with pytest.raises(LiveAcceptanceError, match="received signal 15") as raised: + _invoke( + fixture, + service, + runtime, + frame_inventory_collector=harness.collect, + batch_validator=harness.validate_batch, + ) + + state = raised.value.receipt["operation_state"] + assert state["committed_slots"] == [] + assert service.closed is True + assert raised.value.receipt["status"] == "failed" + _assert_no_recovery_or_eject(service, raised.value.receipt) diff --git a/tests/roll/test_live_evidence.py b/tests/roll/test_live_evidence.py new file mode 100644 index 00000000..0b1e1db3 --- /dev/null +++ b/tests/roll/test_live_evidence.py @@ -0,0 +1,591 @@ +from __future__ import annotations + +import hashlib +import json +from pathlib import Path + +import pytest + +from negpy.services.roll import live_evidence +from negpy.services.roll.live_evidence import ( + bind_capture_evidence_inventory, + snapshot_capture_evidence, + validate_six_frame_batch_capture_evidence, +) +from negpy.services.roll.live_reservation import ( + FixedOutputLease, + InventoryConflict, +) + + +def _lease(root: Path) -> FixedOutputLease: + return FixedOutputLease.acquire( + root, + {"schema": "test.capture-evidence-owner.v1"}, + require_empty=True, + ) + + +_SLOTS = (1, 2, 3, 4, 5, 6) +_METER_BYTES = 3_264_000 +_METER_SHA256 = "2d417c2ed40641cd243f33989601b5a06c7a7c5b893c092c1868e0b9addd03e1" +_PREVIEW_BYTES = 6_250_496 +_PREVIEW_SHA256 = "690563a295100f3bb51b5cedbfc3e4a3df467d171d96483420810fd63e75a380" +_METER_LAYOUT = { + "passes": 3, + "rows_per_pass": 425, + "columns": 281, + "decoded_raster_channel_order": ["R", "G", "B", "IR"], + "wire_window_color_order": [9, 1, 2, 3], + "wire_color_to_controller_channel": { + "9": "IR", + "1": "R", + "2": "G", + "3": "B", + }, + "sample_byte_order": "big-endian-u16", + "row_core_bytes": 2_248, + "row_stride_bytes": 2_560, + "row_tail_bytes": 312, +} + + +def _write_sparse_zeros(path: Path, size: int) -> None: + with path.open("xb") as stream: + stream.truncate(size) + + +def _write_completed_batch(root: Path) -> dict[str, object]: + """Materialize Coolscan's durable tree after raw fine-stream cleanup.""" + + batch = root / "batch-slot01-slot06-fixture" + batch.mkdir() + session_id = batch.name + reviewed_sha256 = "a" * 64 + fresh_sha256 = "b" * 64 + from coolscanpy.protocol.ls5000_single_pass.bundle import ( + CAPTURE_BUNDLE_SHA256, + CAPTURE_WORKER_SHA256, + ) + + engine_sha256 = CAPTURE_WORKER_SHA256 + bundle_sha256 = CAPTURE_BUNDLE_SHA256 + plan_payload = b"pinned first-frame plan\n" + continuation_payload = b'{"kind":"pinned-continuation"}\n' + plan_sha256 = hashlib.sha256(plan_payload).hexdigest() + continuation_sha256 = hashlib.sha256(continuation_payload).hexdigest() + (batch / "replay-first-rgbi4-plan.jsonl").write_bytes(plan_payload) + (batch / "replay-next-rgbi4-plan.json").write_bytes(continuation_payload) + (batch / "replay-first-rgbi4-manifest.json").write_text( + json.dumps({"plan_sha256": plan_sha256}), + encoding="utf-8", + ) + (batch / "stdout.txt").write_bytes(b"") + (batch / "stderr.txt").write_bytes(b"") + + approvals = { + 1: {"binding_sha256": "1" * 64}, + 6: {"binding_sha256": "6" * 64}, + } + frames = [ + { + "ack": f"frame-{slot:03d}/parent-ack.json", + "boundary_offset_rows": 0, + "journal": f"frame-{slot:03d}/journal.json", + "manual_review_approval": approvals.get(slot), + "output": f"frame-{slot:03d}/capture.bin", + "slot": slot, + } + for slot in _SLOTS + ] + job = { + "apply_all_boundary_offsets_before_first_frame": True, + "capture_plan_sha256": plan_sha256, + "continuation_plan_sha256": continuation_sha256, + "expected_usb_address": 7, + "expected_usb_bus": 3, + "frames": frames, + "parent_ack_required_after_every_frame": True, + "release_once_after_last_frame": True, + "reviewed_roll_fingerprint": {"binding_sha256": reviewed_sha256}, + "schema_version": 3, + "session_id": session_id, + "session_contract": "one-process-one-reservation", + } + job_payload = json.dumps(job, sort_keys=True, separators=(",", ":")).encode() + (batch / "batch-job.json").write_bytes(job_payload) + job_sha256 = hashlib.sha256(job_payload).hexdigest() + + density_calibration = {"session_id": session_id, "fixture": "validated"} + (batch / "session-journal.json").write_text( + json.dumps( + { + "status": "complete", + "session_id": session_id, + "density_calibration_session_id": session_id, + "nikon_density_calibration": density_calibration, + "selected_slots": list(_SLOTS), + "completed_slots": list(_SLOTS), + "active_frame_index": None, + "active_slot": None, + "batch_job_sha256": job_sha256, + "capture_engine_sha256": engine_sha256, + "capture_bundle_sha256": bundle_sha256, + "plan_sha256": plan_sha256, + "continuation_plan_sha256": continuation_sha256, + "manual_review_approval_sha256_by_slot": { + str(slot): (None if slot not in approvals else approvals[slot]["binding_sha256"]) for slot in _SLOTS + }, + "reviewed_roll_fingerprint_sha256": reviewed_sha256, + "expected_usb_bus": 3, + "expected_usb_address": 7, + "actual_usb_bus": 3, + "actual_usb_address": 7, + "reservation_acquired": True, + "unit_release_attempts": 1, + "unit_released": True, + "recovery_required": "none", + }, + sort_keys=True, + ), + encoding="utf-8", + ) + + output_sha256_by_slot: dict[str, str] = {} + for frame_index, slot in enumerate(_SLOTS, start=1): + frame = batch / f"frame-{slot:03d}" + frame.mkdir() + meter = frame / "capture-meter.bin" + _write_sparse_zeros(meter, _METER_BYTES) + nonce = f"{slot:032x}" + selection = { + "frame": slot, + "requested_boundary_offset_rows": 0, + "applied_boundary_offset_rows": 0, + "roll_identity": { + "reviewed_fingerprint_sha256": reviewed_sha256, + "fresh_fingerprint_sha256": fresh_sha256, + "comparison": {"matches": True, "reason": "matched"}, + "selected_slot_comparison": { + "matches": True, + "reason": "matched", + "slot": slot, + }, + }, + } + output_sha256 = hashlib.sha256(f"capture-{slot}".encode()).hexdigest() + output_sha256_by_slot[str(slot)] = output_sha256 + journal: dict[str, object] = { + "status": "frame-complete", + "frame_complete": True, + "session_reservation_retained": True, + "unit_released": False, + "recovery_required": None, + "batch_session": { + "session_id": session_id, + "frame_index": frame_index, + "frame_total": len(_SLOTS), + "selected_slots": list(_SLOTS), + }, + "capture_mode": "full", + "requested_frame": slot, + "requested_boundary_offset_rows": 0, + "expected_reads": 2_980, + "completed_reads": 2_980, + "expected_bytes": 619_458_560, + "completed_bytes": 619_458_560, + "disk_bytes": 619_458_560, + "output": str(frame / "capture.bin"), + "output_sha256": output_sha256, + "plan_sha256": plan_sha256, + "continuation_plan_sha256": continuation_sha256, + "capture_engine_sha256": engine_sha256, + "capture_bundle_sha256": bundle_sha256, + "manual_review_approval": approvals.get(slot), + "reviewed_roll_fingerprint_sha256": reviewed_sha256, + "expected_usb_bus": 3, + "expected_usb_address": 7, + "actual_usb_bus": 3, + "actual_usb_address": 7, + "ack_nonce": nonce, + "density_calibration_session_id": session_id, + "nikon_density_calibration": density_calibration, + "live_frame_selection": selection, + "meter_evidence": { + "path": str(meter), + "bytes": _METER_BYTES, + "sha256": _METER_SHA256, + "complete": True, + "durable_completed_passes": 3, + }, + "meter_evidence_persisted_before_fine_arm": True, + "meter_group_bytes": [1_088_000, 1_088_000, 1_088_000], + "meter_group_offsets": [0, 1_088_000, 2_176_000], + "meter_completed_reads": 15, + "meter_completed_bytes": _METER_BYTES, + "meter_layout": _METER_LAYOUT, + } + if slot == 1: + preview = frame / "capture-preview.bin" + _write_sparse_zeros(preview, _PREVIEW_BYTES) + table_payload = b"six-strip-transport-table" + table = frame / "capture-008e.bin" + table.write_bytes(table_payload) + mapping = frame / "capture-frame-map.json" + mapping.write_text(json.dumps(selection, sort_keys=True), encoding="utf-8") + journal["live_index_artifacts"] = { + "preview": str(preview), + "table": str(table), + "mapping": str(mapping), + } + journal["live_index_evidence"] = { + "status": "persisted-before-frame-detection", + "preview_bytes": _PREVIEW_BYTES, + "preview_sha256": _PREVIEW_SHA256, + "table_bytes": len(table_payload), + "table_sha256": hashlib.sha256(table_payload).hexdigest(), + } + (frame / "journal.json").write_text( + json.dumps(journal, sort_keys=True), + encoding="utf-8", + ) + (frame / "parent-ack.json").write_text( + json.dumps( + { + "ack_nonce": nonce, + "action": "continue", + "frame_index": frame_index, + "schema_version": 1, + "session_id": session_id, + "slot": slot, + }, + sort_keys=True, + ), + encoding="utf-8", + ) + return { + "batch": batch, + "output_sha256_by_slot": output_sha256_by_slot, + "reviewed_sha256": reviewed_sha256, + } + + +def test_completed_post_finalization_batch_binds_all_six_capture_records( + tmp_path: Path, +) -> None: + root = tmp_path / "attempts" + root.mkdir() + expected = _write_completed_batch(root) + + snapshot = snapshot_capture_evidence(root) + binding = validate_six_frame_batch_capture_evidence(snapshot) + + assert snapshot.manifest["file_count"] == 28 + assert binding["frame_output_sha256_by_slot"] == expected["output_sha256_by_slot"] + assert binding["reviewed_fingerprint_sha256"] == expected["reviewed_sha256"] + + +def _rewrite_json(path: Path, mutate) -> None: + document = json.loads(path.read_text(encoding="utf-8")) + mutate(document) + path.write_text(json.dumps(document, sort_keys=True), encoding="utf-8") + + +def test_synthetic_journal_skeleton_without_meter_or_ack_artifacts_is_rejected( + tmp_path: Path, +) -> None: + root = tmp_path / "attempts" + root.mkdir() + completed = _write_completed_batch(root) + batch = completed["batch"] + assert isinstance(batch, Path) + for slot in _SLOTS: + frame = batch / f"frame-{slot:03d}" + (frame / "capture-meter.bin").unlink() + (frame / "parent-ack.json").unlink() + + snapshot = snapshot_capture_evidence(root) + + with pytest.raises(InventoryConflict, match="capture evidence is incomplete"): + validate_six_frame_batch_capture_evidence(snapshot) + + +def test_nonempty_second_attempt_tree_is_rejected(tmp_path: Path) -> None: + root = tmp_path / "attempts" + root.mkdir() + _write_completed_batch(root) + partial = root / "batch-slot01-slot06-aborted" / "frame-001" + partial.mkdir(parents=True) + (partial / "partial.bin").write_bytes(b"partial") + + snapshot = snapshot_capture_evidence(root) + + with pytest.raises(InventoryConflict, match="outside the completed six-frame batch"): + validate_six_frame_batch_capture_evidence(snapshot) + + +def test_additional_diagnostic_inside_completed_batch_is_allowed( + tmp_path: Path, +) -> None: + root = tmp_path / "attempts" + root.mkdir() + completed = _write_completed_batch(root) + batch = completed["batch"] + assert isinstance(batch, Path) + diagnostic = batch / "diagnostics" / "capture-timing.txt" + diagnostic.parent.mkdir() + diagnostic.write_text("retained diagnostic\n", encoding="utf-8") + + snapshot = snapshot_capture_evidence(root) + binding = validate_six_frame_batch_capture_evidence(snapshot) + + assert binding["session_id"] == batch.name + assert any(row["path"].endswith("capture-timing.txt") for row in snapshot.manifest["files"]) + + +@pytest.mark.parametrize( + ("target", "mutation", "message"), + [ + ( + "frame-003/journal.json", + lambda document: document.__setitem__("completed_bytes", 1), + "frame 3 journal", + ), + ( + "frame-004/parent-ack.json", + lambda document: document.__setitem__("action", "stop"), + "frame 4 parent ACK", + ), + ( + "session-journal.json", + lambda document: document.__setitem__("actual_usb_address", 8), + "completed six-frame batch", + ), + ( + "session-journal.json", + lambda document: document.pop("batch_job_sha256"), + "completed six-frame batch", + ), + ( + "session-journal.json", + lambda document: document.__setitem__("batch_job_sha256", "0" * 64), + "completed six-frame batch", + ), + ( + "frame-001/capture-frame-map.json", + lambda document: document.__setitem__("frame", 2), + "live preview, transport table, or frame map", + ), + ], +) +def test_mutated_capture_contract_is_rejected( + tmp_path: Path, + target: str, + mutation, + message: str, +) -> None: + root = tmp_path / "attempts" + root.mkdir() + completed = _write_completed_batch(root) + batch = completed["batch"] + assert isinstance(batch, Path) + _rewrite_json(batch / target, mutation) + + snapshot = snapshot_capture_evidence(root) + + with pytest.raises(InventoryConflict, match=message): + validate_six_frame_batch_capture_evidence(snapshot) + + +def test_meter_payload_must_match_its_frame_journal_digest(tmp_path: Path) -> None: + root = tmp_path / "attempts" + root.mkdir() + completed = _write_completed_batch(root) + batch = completed["batch"] + assert isinstance(batch, Path) + meter = batch / "frame-005" / "capture-meter.bin" + with meter.open("r+b") as stream: + stream.write(b"changed") + + snapshot = snapshot_capture_evidence(root) + + with pytest.raises(InventoryConflict, match="frame 5 meter sidecar"): + validate_six_frame_batch_capture_evidence(snapshot) + + +def test_parent_ack_nonce_must_use_the_worker_safe_alphabet(tmp_path: Path) -> None: + root = tmp_path / "attempts" + root.mkdir() + completed = _write_completed_batch(root) + batch = completed["batch"] + assert isinstance(batch, Path) + _rewrite_json( + batch / "frame-002" / "journal.json", + lambda document: document.__setitem__("ack_nonce", "../unsafe"), + ) + _rewrite_json( + batch / "frame-002" / "parent-ack.json", + lambda document: document.__setitem__("ack_nonce", "../unsafe"), + ) + + snapshot = snapshot_capture_evidence(root) + + with pytest.raises(InventoryConflict, match="frame 2 parent ACK"): + validate_six_frame_batch_capture_evidence(snapshot) + + +@pytest.mark.parametrize("name", ["capture-preview.bin", "capture-008e.bin"]) +def test_live_index_payload_must_match_first_frame_journal( + tmp_path: Path, + name: str, +) -> None: + root = tmp_path / "attempts" + root.mkdir() + completed = _write_completed_batch(root) + batch = completed["batch"] + assert isinstance(batch, Path) + artifact = batch / "frame-001" / name + with artifact.open("r+b") as stream: + stream.write(b"changed") + + snapshot = snapshot_capture_evidence(root) + + with pytest.raises( + InventoryConflict, + match="live preview, transport table, or frame map", + ): + validate_six_frame_batch_capture_evidence(snapshot) + + +def test_retained_plan_bytes_must_match_job_and_journals(tmp_path: Path) -> None: + root = tmp_path / "attempts" + root.mkdir() + completed = _write_completed_batch(root) + batch = completed["batch"] + assert isinstance(batch, Path) + (batch / "replay-first-rgbi4-plan.jsonl").write_bytes(b"changed plan\n") + + snapshot = snapshot_capture_evidence(root) + + with pytest.raises(InventoryConflict, match="retained plans"): + validate_six_frame_batch_capture_evidence(snapshot) + + +def test_snapshot_hashes_and_binds_retained_attempt_tree(tmp_path: Path) -> None: + root = tmp_path / "attempts" + root.mkdir() + lease = _lease(root) + try: + frame = root / "batch-a" / "frame-001" + frame.mkdir(parents=True) + preview = frame / "capture-preview.bin" + table = frame / "capture-008e.bin" + preview.write_bytes(b"preview") + table.write_bytes(b"table") + + snapshot = snapshot_capture_evidence(root) + inventory = lease.assert_inventory(snapshot.files) + receipt = bind_capture_evidence_inventory(snapshot, inventory) + + assert receipt["file_count"] == 2 + assert receipt["directory_count"] == 2 + assert receipt["total_bytes"] == 12 + assert receipt["files"] == [ + { + "path": "batch-a/frame-001/capture-008e.bin", + "bytes": 5, + "sha256": hashlib.sha256(b"table").hexdigest(), + }, + { + "path": "batch-a/frame-001/capture-preview.bin", + "bytes": 7, + "sha256": hashlib.sha256(b"preview").hexdigest(), + }, + ] + finally: + lease.release() + + +def test_snapshot_rejects_symbolic_link(tmp_path: Path) -> None: + root = tmp_path / "attempts" + root.mkdir() + target = tmp_path / "outside.bin" + target.write_bytes(b"outside") + (root / "linked.bin").symlink_to(target) + + with pytest.raises(InventoryConflict, match="regular file"): + snapshot_capture_evidence(root) + + +def test_inventory_binding_rejects_post_hash_mutation(tmp_path: Path) -> None: + root = tmp_path / "attempts" + root.mkdir() + lease = _lease(root) + try: + artifact = root / "journal.json" + artifact.write_bytes(b"one") + snapshot = snapshot_capture_evidence(root) + artifact.write_bytes(b"two") + inventory = lease.assert_inventory(snapshot.files) + + with pytest.raises(InventoryConflict, match="changed between hashing"): + bind_capture_evidence_inventory(snapshot, inventory) + finally: + lease.release() + + +def test_snapshot_refuses_manifest_that_cannot_fit_in_run_receipt( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + root = tmp_path / "attempts" + root.mkdir() + (root / "capture-preview.bin").write_bytes(b"preview") + monkeypatch.setattr(live_evidence, "_MAX_MANIFEST_BYTES", 1) + + with pytest.raises(InventoryConflict, match="receipt-safe size limit"): + snapshot_capture_evidence(root) + + +def test_duplicate_json_keys_in_capture_evidence_are_rejected(tmp_path: Path) -> None: + root = tmp_path / "attempts" + root.mkdir() + completed = _write_completed_batch(root) + batch = completed["batch"] + assert isinstance(batch, Path) + session_path = batch / "session-journal.json" + document = json.loads(session_path.read_text(encoding="utf-8")) + body = json.dumps(document, sort_keys=True) + assert body.startswith("{") + duplicated = ( + '{"batch_job_sha256": "' + "0" * 64 + '", ' + body[1:] + ) + assert json.loads(duplicated)["batch_job_sha256"] == document["batch_job_sha256"] + session_path.write_text(duplicated, encoding="utf-8") + + snapshot = snapshot_capture_evidence(root) + + with pytest.raises(InventoryConflict, match="repeats key"): + validate_six_frame_batch_capture_evidence(snapshot) + + +def test_session_engine_identity_must_match_the_pinned_runtime(tmp_path: Path) -> None: + from coolscanpy.protocol.ls5000_single_pass.bundle import CAPTURE_BUNDLE_SHA256 + + root = tmp_path / "attempts" + root.mkdir() + completed = _write_completed_batch(root) + batch = completed["batch"] + assert isinstance(batch, Path) + foreign = "f" * 64 + assert foreign != CAPTURE_BUNDLE_SHA256 + for name in ("session-journal.json", *(f"frame-{slot:03d}/journal.json" for slot in _SLOTS)): + _rewrite_json( + batch / name, + lambda document: document.__setitem__("capture_bundle_sha256", foreign), + ) + + snapshot = snapshot_capture_evidence(root) + + with pytest.raises(InventoryConflict, match="completed six-frame batch"): + validate_six_frame_batch_capture_evidence(snapshot) diff --git a/tests/roll/test_service.py b/tests/roll/test_service.py index d8606001..8bcb6eca 100644 --- a/tests/roll/test_service.py +++ b/tests/roll/test_service.py @@ -50,6 +50,25 @@ def test_open_then_close(self, fake_coolscanpy) -> None: assert roll.closed is True assert device.closed is True + def test_open_forwards_caller_owned_attempts_root( + self, + monkeypatch: pytest.MonkeyPatch, + tmp_path: Path, + ) -> None: + sentinel = object() + calls: list[tuple[object, object, object]] = [] + + def open_roll(device_id, *, material=None, attempts_root=None): + calls.append((device_id, material, attempts_root)) + return sentinel + + monkeypatch.setattr(roll_service.coolscanpy_roll, "open_roll", open_roll) + service = RollScanningService() + evidence = tmp_path / "scanner-attempts" + service.open_roll("ls5000-usb-001", attempts_root=evidence) + + assert calls == [("ls5000-usb-001", None, evidence)] + def test_failed_close_retains_the_open_handle_for_a_safe_retry(self, fake_coolscanpy) -> None: ownership_error = RuntimeError("USB ownership is retained") diff --git a/tests/test_ls5000_frozen_packaging.py b/tests/test_ls5000_frozen_packaging.py index 4e29a65f..a69c41cd 100644 --- a/tests/test_ls5000_frozen_packaging.py +++ b/tests/test_ls5000_frozen_packaging.py @@ -620,6 +620,36 @@ def import_without_exact_builder(module_name: str): build.preflight_coolscan_runtime() +def test_build_preflight_rejects_coolscan_without_durable_attempts_api( + monkeypatch: pytest.MonkeyPatch, +) -> None: + import build + + real_import = build.importlib.import_module + + class StaleDevice: + def roll(self, *, material=None): + del material + + def import_with_stale_device(module_name: str): + module = real_import(module_name) + if module_name != "coolscanpy": + return module + return SimpleNamespace( + Device=StaleDevice, + DigitalIceAcquisition=module.DigitalIceAcquisition, + DigitalIceAcquisitionEvidence=module.DigitalIceAcquisitionEvidence, + build_digital_ice_acquisition_evidence=( + module.build_digital_ice_acquisition_evidence + ), + ) + + monkeypatch.setattr(build.importlib, "import_module", import_with_stale_device) + + with pytest.raises(RuntimeError, match="attempts_root"): + build.preflight_coolscan_runtime() + + def test_build_preflight_accepts_the_current_sealed_capture_bundle() -> None: import build from coolscanpy.protocol.ls5000_single_pass.bundle import CAPTURE_BUNDLE_SHA256 diff --git a/uv.lock b/uv.lock index 85f5f46e..4004ac9b 100644 --- a/uv.lock +++ b/uv.lock @@ -77,7 +77,7 @@ wheels = [ [[package]] name = "coolscanpy" version = "0.1.3" -source = { git = "https://github.com/rohanpandula/coolscanpy.git?rev=733151d25b8e962e3d5cb6ac1a6989315dc4e1ed#733151d25b8e962e3d5cb6ac1a6989315dc4e1ed" } +source = { git = "https://github.com/rohanpandula/coolscanpy.git?rev=c9cc36d4890f1e013d0e51b5e67dffb6dc1b98ef#c9cc36d4890f1e013d0e51b5e67dffb6dc1b98ef" } dependencies = [ { name = "imagecodecs" }, { name = "jinja2" }, @@ -432,7 +432,7 @@ requires-dist = [ [package.metadata.requires-dev] camera = [{ name = "gphoto2", marker = "sys_platform != 'win32'", specifier = ">=2.5" }] -coolscan-roll = [{ name = "coolscanpy", git = "https://github.com/rohanpandula/coolscanpy.git?rev=733151d25b8e962e3d5cb6ac1a6989315dc4e1ed" }] +coolscan-roll = [{ name = "coolscanpy", git = "https://github.com/rohanpandula/coolscanpy.git?rev=c9cc36d4890f1e013d0e51b5e67dffb6dc1b98ef" }] dev = [ { name = "pyinstaller", specifier = ">=6.18.0" }, { name = "pytest", specifier = "==9.0.2" }, From 46e63430adf16bce8c2b29b9e48a84fc275dd9d0 Mon Sep 17 00:00:00 2001 From: Rohan Pandula Date: Wed, 22 Jul 2026 19:51:17 -0700 Subject: [PATCH 24/48] fix(ls5000): derive review slots from fresh preview --- negpy/services/roll/deep_acceptance.py | 21 +++++++------ negpy/services/roll/live_acceptance.py | 18 ++++++++--- negpy/services/roll/live_review.py | 42 +++++++++++++++++++++++--- tests/roll/test_live_review.py | 23 ++++++++++++++ 4 files changed, 85 insertions(+), 19 deletions(-) diff --git a/negpy/services/roll/deep_acceptance.py b/negpy/services/roll/deep_acceptance.py index 39a69642..606ec71f 100644 --- a/negpy/services/roll/deep_acceptance.py +++ b/negpy/services/roll/deep_acceptance.py @@ -55,6 +55,8 @@ def flock(self, descriptor: int, operation: int) -> None: ... SCHEMA = "negpy.ls5000-deep-acceptance.v1" SLOTS = (1, 2, 3, 4, 5, 6) +# Historical fixture/export compatibility only. The production audit derives +# the actual approved slots from the sealed frame evidence. APPROVED_SLOTS = frozenset((1, 6)) _OUTPUT_FIELDS = ( "rgb_path", @@ -1548,11 +1550,11 @@ def validate_completed_frame( def _validate_manual_approval( frame: _FrameAudit, *, - expected: bool, + expected: bool | None, ) -> dict[str, Any] | None: slot = frame.summary["slot"] approval_value = frame.receipt.get("manual_approval") - if (approval_value is not None) is not expected: + if expected is not None and (approval_value is not None) is not expected: _fail(f"slot {slot}", "manual approval presence is wrong") if approval_value is None: return None @@ -1706,6 +1708,7 @@ def _run_receipt_binding( operation_state = document.get("operation_state") deep_result = document.get("deep_acceptance") expected_device_id = frames[0].receipt.get("device_id") if frames else None + approved_slots = sorted(approvals) if ( document.get("schema") != "negpy.ls5000-live-acceptance.v2" or document.get("status") != "succeeded" @@ -1713,7 +1716,7 @@ def _run_receipt_binding( or document.get("device_id") != expected_device_id or document.get("output_dir") != str(root) or document.get("slots") != list(SLOTS) - or document.get("approved_slots") != sorted(APPROVED_SLOTS) + or document.get("approved_slots") != approved_slots or settings != { "write_unrepaired": True, @@ -1798,7 +1801,7 @@ def _run_receipt_binding( ) if _canonical_json(reviewed_document, newline=True, ensure_ascii=True) != reviewed_bytes: _fail("live run receipt", "reviewed approval is not canonical JSON") - expected_approval_rows = [approvals[slot] for slot in sorted(APPROVED_SLOTS)] + expected_approval_rows = [approvals[slot] for slot in approved_slots] reviewed_contact = _require_dict( reviewed_document.get("contact_sheet"), label="reviewed contact sheet", @@ -1868,10 +1871,7 @@ def _validate_six_frame_batch_locked( or getattr(ownership, "selected_slot") != index ): _fail(f"slot {index}", "batch coordinates are wrong") - approval_row = _validate_manual_approval( - frame, - expected=index in APPROVED_SLOTS, - ) + approval_row = _validate_manual_approval(frame, expected=None) if approval_row is not None: approval_rows[index] = approval_row candidate = ( @@ -1914,13 +1914,14 @@ def _validate_six_frame_batch_locked( approvals=approval_rows, ) assert common is not None + approved_slots = sorted(approval_rows) result: dict[str, Any] = { "schema": SCHEMA, "status": "passed", "scope": "six-frame-batch", "output_dir": str(root), "slots": list(SLOTS), - "approved_slots": sorted(APPROVED_SLOTS), + "approved_slots": approved_slots, "reservation_id": common[0], "preview_sha256": common[2], "preview_identity_sha256": common[3], @@ -1943,7 +1944,7 @@ def _validate_six_frame_batch_locked( "slot": slot, "binding_sha256": approval_rows[slot]["binding_sha256"], } - for slot in sorted(APPROVED_SLOTS) + for slot in approved_slots ], ) return result diff --git a/negpy/services/roll/live_acceptance.py b/negpy/services/roll/live_acceptance.py index d3377383..39861f58 100644 --- a/negpy/services/roll/live_acceptance.py +++ b/negpy/services/roll/live_acceptance.py @@ -52,6 +52,8 @@ SCHEMA = "negpy.ls5000-live-acceptance.v2" SLOTS = (1, 2, 3, 4, 5, 6) +# Kept for backwards-compatible imports. The active approval set is derived +# from the restored preview and the SHA-pinned review document below. APPROVED_SLOTS = (1, 6) EXPECTED_DEVICE_MODEL = "Nikon LS-5000 ED 1.03" FILENAME_PATTERN = 'acceptance_slot{{ "%02d" % seq }}' @@ -416,6 +418,7 @@ def run_live_acceptance( session: _SessionPayload | None = None review: ValidatedReviewedApproval | None = None + reviewed_approval_slots: tuple[int, ...] = () runtime: Any = None deep_batch: dict[str, Any] | None = None output_dir: Path | None = None @@ -655,6 +658,7 @@ def on_scan_progress(progress: object) -> None: request.preview_session_sha256, ) review = review_loader(request, session) + reviewed_approval_slots = tuple(sorted(review.approvals)) runtime = hybrid_runtime_loader(request.hybrid_runtime_manifest_path) if runtime is None: raise ValueError("the pinned Hybrid runtime is not installed") @@ -697,12 +701,16 @@ def on_scan_progress(progress: object) -> None: raise ValueError(f"restored slots are {tuple(restored_slots)}; expected exactly {SLOTS}") validate_restored_thumbnails(thumbnails, review) required_approval_slots.extend(slot for slot in SLOTS if service.needs_approval(slot)) - if tuple(required_approval_slots) != APPROVED_SLOTS: - raise ValueError(f"restored session approval set is {tuple(required_approval_slots)}; expected {APPROVED_SLOTS}") + if tuple(required_approval_slots) != reviewed_approval_slots: + raise ValueError( + "restored session approval set does not match the reviewed " + f"evidence: session={tuple(required_approval_slots)}, " + f"review={reviewed_approval_slots}" + ) safe_emit({"event": "preview_session_restored", "slots": list(restored_slots)}) phase = "applying_reviewed_approvals" - for slot in APPROVED_SLOTS: + for slot in reviewed_approval_slots: returned = service.approve(slot) expected_payload = approval_payload(review.approvals[slot]) if approval_payload(returned) != expected_payload: @@ -908,7 +916,7 @@ def on_repair_progress( if review is None: raise RuntimeError("reviewed approval disappeared before deep acceptance") expected_manual_approval_bindings: list[dict[str, Any]] = [] - for slot in APPROVED_SLOTS: + for slot in reviewed_approval_slots: binding = approval_payload(review.approvals[slot]).get("binding_sha256") if type(binding) is not str or _SHA256_RE.fullmatch(binding) is None: raise ValueError(f"reviewed approval binding for slot {slot} is invalid") @@ -922,7 +930,7 @@ def on_repair_progress( type(deep_batch) is not dict or deep_batch.get("status") != "passed" or deep_batch.get("slots") != list(SLOTS) - or deep_batch.get("approved_slots") != list(APPROVED_SLOTS) + or deep_batch.get("approved_slots") != list(reviewed_approval_slots) or deep_batch.get("device_id") != request.device_id or deep_batch.get("device_model") != EXPECTED_DEVICE_MODEL or deep_batch.get("reviewed_fingerprint_sha256") != review.reviewed_fingerprint_sha256 diff --git a/negpy/services/roll/live_review.py b/negpy/services/roll/live_review.py index 8b59d674..ca796ef0 100644 --- a/negpy/services/roll/live_review.py +++ b/negpy/services/roll/live_review.py @@ -20,6 +20,11 @@ SCHEMA = "negpy.ls5000-reviewed-approval.v1" REVIEW_BASIS = "visual-inspection-of-six-frame-contact-sheet-and-canonical-restored-thumbnails" +# Historical six-strip reviews used slots 1 and 6. A refeed can legitimately +# change which boundary has enough direct transport evidence, so production +# review must derive this list from the restored preview instead of treating +# the historical pair as a physical invariant. Keep the pair as a fallback +# for lightweight older test/session doubles that do not expose ``slots``. APPROVED_SLOTS = (1, 6) _SHA256_CHARS = frozenset("0123456789abcdef") _MAX_REVIEW_BYTES = 64 * 1024 @@ -163,6 +168,34 @@ def _default_approval_parser(payload: object) -> object: return ManualFrameApproval.from_payload(payload) +def _required_approval_slots(session: object) -> tuple[int, ...]: + """Return the exact slots that this restored preview marks manual. + + The preview session is the authority for this list. Falling back only + preserves compatibility with deliberately minimal legacy test doubles; + real ``RollPreviewSession`` instances always expose immutable slots. + """ + + slots = getattr(session, "slots", None) + if not isinstance(slots, tuple): + return APPROVED_SLOTS + selected = getattr(session, "selected_slots", None) + if isinstance(selected, tuple) and selected and all(type(slot) is int for slot in selected): + selected_slots = frozenset(selected) + else: + selected_slots = None + required: list[int] = [] + for item in slots: + slot_id = getattr(item, "slot_id", None) + origin = getattr(item, "base_origin", None) + manual = getattr(origin, "manual_review", None) + if type(slot_id) is not int or type(manual) is not bool: + return APPROVED_SLOTS + if manual and (selected_slots is None or slot_id in selected_slots): + required.append(slot_id) + return tuple(required) + + def load_reviewed_approval( review_path: Path, expected_sha256: str, @@ -173,7 +206,7 @@ def load_reviewed_approval( session_loader: Callable[[str], object] = _default_session_loader, approval_parser: Callable[[object], object] = _default_approval_parser, ) -> ValidatedReviewedApproval: - """Load, pin, and rederive the exact approvals for selected edge slots.""" + """Load, pin, and rederive the exact approvals the preview requires.""" if not _is_sha256(expected_sha256): raise ValueError("reviewed-approval pin must be a lowercase SHA-256 digest") @@ -246,11 +279,12 @@ def load_reviewed_approval( if not _is_sha256(fingerprint) or document.get("reviewed_fingerprint_sha256") != fingerprint: raise ValueError("reviewed approval fingerprint does not match the preview") + expected_slots = _required_approval_slots(session) rows = document.get("approvals") - if type(rows) is not list or len(rows) != len(APPROVED_SLOTS): - raise ValueError("reviewed approval must contain exactly slots 1 and 6") + if type(rows) is not list or len(rows) != len(expected_slots): + raise ValueError("reviewed approval does not match the preview manual-review slots") approvals: dict[int, object] = {} - for expected_slot, row in zip(APPROVED_SLOTS, rows, strict=True): + for expected_slot, row in zip(expected_slots, rows, strict=True): parsed = approval_parser(row) parsed_payload = approval_payload(parsed) if parsed_payload != row or parsed_payload.get("slot") != expected_slot: diff --git a/tests/roll/test_live_review.py b/tests/roll/test_live_review.py index 479a75ad..2b338a87 100644 --- a/tests/roll/test_live_review.py +++ b/tests/roll/test_live_review.py @@ -134,6 +134,29 @@ def test_loads_pinned_review_and_rederives_both_approvals(tmp_path: Path) -> Non validate_restored_thumbnails(thumbnails, reviewed) +def test_derives_required_approvals_from_the_restored_preview(tmp_path: Path) -> None: + values = list(_fixture(tmp_path)) + approvals = {1: values[7]["approvals"][0]} + document = dict(values[7]) + document["approvals"] = [approvals[1]] + values[0].write_text(json.dumps(document), encoding="utf-8") + values[1] = hashlib.sha256(values[0].read_bytes()).hexdigest() + + session = _Session("c" * 64, approvals) + session.slots = tuple( + SimpleNamespace( + slot_id=slot, + base_origin=SimpleNamespace(manual_review=(slot in (1, 7))), + ) + for slot in range(1, 8) + ) + session.selected_slots = (1, 2, 3, 4, 5, 6) + values[5] = session + + reviewed = _load(tuple(values)) + assert tuple(reviewed.approvals) == (1,) + + def test_wrong_review_hash_fails_before_parsing(tmp_path: Path) -> None: values = list(_fixture(tmp_path)) values[1] = "0" * 64 From bcf254c9958b693e4b7113d64d40608fb68d8d8d Mon Sep 17 00:00:00 2001 From: Rohan Pandula Date: Wed, 22 Jul 2026 20:36:57 -0700 Subject: [PATCH 25/48] fix(roll): pin fixed short-strip frame table --- docs/COOLSCANPY_ROLL_SCANNING.md | 5 +++-- pyproject.toml | 2 +- uv.lock | 4 ++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/COOLSCANPY_ROLL_SCANNING.md b/docs/COOLSCANPY_ROLL_SCANNING.md index 7a9a8a05..5b57399a 100644 --- a/docs/COOLSCANPY_ROLL_SCANNING.md +++ b/docs/COOLSCANPY_ROLL_SCANNING.md @@ -38,7 +38,8 @@ uv sync --group coolscan-roll ``` That group pins the reviewed immutable commit -`c9cc36d4890f1e013d0e51b5e67dffb6dc1b98ef`, built on the merged streaming +`4675b8bedd10ff101f2275c30095a7dc065c846a`, built on the fixed-size +Nikon frame-table repair and the merged streaming finalization from [PR #1](https://github.com/rohanpandula/coolscanpy/pull/1). The pin includes exact USB-topology ownership, six-strip leading-edge handling, Nikon density/exact-builder evidence, and Digital ICE acquisition @@ -72,7 +73,7 @@ so a signed build fails before release if the JIT exception is absent. If you manage your own environment instead, install the package directly: ``` -pip install "coolscanpy @ git+https://github.com/rohanpandula/coolscanpy.git@c9cc36d4890f1e013d0e51b5e67dffb6dc1b98ef" +pip install "coolscanpy @ git+https://github.com/rohanpandula/coolscanpy.git@4675b8bedd10ff101f2275c30095a7dc065c846a" ``` The accepted color-negative roll workflow has no SANE dependency. diff --git a/pyproject.toml b/pyproject.toml index 97e096ed..dafaf868 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -60,7 +60,7 @@ coolscan-roll = [ # Pin the reviewed color-negative density/DICE/direct-USB implementation # until its first tagged release. # No SANE dependency for this workflow; see docs/COOLSCANPY_ROLL_SCANNING.md. - "coolscanpy @ git+https://github.com/rohanpandula/coolscanpy.git@c9cc36d4890f1e013d0e51b5e67dffb6dc1b98ef", + "coolscanpy @ git+https://github.com/rohanpandula/coolscanpy.git@4675b8bedd10ff101f2275c30095a7dc065c846a", ] fauxice = [ "portable-digital-ice @ https://github.com/rohanpandula/digital-fauxice/releases/download/v0.3.0/portable_digital_ice-0.3.0-py3-none-any.whl", diff --git a/uv.lock b/uv.lock index 4004ac9b..b8c2798a 100644 --- a/uv.lock +++ b/uv.lock @@ -77,7 +77,7 @@ wheels = [ [[package]] name = "coolscanpy" version = "0.1.3" -source = { git = "https://github.com/rohanpandula/coolscanpy.git?rev=c9cc36d4890f1e013d0e51b5e67dffb6dc1b98ef#c9cc36d4890f1e013d0e51b5e67dffb6dc1b98ef" } +source = { git = "https://github.com/rohanpandula/coolscanpy.git?rev=4675b8bedd10ff101f2275c30095a7dc065c846a#4675b8bedd10ff101f2275c30095a7dc065c846a" } dependencies = [ { name = "imagecodecs" }, { name = "jinja2" }, @@ -432,7 +432,7 @@ requires-dist = [ [package.metadata.requires-dev] camera = [{ name = "gphoto2", marker = "sys_platform != 'win32'", specifier = ">=2.5" }] -coolscan-roll = [{ name = "coolscanpy", git = "https://github.com/rohanpandula/coolscanpy.git?rev=c9cc36d4890f1e013d0e51b5e67dffb6dc1b98ef" }] +coolscan-roll = [{ name = "coolscanpy", git = "https://github.com/rohanpandula/coolscanpy.git?rev=4675b8bedd10ff101f2275c30095a7dc065c846a" }] dev = [ { name = "pyinstaller", specifier = ">=6.18.0" }, { name = "pytest", specifier = "==9.0.2" }, From d6c660e09801a287f3f3702645c74f9b2f817b45 Mon Sep 17 00:00:00 2001 From: Rohan Pandula Date: Wed, 22 Jul 2026 21:11:01 -0700 Subject: [PATCH 26/48] fix(roll): ignore terminal preview slivers --- docs/COOLSCANPY_ROLL_SCANNING.md | 4 ++-- pyproject.toml | 2 +- uv.lock | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/COOLSCANPY_ROLL_SCANNING.md b/docs/COOLSCANPY_ROLL_SCANNING.md index 5b57399a..1fc95c2e 100644 --- a/docs/COOLSCANPY_ROLL_SCANNING.md +++ b/docs/COOLSCANPY_ROLL_SCANNING.md @@ -38,7 +38,7 @@ uv sync --group coolscan-roll ``` That group pins the reviewed immutable commit -`4675b8bedd10ff101f2275c30095a7dc065c846a`, built on the fixed-size +`e42c68fb53b74aee9fc29eae432d092de351001d`, built on the fixed-size Nikon frame-table repair and the merged streaming finalization from [PR #1](https://github.com/rohanpandula/coolscanpy/pull/1). The pin includes exact USB-topology ownership, six-strip leading-edge @@ -73,7 +73,7 @@ so a signed build fails before release if the JIT exception is absent. If you manage your own environment instead, install the package directly: ``` -pip install "coolscanpy @ git+https://github.com/rohanpandula/coolscanpy.git@4675b8bedd10ff101f2275c30095a7dc065c846a" +pip install "coolscanpy @ git+https://github.com/rohanpandula/coolscanpy.git@e42c68fb53b74aee9fc29eae432d092de351001d" ``` The accepted color-negative roll workflow has no SANE dependency. diff --git a/pyproject.toml b/pyproject.toml index dafaf868..e47f3ec4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -60,7 +60,7 @@ coolscan-roll = [ # Pin the reviewed color-negative density/DICE/direct-USB implementation # until its first tagged release. # No SANE dependency for this workflow; see docs/COOLSCANPY_ROLL_SCANNING.md. - "coolscanpy @ git+https://github.com/rohanpandula/coolscanpy.git@4675b8bedd10ff101f2275c30095a7dc065c846a", + "coolscanpy @ git+https://github.com/rohanpandula/coolscanpy.git@e42c68fb53b74aee9fc29eae432d092de351001d", ] fauxice = [ "portable-digital-ice @ https://github.com/rohanpandula/digital-fauxice/releases/download/v0.3.0/portable_digital_ice-0.3.0-py3-none-any.whl", diff --git a/uv.lock b/uv.lock index b8c2798a..62a54370 100644 --- a/uv.lock +++ b/uv.lock @@ -77,7 +77,7 @@ wheels = [ [[package]] name = "coolscanpy" version = "0.1.3" -source = { git = "https://github.com/rohanpandula/coolscanpy.git?rev=4675b8bedd10ff101f2275c30095a7dc065c846a#4675b8bedd10ff101f2275c30095a7dc065c846a" } +source = { git = "https://github.com/rohanpandula/coolscanpy.git?rev=e42c68fb53b74aee9fc29eae432d092de351001d#e42c68fb53b74aee9fc29eae432d092de351001d" } dependencies = [ { name = "imagecodecs" }, { name = "jinja2" }, @@ -432,7 +432,7 @@ requires-dist = [ [package.metadata.requires-dev] camera = [{ name = "gphoto2", marker = "sys_platform != 'win32'", specifier = ">=2.5" }] -coolscan-roll = [{ name = "coolscanpy", git = "https://github.com/rohanpandula/coolscanpy.git?rev=4675b8bedd10ff101f2275c30095a7dc065c846a" }] +coolscan-roll = [{ name = "coolscanpy", git = "https://github.com/rohanpandula/coolscanpy.git?rev=e42c68fb53b74aee9fc29eae432d092de351001d" }] dev = [ { name = "pyinstaller", specifier = ">=6.18.0" }, { name = "pytest", specifier = "==9.0.2" }, From 2e20d2a06ffe3698b8fb83abefb90721d5aca6d7 Mon Sep 17 00:00:00 2001 From: Rohan Pandula Date: Wed, 22 Jul 2026 21:41:51 -0700 Subject: [PATCH 27/48] fix(repair): pin hybrid synthesis budget --- negpy/services/repair/fauxice_hybrid_runner.py | 13 +++++++++++++ negpy/services/repair/hybrid_runtime_manifest.py | 13 ++++++++++++- tests/repair/test_fauxice_hybrid_runner.py | 2 ++ tests/repair/test_hybrid_runtime_manifest.py | 2 ++ 4 files changed, 29 insertions(+), 1 deletion(-) diff --git a/negpy/services/repair/fauxice_hybrid_runner.py b/negpy/services/repair/fauxice_hybrid_runner.py index d6d0812e..4502c482 100644 --- a/negpy/services/repair/fauxice_hybrid_runner.py +++ b/negpy/services/repair/fauxice_hybrid_runner.py @@ -22,6 +22,7 @@ import hashlib import io import json +import math import os import re import signal @@ -129,6 +130,7 @@ class HybridRuntimeConfig: inpaint_device: str = "cpu" inpaint_threads: int = 1 inpaint_seed: int = 0 + max_synthesis_fraction: float = 0.02 def __post_init__(self) -> None: for field_name in ( @@ -157,6 +159,14 @@ def __post_init__(self) -> None: raise ValueError("inpaint_threads must be a positive integer") if type(self.inpaint_seed) is not int or self.inpaint_seed < 0: raise ValueError("inpaint_seed must be a non-negative integer") + if ( + isinstance(self.max_synthesis_fraction, bool) + or not isinstance(self.max_synthesis_fraction, (int, float)) + or not math.isfinite(float(self.max_synthesis_fraction)) + or not 0.0 <= float(self.max_synthesis_fraction) <= 1.0 + ): + raise ValueError("max_synthesis_fraction must be finite and in [0, 1]") + object.__setattr__(self, "max_synthesis_fraction", float(self.max_synthesis_fraction)) def validate_files(self) -> None: """Fail closed unless every configured runtime artifact is present.""" @@ -723,6 +733,8 @@ def run_hybrid_repair( str(acquisition_manifest_path), "--backend", backend, + "--max-synth-fraction", + format(runtime.max_synthesis_fraction, ".12g"), "--iopaint-python", str(runtime.iopaint_python), "--iopaint-executable", @@ -958,6 +970,7 @@ def _validate_receipt_bindings( or synthesis.get("frame_pixel_count") != frame_pixels or synthesis.get("fraction") != expected_fraction or synthesis.get("within_budget") is not True + or synthesis.get("maximum_fraction") != runtime.max_synthesis_fraction ): raise HybridRunError("hybrid receipt synthesis accounting changed") routing = receipt.get("routing") diff --git a/negpy/services/repair/hybrid_runtime_manifest.py b/negpy/services/repair/hybrid_runtime_manifest.py index 0c684d7b..2021a97e 100644 --- a/negpy/services/repair/hybrid_runtime_manifest.py +++ b/negpy/services/repair/hybrid_runtime_manifest.py @@ -4,6 +4,7 @@ import hashlib import json +import math import os import re import stat @@ -13,7 +14,7 @@ from negpy.services.repair.fauxice_hybrid_runner import HybridRuntimeConfig -RUNTIME_MANIFEST_SCHEMA = "negpy.fauxce-hybrid-runtime.v1" +RUNTIME_MANIFEST_SCHEMA = "negpy.fauxce-hybrid-runtime.v2" RUNTIME_MANIFEST_FILENAME = "fauxce-hybrid-runtime.json" RUNTIME_MANIFEST_MAX_BYTES = 64 * 1024 _SHA256_RE = re.compile(r"[0-9a-f]{64}") @@ -39,6 +40,7 @@ "inpaint_device", "inpaint_threads", "inpaint_seed", + "max_synthesis_fraction", } ) @@ -159,6 +161,14 @@ def load_hybrid_runtime_manifest( value = document[field] if type(value) is not int or value < minimum: raise HybridRuntimeManifestError(f"{field} must be an integer greater than or equal to {minimum}") + maximum_fraction = document["max_synthesis_fraction"] + if ( + isinstance(maximum_fraction, bool) + or not isinstance(maximum_fraction, (int, float)) + or not math.isfinite(float(maximum_fraction)) + or not 0.0 <= float(maximum_fraction) <= 1.0 + ): + raise HybridRuntimeManifestError("max_synthesis_fraction must be finite and in [0, 1]") try: return HybridRuntimeConfig( hybrid_python=Path(document["hybrid_python"]), @@ -174,6 +184,7 @@ def load_hybrid_runtime_manifest( inpaint_device=document["inpaint_device"], inpaint_threads=document["inpaint_threads"], inpaint_seed=document["inpaint_seed"], + max_synthesis_fraction=float(maximum_fraction), ) except (TypeError, ValueError) as error: raise HybridRuntimeManifestError(f"hybrid runtime manifest values are invalid: {error}") from error diff --git a/tests/repair/test_fauxice_hybrid_runner.py b/tests/repair/test_fauxice_hybrid_runner.py index edadd154..8aed0b42 100644 --- a/tests/repair/test_fauxice_hybrid_runner.py +++ b/tests/repair/test_fauxice_hybrid_runner.py @@ -181,6 +181,7 @@ def _write_success_outputs( "synthesis": { "fraction": fraction, "frame_pixel_count": int(mask.size), + "maximum_fraction": float(_arg(argv, "--max-synth-fraction")), "pixel_count": synthesis_pixels, "within_budget": True, }, @@ -489,6 +490,7 @@ def test_argv_carries_the_documented_cli_flags(self, tmp_path: Path) -> None: for flag, value in ( ("--same-frame-id", "frame-004"), ("--backend", "cpu"), + ("--max-synth-fraction", "0.02"), ("--iopaint-python", str(runtime.iopaint_python)), ("--iopaint-executable", str(runtime.iopaint_executable)), ("--iopaint-source-manifest-sha256", runtime.iopaint_source_manifest_sha256), diff --git a/tests/repair/test_hybrid_runtime_manifest.py b/tests/repair/test_hybrid_runtime_manifest.py index a610ea38..f5400be9 100644 --- a/tests/repair/test_hybrid_runtime_manifest.py +++ b/tests/repair/test_hybrid_runtime_manifest.py @@ -30,6 +30,7 @@ def _document(root: Path) -> dict[str, object]: "iopaint_executable": str(root / "iopaint" / "bin" / "iopaint"), "iopaint_python": str(root / "iopaint" / "bin" / "python"), "iopaint_source_manifest_sha256": "3" * 64, + "max_synthesis_fraction": 0.1, "model_dir": str(root / "models"), "model_weights": str(root / "models" / "torch" / "hub" / "checkpoints" / "big-lama.pt"), "model_weights_sha256": "4" * 64, @@ -54,6 +55,7 @@ def test_loads_canonical_hash_pinned_runtime(tmp_path: Path) -> None: assert runtime.hybrid_python == tmp_path / "hybrid" / "bin" / "python" assert runtime.core_source_manifest_sha256 == "1" * 64 assert runtime.hybrid_source_manifest_sha256 == "2" * 64 + assert runtime.max_synthesis_fraction == 0.1 def test_default_loader_requires_manifest_and_independent_pin(tmp_path: Path) -> None: From 87b645b4ecce29c571eabeb038aa61e43461a6dd Mon Sep 17 00:00:00 2001 From: Rohan Pandula Date: Wed, 22 Jul 2026 22:29:31 -0700 Subject: [PATCH 28/48] feat(roll): guide bounded hybrid repair --- negpy/desktop/view/sidebar/coolscan_roll.py | 40 +++++++++++++++++++++ negpy/desktop/workers/roll_worker.py | 30 ++++++++++++++++ negpy/infrastructure/roll/settings.py | 15 ++++++++ negpy/services/roll/service.py | 3 +- tests/roll/test_sidebar.py | 3 ++ tests/roll/test_worker.py | 19 ++++++++++ 6 files changed, 109 insertions(+), 1 deletion(-) diff --git a/negpy/desktop/view/sidebar/coolscan_roll.py b/negpy/desktop/view/sidebar/coolscan_roll.py index b4b6fe37..0ccc13b7 100644 --- a/negpy/desktop/view/sidebar/coolscan_roll.py +++ b/negpy/desktop/view/sidebar/coolscan_roll.py @@ -32,6 +32,7 @@ QListWidgetItem, QProgressBar, QPushButton, + QDoubleSpinBox, QSpinBox, QVBoxLayout, QWidget, @@ -120,6 +121,7 @@ def _update_settings_from_ui(self) -> None: write_positive=self.write_positive_check.isChecked(), repair_mode=self.repair_mode_combo.currentData() or RollScanSettings.defaults().repair_mode, positive_mode=self.positive_mode_combo.currentData() or PositiveColorMode.NIKON_EXACT.value, + hybrid_synthesis_limit_percent=self.hybrid_synthesis_limit_spin.value(), ) if updated == self._settings: return @@ -240,6 +242,19 @@ def _init_ui(self) -> None: self.repair_mode_combo.setCurrentIndex(idx) out_form.addRow("Repair mode", self.repair_mode_combo) + self.hybrid_synthesis_limit_spin = QDoubleSpinBox() + self.hybrid_synthesis_limit_spin.setRange(0.0, 100.0) + self.hybrid_synthesis_limit_spin.setDecimals(1) + self.hybrid_synthesis_limit_spin.setSuffix("%") + self.hybrid_synthesis_limit_spin.setValue( + self._settings.hybrid_synthesis_limit_percent + ) + self.hybrid_synthesis_limit_spin.setToolTip( + "Maximum portion of a frame Hybrid may synthesize. This is a ceiling, not a target; " + "the pinned runtime's own ceiling remains in force if it is lower. Set 0% to forbid synthesis." + ) + out_form.addRow("Hybrid ceiling", self.hybrid_synthesis_limit_spin) + layout.addLayout(out_form) self.tier_hint = QLabel("") @@ -247,6 +262,13 @@ def _init_ui(self) -> None: self.tier_hint.setWordWrap(True) layout.addWidget(self.tier_hint) + self.hybrid_guidance = QLabel("") + self.hybrid_guidance.setStyleSheet( + f"color: {THEME.text_muted}; font-size: {THEME.font_size_small}px;" + ) + self.hybrid_guidance.setWordWrap(True) + layout.addWidget(self.hybrid_guidance) + # ── PROGRESS / STATUS ──────────────────────────────── self.progress_bar = QProgressBar() self.progress_bar.setVisible(False) @@ -314,6 +336,7 @@ def _connect_signals(self) -> None: cb.toggled.connect(self._update_settings_from_ui) self.positive_mode_combo.currentIndexChanged.connect(self._update_settings_from_ui) self.repair_mode_combo.currentIndexChanged.connect(self._update_settings_from_ui) + self.hybrid_synthesis_limit_spin.valueChanged.connect(self._update_settings_from_ui) self.preview_btn.clicked.connect(self._on_preview_clicked) self.contact_sheet.itemSelectionChanged.connect(self._on_selection_changed) self.offset_apply_btn.clicked.connect(self._on_apply_offset) @@ -518,6 +541,7 @@ def _on_scan_clicked(self) -> None: write_positive=self.write_positive_check.isChecked(), repair_mode=self.repair_mode_combo.currentData() or RollScanSettings.defaults().repair_mode, positive_mode=self.positive_mode_combo.currentData() or PositiveColorMode.NIKON_EXACT.value, + hybrid_synthesis_limit_percent=self.hybrid_synthesis_limit_spin.value(), ) self._active_scan_request = req self.set_scanning(True) @@ -664,6 +688,21 @@ def _update_tier_hint(self) -> None: self.tier_hint.setText("") self.tier_hint.setVisible(False) + def _update_hybrid_guidance(self) -> None: + """Explain the choice without silently opting a frame into AI fill.""" + + if self.repair_mode_combo.currentData() == RepairMode.HYBRID.value: + ceiling = self.hybrid_synthesis_limit_spin.value() + self.hybrid_guidance.setText( + f"Hybrid is recommended only when the scanner finds severe zero-signal infrared areas. " + f"It keeps Exact repair elsewhere, discloses every generated pixel, and is capped at {ceiling:.1f}%." + ) + else: + self.hybrid_guidance.setText( + "Exact is recommended for normal dust and scratches with usable infrared, and for Nikon-parity-only output. " + "Use Hybrid only when infrared has a severe zero-signal area that Exact cannot repair." + ) + def _apply_gating(self) -> None: missing_preview = self._missing_for_preview() missing_scan = self._missing_for_scan() @@ -671,6 +710,7 @@ def _apply_gating(self) -> None: self.scan_btn.setEnabled(not missing_scan and not self._scanning) self.safe_stop_btn.setEnabled(self._scanning) self._update_tier_hint() + self._update_hybrid_guidance() if missing_scan: self.gate_hint.setText("To scan: " + ", ".join(missing_scan) + ".") self.gate_hint.setVisible(True) diff --git a/negpy/desktop/workers/roll_worker.py b/negpy/desktop/workers/roll_worker.py index adfd7c5c..6e60c2b2 100644 --- a/negpy/desktop/workers/roll_worker.py +++ b/negpy/desktop/workers/roll_worker.py @@ -9,6 +9,8 @@ from __future__ import annotations +import dataclasses +import math import threading from dataclasses import dataclass @@ -49,6 +51,22 @@ class RollBatchScanRequest: write_positive: bool = RollScanSettings.defaults().write_positive repair_mode: str = RollScanSettings.defaults().repair_mode positive_mode: str = RollScanSettings.defaults().positive_mode + hybrid_synthesis_limit_percent: float = ( + RollScanSettings.defaults().hybrid_synthesis_limit_percent + ) + + def __post_init__(self) -> None: + value = self.hybrid_synthesis_limit_percent + if ( + isinstance(value, bool) + or not isinstance(value, (int, float)) + or not math.isfinite(float(value)) + or not 0.0 <= float(value) <= 100.0 + ): + raise ValueError( + "hybrid_synthesis_limit_percent must be finite and in [0, 100]" + ) + object.__setattr__(self, "hybrid_synthesis_limit_percent", float(value)) class RollWorker(QObject): @@ -73,6 +91,7 @@ def __init__( hybrid_runtime: HybridRuntimeConfig | None = None, ) -> None: super().__init__() + self._hybrid_runtime = hybrid_runtime self._service = RollScanningService(hybrid_runtime=hybrid_runtime) self._open_device_id: str | None = None self._operation_lock = threading.RLock() @@ -194,6 +213,16 @@ def run_batch_scan(self, req: RollBatchScanRequest) -> None: self._reject_if_shutting_down() self._ensure_open(req.device_id) self.status.emit("Scanning…") + hybrid_runtime = self._hybrid_runtime + if hybrid_runtime is not None: + user_fraction = req.hybrid_synthesis_limit_percent / 100.0 + hybrid_runtime = dataclasses.replace( + hybrid_runtime, + max_synthesis_fraction=min( + user_fraction, + hybrid_runtime.max_synthesis_fraction, + ), + ) for frame in self._service.scan_many( req.slots, on_progress=lambda p: self.progress.emit( @@ -210,6 +239,7 @@ def run_batch_scan(self, req: RollBatchScanRequest) -> None: write_positive=req.write_positive, repair_mode=req.repair_mode, positive_mode=req.positive_mode, + hybrid_runtime=hybrid_runtime, on_repair_progress=lambda fraction: self.progress.emit( fraction, "Applying Digital ICE repair…", diff --git a/negpy/infrastructure/roll/settings.py b/negpy/infrastructure/roll/settings.py index 1a148d76..bf17736a 100644 --- a/negpy/infrastructure/roll/settings.py +++ b/negpy/infrastructure/roll/settings.py @@ -3,6 +3,7 @@ from __future__ import annotations from dataclasses import dataclass +import math from negpy.infrastructure.roll.repair import RepairMode from negpy.services.roll.exact_color import PositiveColorMode @@ -43,6 +44,20 @@ class RollScanSettings: write_positive: bool = True repair_mode: str = RepairMode.HYBRID.value positive_mode: str = PositiveColorMode.NIKON_EXACT.value + # This is a user-selected ceiling, not a target percentage. The pinned + # runtime remains the final hard limit; the worker takes the lower value. + hybrid_synthesis_limit_percent: float = 10.0 + + def __post_init__(self) -> None: + value = self.hybrid_synthesis_limit_percent + if ( + isinstance(value, bool) + or not isinstance(value, (int, float)) + or not math.isfinite(float(value)) + or not 0.0 <= float(value) <= 100.0 + ): + raise ValueError("hybrid_synthesis_limit_percent must be finite and in [0, 100]") + object.__setattr__(self, "hybrid_synthesis_limit_percent", float(value)) @classmethod def defaults(cls) -> "RollScanSettings": diff --git a/negpy/services/roll/service.py b/negpy/services/roll/service.py index 2f02a451..b025492a 100644 --- a/negpy/services/roll/service.py +++ b/negpy/services/roll/service.py @@ -856,6 +856,7 @@ def write_frame( repair_mode: str = RepairMode.EXACT.value, positive_mode: str = roll_exact_color.PositiveColorMode.NIKON_EXACT.value, builder_receipt: roll_exact_color.BuilderReceipt | None = None, + hybrid_runtime: HybridRuntimeConfig | None = None, on_repair_progress: Callable[[float], None] | None = None, ) -> RollFrameOutput: """Write one scanned `Frame` to disk across up to three tiers, plus a @@ -1032,7 +1033,7 @@ def write_frame( repair_result = roll_repair.repair( acquisition, resolved_repair_mode, - hybrid_runtime=self._hybrid_runtime, + hybrid_runtime=hybrid_runtime or self._hybrid_runtime, progress=on_repair_progress, cancel=self._repair_cancel, ) diff --git a/tests/roll/test_sidebar.py b/tests/roll/test_sidebar.py index eee11316..17d42bff 100644 --- a/tests/roll/test_sidebar.py +++ b/tests/roll/test_sidebar.py @@ -60,6 +60,8 @@ def test_sidebar_builds_with_all_controls(self) -> None: "approve_btn", "gate_hint", "status_label", + "hybrid_synthesis_limit_spin", + "hybrid_guidance", ): assert hasattr(w, attr), attr @@ -220,6 +222,7 @@ def test_scan_click_builds_request_from_selection_and_settings(self, fake_coolsc assert req.device_id == "dev-1" assert req.slots == (1, 2) assert req.output_folder == str(tmp_path) + assert req.hybrid_synthesis_limit_percent == 10.0 assert w._scanning is True def test_scan_click_without_a_selected_slot_is_a_noop(self, fake_coolscanpy, tmp_path) -> None: diff --git a/tests/roll/test_worker.py b/tests/roll/test_worker.py index 7c1b54b3..1fc3b433 100644 --- a/tests/roll/test_worker.py +++ b/tests/roll/test_worker.py @@ -521,6 +521,24 @@ def test_request_defaults_match_the_complete_parity_workflow(self) -> None: assert req.write_positive is True assert req.repair_mode == "hybrid" assert req.positive_mode == "nikon-exact" + assert req.hybrid_synthesis_limit_percent == 10.0 + + @pytest.mark.parametrize( + "value", + (float("nan"), float("inf"), -0.1, 100.1, True), + ) + def test_request_rejects_invalid_hybrid_synthesis_limit( + self, + value: float | bool, + ) -> None: + with pytest.raises(ValueError, match="hybrid_synthesis_limit_percent"): + RollBatchScanRequest( + device_id="d", + slots=(1,), + output_folder="/tmp/x", + filename_pattern="p", + hybrid_synthesis_limit_percent=value, + ) def test_tier_flags_are_forwarded_to_write_frame( self, @@ -564,6 +582,7 @@ def _spy(frame, output_folder, filename_pattern, **kwargs): "write_positive": True, "repair_mode": "hybrid", "positive_mode": "negpy-approximate", + "hybrid_runtime": None, } def test_repaired_tier_writes_through_the_worker_with_a_registered_engine(self, fake_coolscanpy, fake_repair_engine, tmp_path) -> None: From c38991dbaa8e57a7d66db3e276998de5f1bdb35a Mon Sep 17 00:00:00 2001 From: Rohan Pandula Date: Wed, 22 Jul 2026 23:37:28 -0700 Subject: [PATCH 29/48] fix(roll): tolerate Finder metadata in sealed runs --- negpy/services/roll/deep_acceptance.py | 3 ++ negpy/services/roll/live_evidence.py | 14 ++++++- negpy/services/roll/live_reservation.py | 17 ++++++++ tests/roll/test_deep_acceptance.py | 53 +++++++++++++++++++++++++ tests/roll/test_live_acceptance.py | 6 ++- tests/roll/test_live_evidence.py | 48 +++++++++++++++++++++- tests/roll/test_live_reservation.py | 19 +++++++++ 7 files changed, 154 insertions(+), 6 deletions(-) diff --git a/negpy/services/roll/deep_acceptance.py b/negpy/services/roll/deep_acceptance.py index 606ec71f..490de50e 100644 --- a/negpy/services/roll/deep_acceptance.py +++ b/negpy/services/roll/deep_acceptance.py @@ -32,6 +32,7 @@ ) from negpy.services.roll import exact_color, nikon_icc from negpy.services.roll import service as roll_service +from negpy.services.roll.live_reservation import is_ignorable_finder_metadata_file from negpy.services.roll.portable_builder import PortableStage1Builder from negpy.services.roll.portable_cms import PortableCMSOnEvaluator from negpy.services.roll.service import RollFrameOutput @@ -1637,6 +1638,8 @@ def _inventory( for name in files: child = parent / name metadata = child.lstat() + if is_ignorable_finder_metadata_file(name, metadata): + continue if stat.S_ISLNK(metadata.st_mode) or not stat.S_ISREG(metadata.st_mode): _fail("inventory", f"file entry is unsafe: {child}") found_files.add(child.resolve(strict=True)) diff --git a/negpy/services/roll/live_evidence.py b/negpy/services/roll/live_evidence.py index 5ef30893..db2fb6a4 100644 --- a/negpy/services/roll/live_evidence.py +++ b/negpy/services/roll/live_evidence.py @@ -16,6 +16,7 @@ FileIdentity, InventoryConflict, InventorySnapshot, + is_ignorable_finder_metadata_file, ) @@ -190,6 +191,9 @@ def snapshot_capture_evidence(root: Path) -> CaptureEvidenceSnapshot: path = current / name if current == canonical and name == OUTPUT_LOCK_NAME: continue + metadata = path.lstat() + if is_ignorable_finder_metadata_file(name, metadata): + continue paths.append(path) if len(paths) > _MAX_FILES: raise InventoryConflict("capture evidence contains too many files") @@ -487,6 +491,7 @@ def validate_six_frame_batch_capture_evidence( batch_session = journal.get("batch_session") output_sha256 = journal.get("output_sha256") selection = journal.get("live_frame_selection") + boundary_offset = selection.get("boundary_offset") if isinstance(selection, dict) else None roll_identity = selection.get("roll_identity") if isinstance(selection, dict) else None selected_comparison = roll_identity.get("selected_slot_comparison") if isinstance(roll_identity, dict) else None comparison = roll_identity.get("comparison") if isinstance(roll_identity, dict) else None @@ -526,8 +531,13 @@ def validate_six_frame_batch_capture_evidence( or batch_session.get("selected_slots") != list(_SIX_FRAME_SLOTS) or not isinstance(selection, dict) or selection.get("frame") != slot - or selection.get("requested_boundary_offset_rows") != frame_job["boundary_offset_rows"] - or selection.get("applied_boundary_offset_rows") != frame_job["boundary_offset_rows"] + # The Coolscan worker writes this proof beneath ``boundary_offset`` + # alongside the resolved transport origin. Keep the validation + # closed over that canonical, engine-owned layout rather than + # silently accepting an obsolete flattened representation. + or not isinstance(boundary_offset, dict) + or boundary_offset.get("requested_rows") != frame_job["boundary_offset_rows"] + or boundary_offset.get("applied_rows") != frame_job["boundary_offset_rows"] or not isinstance(roll_identity, dict) or roll_identity.get("reviewed_fingerprint_sha256") != reviewed_binding or not _is_sha256(roll_identity.get("fresh_fingerprint_sha256")) diff --git a/negpy/services/roll/live_reservation.py b/negpy/services/roll/live_reservation.py index a20c827b..a2e22330 100644 --- a/negpy/services/roll/live_reservation.py +++ b/negpy/services/roll/live_reservation.py @@ -19,6 +19,7 @@ OUTPUT_LOCK_NAME = ".negpy-live-acceptance.lock" +_IGNORABLE_FINDER_METADATA_NAMES = frozenset({".DS_Store"}) DEFAULT_RECEIPT_MAX_BYTES = 1024 * 1024 DEFAULT_LOCK_MAX_BYTES = 16 * 1024 @@ -141,6 +142,18 @@ def _directory_checkpoint(metadata: os.stat_result) -> tuple[int, int, int, int] ) +def is_ignorable_finder_metadata_file(name: str, metadata: os.stat_result) -> bool: + """Return whether ``name`` is macOS directory metadata, never an artifact. + + Finder creates a regular ``.DS_Store`` merely by browsing a directory. It + has no capture or output provenance, so it must not make a held receipt + fail. Keep the exception exact: a directory, symlink, or any other hidden + name remains an inventory conflict. + """ + + return name in _IGNORABLE_FINDER_METADATA_NAMES and stat.S_ISREG(metadata.st_mode) + + def _write_all(descriptor: int, payload: bytes) -> None: written = 0 while written < len(payload): @@ -484,6 +497,8 @@ def _relative_entry(self, path: Path | str, *, label: str) -> str: name = os.fspath(relative) if name == OUTPUT_LOCK_NAME: raise InventoryConflict("the fixed output lock cannot be declared as an output") + if Path(name).name in _IGNORABLE_FINDER_METADATA_NAMES: + raise InventoryConflict("Finder metadata cannot be declared as an output") return name def _scan_directory( @@ -507,6 +522,8 @@ def _scan_directory( metadata = _child_metadata(descriptor, name) except OSError as error: raise InventoryConflict(f"output entry changed while inspecting {relative}: {error}") from error + if is_ignorable_finder_metadata_file(name, metadata): + continue if stat.S_ISLNK(metadata.st_mode): raise InventoryConflict(f"output entry is a symbolic link: {relative}") if stat.S_ISREG(metadata.st_mode): diff --git a/tests/roll/test_deep_acceptance.py b/tests/roll/test_deep_acceptance.py index 2cfad360..6a73ad20 100644 --- a/tests/roll/test_deep_acceptance.py +++ b/tests/roll/test_deep_acceptance.py @@ -772,6 +772,59 @@ def test_six_frame_batch_rejects_one_unreferenced_file( ) +def test_six_frame_batch_ignores_regular_finder_metadata( + monkeypatch: pytest.MonkeyPatch, + tmp_path: Path, +) -> None: + outputs, audits = _fake_audits(tmp_path) + iterator = iter(audits) + monkeypatch.setattr( + deep_acceptance, + "_validate_frame", + lambda *args, **kwargs: next(iterator), + ) + (tmp_path / ".DS_Store").write_bytes(b"finder metadata") + + result = deep_acceptance.validate_six_frame_batch( + outputs, + output_dir=tmp_path, + builder=object(), + evaluator=object(), + hybrid_runtime=_Runtime(), + ) + + assert result["status"] == "passed" + assert result["inventory"]["visible_file_count"] == 42 + + +def test_six_frame_batch_rejects_finder_metadata_symlink( + monkeypatch: pytest.MonkeyPatch, + tmp_path: Path, +) -> None: + outputs, audits = _fake_audits(tmp_path) + iterator = iter(audits) + monkeypatch.setattr( + deep_acceptance, + "_validate_frame", + lambda *args, **kwargs: next(iterator), + ) + outside = tmp_path.parent / "outside-metadata" + outside.write_bytes(b"not metadata") + (tmp_path / ".DS_Store").symlink_to(outside) + + with pytest.raises( + deep_acceptance.DeepAcceptanceError, + match="file entry is unsafe", + ): + deep_acceptance.validate_six_frame_batch( + outputs, + output_dir=tmp_path, + builder=object(), + evaluator=object(), + hybrid_runtime=_Runtime(), + ) + + def test_six_frame_batch_rejects_cross_frame_fingerprint_drift( monkeypatch: pytest.MonkeyPatch, tmp_path: Path, diff --git a/tests/roll/test_live_acceptance.py b/tests/roll/test_live_acceptance.py index 69657a75..c2ac7d37 100644 --- a/tests/roll/test_live_acceptance.py +++ b/tests/roll/test_live_acceptance.py @@ -307,8 +307,10 @@ def _write_completed_attempt_evidence( nonce = f"{slot:032x}" selection = { "frame": slot, - "requested_boundary_offset_rows": 0, - "applied_boundary_offset_rows": 0, + "boundary_offset": { + "requested_rows": 0, + "applied_rows": 0, + }, "roll_identity": { "reviewed_fingerprint_sha256": reviewed_fingerprint, "fresh_fingerprint_sha256": fresh_fingerprint, diff --git a/tests/roll/test_live_evidence.py b/tests/roll/test_live_evidence.py index 0b1e1db3..808af377 100644 --- a/tests/roll/test_live_evidence.py +++ b/tests/roll/test_live_evidence.py @@ -160,8 +160,10 @@ def _write_completed_batch(root: Path) -> dict[str, object]: nonce = f"{slot:032x}" selection = { "frame": slot, - "requested_boundary_offset_rows": 0, - "applied_boundary_offset_rows": 0, + "boundary_offset": { + "requested_rows": 0, + "applied_rows": 0, + }, "roll_identity": { "reviewed_fingerprint_sha256": reviewed_sha256, "fresh_fingerprint_sha256": fresh_sha256, @@ -285,6 +287,43 @@ def test_completed_post_finalization_batch_binds_all_six_capture_records( assert binding["reviewed_fingerprint_sha256"] == expected["reviewed_sha256"] +def test_finder_metadata_is_ignored_without_weakening_capture_evidence_inventory( + tmp_path: Path, +) -> None: + root = tmp_path / "attempts" + root.mkdir() + lease = _lease(root) + completed = _write_completed_batch(root) + batch = completed["batch"] + assert isinstance(batch, Path) + (root / ".DS_Store").write_bytes(b"finder root metadata") + (batch / ".DS_Store").write_bytes(b"finder batch metadata") + + try: + snapshot = snapshot_capture_evidence(root) + inventory = lease.assert_inventory(snapshot.files) + binding = validate_six_frame_batch_capture_evidence(snapshot) + receipt_evidence = bind_capture_evidence_inventory(snapshot, inventory) + finally: + lease.release() + + assert snapshot.manifest["file_count"] == 28 + assert all(not row["path"].endswith(".DS_Store") for row in snapshot.manifest["files"]) + assert binding["session_id"] == batch.name + assert receipt_evidence["retained"] is True + + +def test_finder_metadata_name_does_not_bypass_symlink_rejection(tmp_path: Path) -> None: + root = tmp_path / "attempts" + root.mkdir() + outside = tmp_path / "outside" + outside.write_bytes(b"must not be followed") + (root / ".DS_Store").symlink_to(outside) + + with pytest.raises(InventoryConflict, match="exclusively owned regular file"): + snapshot_capture_evidence(root) + + def _rewrite_json(path: Path, mutate) -> None: document = json.loads(path.read_text(encoding="utf-8")) mutate(document) @@ -376,6 +415,11 @@ def test_additional_diagnostic_inside_completed_batch_is_allowed( lambda document: document.__setitem__("frame", 2), "live preview, transport table, or frame map", ), + ( + "frame-005/journal.json", + lambda document: document["live_frame_selection"]["boundary_offset"].__setitem__("applied_rows", 1), + "frame 5 journal", + ), ], ) def test_mutated_capture_contract_is_rejected( diff --git a/tests/roll/test_live_reservation.py b/tests/roll/test_live_reservation.py index 703dcee4..d241abdf 100644 --- a/tests/roll/test_live_reservation.py +++ b/tests/roll/test_live_reservation.py @@ -211,6 +211,25 @@ def test_inventory_allows_only_declared_files_and_implied_directories( lease.release() +def test_inventory_ignores_only_regular_finder_metadata(tmp_path: Path) -> None: + output = tmp_path / "outputs" + output.mkdir() + lease = FixedOutputLease.acquire(output, _lock_document()) + artifact = output / "acceptance_slot01.tif" + artifact.write_bytes(b"captured") + (output / ".DS_Store").write_bytes(b"finder metadata") + + try: + snapshot = lease.assert_inventory([artifact]) + finally: + lease.release() + + assert [name for name, _identity in snapshot.files] == [ + OUTPUT_LOCK_NAME, + artifact.name, + ] + + def test_inventory_detects_mutation_of_previously_owned_file(tmp_path: Path) -> None: output = tmp_path / "outputs" output.mkdir() From c3f4aeca64c301766e27609df3b4de8fbeb8e7eb Mon Sep 17 00:00:00 2001 From: Rohan Pandula Date: Wed, 22 Jul 2026 23:39:41 -0700 Subject: [PATCH 30/48] style(roll): condense offset proof note --- negpy/services/roll/live_evidence.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/negpy/services/roll/live_evidence.py b/negpy/services/roll/live_evidence.py index db2fb6a4..03ffa15e 100644 --- a/negpy/services/roll/live_evidence.py +++ b/negpy/services/roll/live_evidence.py @@ -531,10 +531,7 @@ def validate_six_frame_batch_capture_evidence( or batch_session.get("selected_slots") != list(_SIX_FRAME_SLOTS) or not isinstance(selection, dict) or selection.get("frame") != slot - # The Coolscan worker writes this proof beneath ``boundary_offset`` - # alongside the resolved transport origin. Keep the validation - # closed over that canonical, engine-owned layout rather than - # silently accepting an obsolete flattened representation. + # Coolscan's engine-owned offset proof uses this nested layout. or not isinstance(boundary_offset, dict) or boundary_offset.get("requested_rows") != frame_job["boundary_offset_rows"] or boundary_offset.get("applied_rows") != frame_job["boundary_offset_rows"] From 9c6c80d4e8be8b8a856554a998a544a90c5ed967 Mon Sep 17 00:00:00 2001 From: Rohan Pandula Date: Wed, 22 Jul 2026 23:45:12 -0700 Subject: [PATCH 31/48] style(roll): format integrated scanner workflow --- build.py | 9 +-- negpy/desktop/controller.py | 9 +-- negpy/desktop/view/main_window.py | 8 +-- negpy/desktop/view/sidebar/coolscan_roll.py | 32 +++------- negpy/desktop/workers/roll_worker.py | 25 ++------ negpy/infrastructure/roll/fauxice_bridge.py | 20 ++----- negpy/services/repair/fauxice_ir_repair.py | 16 ++--- .../repair/hybrid_runtime_manifest.py | 7 +-- negpy/services/roll/exact_color.py | 32 +++------- negpy/services/roll/live_evidence.py | 5 +- negpy/services/roll/portable_cms.py | 60 +++++-------------- tests/roll/conftest.py | 6 +- tests/roll/test_coolscanpy_roll.py | 24 ++------ tests/roll/test_fauxice_bridge.py | 4 +- tests/roll/test_live_acceptance.py | 21 ++++--- tests/roll/test_live_evidence.py | 4 +- tests/roll/test_live_review.py | 10 +--- tests/roll/test_native_builder.py | 4 +- tests/roll/test_nikon_icc.py | 8 +-- tests/roll/test_service.py | 8 +-- tests/test_controller.py | 4 +- tests/test_ls5000_frozen_packaging.py | 16 ++--- tests/test_main_window.py | 8 +-- tests/test_wheel_packaging.py | 10 +--- 24 files changed, 92 insertions(+), 258 deletions(-) diff --git a/build.py b/build.py index 6668e534..eef8d565 100644 --- a/build.py +++ b/build.py @@ -160,14 +160,9 @@ def preflight_coolscan_runtime() -> str: device_type = getattr(loaded["coolscanpy"], "Device") roll_parameters = inspect.signature(device_type.roll).parameters attempts_root = roll_parameters.get("attempts_root") - if ( - attempts_root is None - or attempts_root.kind is not inspect.Parameter.KEYWORD_ONLY - or attempts_root.default is not None - ): + if attempts_root is None or attempts_root.kind is not inspect.Parameter.KEYWORD_ONLY or attempts_root.default is not None: raise RuntimeError( - "Coolscan build API is stale or incomplete: " - "coolscanpy.Device.roll must expose optional keyword-only attempts_root" + "Coolscan build API is stale or incomplete: coolscanpy.Device.roll must expose optional keyword-only attempts_root" ) bundle_module = loaded["coolscanpy.protocol.ls5000_single_pass.bundle"] diff --git a/negpy/desktop/controller.py b/negpy/desktop/controller.py index 677b5574..7a770627 100644 --- a/negpy/desktop/controller.py +++ b/negpy/desktop/controller.py @@ -2368,9 +2368,7 @@ def request_shutdown(self) -> bool: except Exception as error: logger.exception("application shutdown blocked by scanner teardown") self._shutdown_block_reason = str(error) - self.roll_status.emit( - "Cannot exit: the Coolscan reservation is not safely closed." - ) + self.roll_status.emit("Cannot exit: the Coolscan reservation is not safely closed.") return False self._roll_shutdown_complete = True self._shutdown_block_reason = None @@ -3300,10 +3298,7 @@ def cleanup(self) -> None: if self._cleaned_up: return if not self.request_shutdown(): - raise RollScanningError( - self._shutdown_block_reason - or "Coolscan reservation is not safely closed" - ) + raise RollScanningError(self._shutdown_block_reason or "Coolscan reservation is not safely closed") self._cleaned_up = True self._render_debounce.stop() self._cursor_readout_timer.stop() diff --git a/negpy/desktop/view/main_window.py b/negpy/desktop/view/main_window.py index b64514bc..75ef9777 100644 --- a/negpy/desktop/view/main_window.py +++ b/negpy/desktop/view/main_window.py @@ -166,12 +166,8 @@ def request_shutdown_for_exit(self) -> bool: """Gate both window-close and application-level quit requests.""" if not self.controller.request_shutdown(): - reason = self.controller.shutdown_block_reason or ( - "The scanner reservation has not closed safely yet." - ) - self.statusBar().showMessage( - "Exit blocked until the Coolscan closes safely." - ) + reason = self.controller.shutdown_block_reason or ("The scanner reservation has not closed safely yet.") + self.statusBar().showMessage("Exit blocked until the Coolscan closes safely.") QMessageBox.critical( self, "Scanner still reserved", diff --git a/negpy/desktop/view/sidebar/coolscan_roll.py b/negpy/desktop/view/sidebar/coolscan_roll.py index 0ccc13b7..edd1d372 100644 --- a/negpy/desktop/view/sidebar/coolscan_roll.py +++ b/negpy/desktop/view/sidebar/coolscan_roll.py @@ -246,9 +246,7 @@ def _init_ui(self) -> None: self.hybrid_synthesis_limit_spin.setRange(0.0, 100.0) self.hybrid_synthesis_limit_spin.setDecimals(1) self.hybrid_synthesis_limit_spin.setSuffix("%") - self.hybrid_synthesis_limit_spin.setValue( - self._settings.hybrid_synthesis_limit_percent - ) + self.hybrid_synthesis_limit_spin.setValue(self._settings.hybrid_synthesis_limit_percent) self.hybrid_synthesis_limit_spin.setToolTip( "Maximum portion of a frame Hybrid may synthesize. This is a ceiling, not a target; " "the pinned runtime's own ceiling remains in force if it is lower. Set 0% to forbid synthesis." @@ -263,9 +261,7 @@ def _init_ui(self) -> None: layout.addWidget(self.tier_hint) self.hybrid_guidance = QLabel("") - self.hybrid_guidance.setStyleSheet( - f"color: {THEME.text_muted}; font-size: {THEME.font_size_small}px;" - ) + self.hybrid_guidance.setStyleSheet(f"color: {THEME.text_muted}; font-size: {THEME.font_size_small}px;") self.hybrid_guidance.setWordWrap(True) layout.addWidget(self.hybrid_guidance) @@ -432,9 +428,7 @@ def _on_preview_clicked(self) -> None: self._set_status("Reading roll transport…") self.progress_bar.setVisible(True) self.progress_bar.setValue(0) - self.controller.start_coolscan_roll_preview( - RollPreviewRequest(device_id=device_id) - ) + self.controller.start_coolscan_roll_preview(RollPreviewRequest(device_id=device_id)) @pyqtSlot(list) def _on_preview_ready(self, thumbnails: "list[coolscanpy.Thumbnail]") -> None: @@ -608,15 +602,9 @@ def _completion_issues( return [] issues: list[str] = [] completed_slots = {output.slot for output in outputs} - missing_slots = [ - slot for slot in request.slots if slot not in completed_slots - ] + missing_slots = [slot for slot in request.slots if slot not in completed_slots] if missing_slots: - issues.append( - "slot(s) " - + ", ".join(str(slot) for slot in missing_slots) - + " did not complete" - ) + issues.append("slot(s) " + ", ".join(str(slot) for slot in missing_slots) + " did not complete") for output in outputs: prefix = f"slot {output.slot}: " if request.write_unrepaired and not output.rgb_path: @@ -627,17 +615,11 @@ def _completion_issues( if ( repair_was_needed and request.repair_mode == RepairMode.HYBRID.value - and not ( - output.native_synthesis_mask_path - and output.hybrid_receipt_path - ) + and not (output.native_synthesis_mask_path and output.hybrid_receipt_path) ): issues.append(prefix + "Hybrid repair degraded or unavailable") if request.write_positive and not output.positive_path: - if ( - request.positive_mode - == PositiveColorMode.NIKON_EXACT.value - ): + if request.positive_mode == PositiveColorMode.NIKON_EXACT.value: issues.append(prefix + "Nikon exact positive unavailable") else: issues.append(prefix + "requested positive output unavailable") diff --git a/negpy/desktop/workers/roll_worker.py b/negpy/desktop/workers/roll_worker.py index 6e60c2b2..b891d6ad 100644 --- a/negpy/desktop/workers/roll_worker.py +++ b/negpy/desktop/workers/roll_worker.py @@ -51,9 +51,7 @@ class RollBatchScanRequest: write_positive: bool = RollScanSettings.defaults().write_positive repair_mode: str = RollScanSettings.defaults().repair_mode positive_mode: str = RollScanSettings.defaults().positive_mode - hybrid_synthesis_limit_percent: float = ( - RollScanSettings.defaults().hybrid_synthesis_limit_percent - ) + hybrid_synthesis_limit_percent: float = RollScanSettings.defaults().hybrid_synthesis_limit_percent def __post_init__(self) -> None: value = self.hybrid_synthesis_limit_percent @@ -63,9 +61,7 @@ def __post_init__(self) -> None: or not math.isfinite(float(value)) or not 0.0 <= float(value) <= 100.0 ): - raise ValueError( - "hybrid_synthesis_limit_percent must be finite and in [0, 100]" - ) + raise ValueError("hybrid_synthesis_limit_percent must be finite and in [0, 100]") object.__setattr__(self, "hybrid_synthesis_limit_percent", float(value)) @@ -99,9 +95,7 @@ def __init__( def _reject_if_shutting_down(self) -> None: if self._shutdown_requested.is_set(): - raise RollShutdownBlocked( - "scanner teardown has started; no new operation may begin" - ) + raise RollShutdownBlocked("scanner teardown has started; no new operation may begin") # ----- device / roll lifecycle ----- @@ -143,9 +137,7 @@ def close_roll(self) -> bool: self._service.close() except Exception as error: logger.exception("error closing roll") - self.status.emit( - "Scanner close is unresolved; the reservation remains open." - ) + self.status.emit("Scanner close is unresolved; the reservation remains open.") self.error.emit(f"Close roll: {error}") return False else: @@ -290,19 +282,14 @@ def shutdown(self, *, timeout_seconds: float = 5.0) -> None: self.safe_stop() acquired = self._operation_lock.acquire(timeout=timeout_seconds) if not acquired: - message = ( - "the current scanner operation is still stopping; " - "application exit is blocked" - ) + message = "the current scanner operation is still stopping; application exit is blocked" self.status.emit(message) raise RollShutdownBlocked(message) try: self._service.close() except Exception as error: logger.exception("error closing roll on shutdown") - self.status.emit( - "Scanner close is unresolved; application exit is blocked." - ) + self.status.emit("Scanner close is unresolved; application exit is blocked.") self.error.emit(f"Shutdown: {error}") raise else: diff --git a/negpy/infrastructure/roll/fauxice_bridge.py b/negpy/infrastructure/roll/fauxice_bridge.py index 1bcc2849..782abb3d 100644 --- a/negpy/infrastructure/roll/fauxice_bridge.py +++ b/negpy/infrastructure/roll/fauxice_bridge.py @@ -107,36 +107,26 @@ def repair( or not routed_native_mask.flags.c_contiguous ): raise RuntimeError("hybrid disclosure mask geometry is invalid") - if ( - hashlib.sha256(routed_native_mask_png).hexdigest() - != result.hybrid_mask_sha256 - ): + if hashlib.sha256(routed_native_mask_png).hexdigest() != result.hybrid_mask_sha256: raise RuntimeError("hybrid native mask SHA-256 changed") if hashlib.sha256(result.hybrid_receipt).hexdigest() != result.hybrid_receipt_sha256: raise RuntimeError("hybrid receipt SHA-256 changed") - native_mask = np.ascontiguousarray( - routed_native_mask & acquisition.ir_validity - ) + native_mask = np.ascontiguousarray(routed_native_mask & acquisition.ir_validity) native_mask_png = _encode_mask_png(native_mask) storage_mask = acquisition.storage_mask(native_mask) storage_mask_png = _encode_mask_png(storage_mask) mask_fields = { "native_synthesis_mask_png": native_mask_png, - "native_synthesis_mask_sha256": hashlib.sha256( - native_mask_png - ).hexdigest(), + "native_synthesis_mask_sha256": hashlib.sha256(native_mask_png).hexdigest(), "native_synthesis_mask_shape": tuple(native_mask.shape), "routed_native_synthesis_mask_png": routed_native_mask_png, "routed_native_synthesis_mask_sha256": result.hybrid_mask_sha256, - "routed_native_synthesis_mask_shape": tuple( - routed_native_mask.shape - ), + "routed_native_synthesis_mask_shape": tuple(routed_native_mask.shape), "storage_synthesis_mask_png": storage_mask_png, "storage_synthesis_mask_sha256": hashlib.sha256(storage_mask_png).hexdigest(), "storage_synthesis_mask_shape": tuple(storage_mask.shape), "synthesis_mask_transform": acquisition.storage_transform, - "synthesis_fraction": float(np.count_nonzero(native_mask)) - / float(native_mask.size), + "synthesis_fraction": float(np.count_nonzero(native_mask)) / float(native_mask.size), "routing_counts": result.hybrid_routing_counts, "hybrid_receipt": result.hybrid_receipt, "hybrid_receipt_sha256": result.hybrid_receipt_sha256, diff --git a/negpy/services/repair/fauxice_ir_repair.py b/negpy/services/repair/fauxice_ir_repair.py index 659745a2..11ff5cab 100644 --- a/negpy/services/repair/fauxice_ir_repair.py +++ b/negpy/services/repair/fauxice_ir_repair.py @@ -304,9 +304,7 @@ def repair_ir_dust( ) if cancel is not None and cancel.is_set(): - raise FauxiceRepairCancelled( - "cancelled by caller before repair started" - ) + raise FauxiceRepairCancelled("cancelled by caller before repair started") main_rgbi = np.dstack([rgb, ir]) @@ -326,9 +324,7 @@ def repair_ir_dust( detail = "runtime artifacts changed during validation" hybrid_note = f"hybrid mode requested but the configured hybrid runtime is unavailable ({detail}); degraded to exact repair. " elif cancel is not None and cancel.is_set(): - raise FauxiceRepairCancelled( - "cancelled before the hybrid run started" - ) + raise FauxiceRepairCancelled("cancelled before the hybrid run started") else: try: with tempfile.TemporaryDirectory(prefix="negpy-fauxice-hybrid-") as scratch: @@ -345,9 +341,7 @@ def repair_ir_dust( ) mode_resolved = RepairMode.HYBRID except HybridRunCancelled as error: - raise FauxiceRepairCancelled( - f"hybrid repair cancelled: {error}" - ) from error + raise FauxiceRepairCancelled(f"hybrid repair cancelled: {error}") from error except HybridRunError as error: hybrid_note = f"hybrid mode requested but the fauxce-hybrid run failed ({error}); degraded to exact repair. " @@ -391,9 +385,7 @@ def repair_ir_dust( cancel=cancel, ) except ProcessingCancelled as error: - raise FauxiceRepairCancelled( - f"{hybrid_note}cancelled before completion: {error}" - ) from error + raise FauxiceRepairCancelled(f"{hybrid_note}cancelled before completion: {error}") from error except (ValueError, TypeError, RuntimeError) as error: return FauxiceRepairResult( status=RepairStatus.SKIPPED, diff --git a/negpy/services/repair/hybrid_runtime_manifest.py b/negpy/services/repair/hybrid_runtime_manifest.py index 2021a97e..e462a284 100644 --- a/negpy/services/repair/hybrid_runtime_manifest.py +++ b/negpy/services/repair/hybrid_runtime_manifest.py @@ -65,12 +65,7 @@ def _stable_regular_bytes(path: Path, *, maximum_bytes: int, label: str) -> byte raise HybridRuntimeManifestError(f"{label} must be a regular non-symlink file") if linked.st_size < 0 or linked.st_size > maximum_bytes: raise HybridRuntimeManifestError(f"{label} exceeds its safe size limit") - flags = ( - os.O_RDONLY - | getattr(os, "O_CLOEXEC", 0) - | getattr(os, "O_NOFOLLOW", 0) - | getattr(os, "O_NONBLOCK", 0) - ) + flags = os.O_RDONLY | getattr(os, "O_CLOEXEC", 0) | getattr(os, "O_NOFOLLOW", 0) | getattr(os, "O_NONBLOCK", 0) descriptor = os.open(path, flags) with os.fdopen(descriptor, "rb", closefd=True) as handle: descriptor = None diff --git a/negpy/services/roll/exact_color.py b/negpy/services/roll/exact_color.py index a6ea3ae0..3851ad6b 100644 --- a/negpy/services/roll/exact_color.py +++ b/negpy/services/roll/exact_color.py @@ -351,10 +351,7 @@ def load_native_builder_receipt( ownership_path = directory / "nikon-density-frame-ownership.json" density_path = directory / "nikon-density-evidence.json" analyzer_path = directory / "analyzer-rgb-u16le.bin" - lut_paths = tuple( - directory / f"builder-preF-{channel}.bin" - for channel in _BUILDER_CHANNELS - ) + lut_paths = tuple(directory / f"builder-preF-{channel}.bin" for channel in _BUILDER_CHANNELS) paths = ( receipt_path, evidence_path, @@ -420,9 +417,7 @@ def load_native_builder_receipt( envelope = _parse_json_object(receipt_blob, label="native builder receipt") if _canonical_json(envelope) != receipt_blob: - raise ExactColorIntegrityError( - "native builder receipt is not canonical JSON" - ) + raise ExactColorIntegrityError("native builder receipt is not canonical JSON") # Import lazily: native_builder owns the pinned derivation and imports this # boundary for its receipt factory. Re-running that derivation is what # prevents a writable evidence directory from self-attesting new LUTs. @@ -437,9 +432,7 @@ def load_native_builder_receipt( pre_f_luts=(pre_f_r, pre_f_g, pre_f_b), ) if directory.name != rebuilt.sha256: - raise ExactColorIntegrityError( - "native builder evidence is outside its content-addressed directory" - ) + raise ExactColorIntegrityError("native builder evidence is outside its content-addressed directory") return rebuilt @@ -1336,12 +1329,7 @@ def _stable_read_non_symlink( raise ExactColorIntegrityError(f"{label} must be exactly {expected_bytes} bytes") if max_bytes is not None and before.st_size > max_bytes: raise ExactColorIntegrityError(f"{label} exceeds the bounded receipt size") - flags = ( - os.O_RDONLY - | getattr(os, "O_CLOEXEC", 0) - | getattr(os, "O_NOFOLLOW", 0) - | getattr(os, "O_NONBLOCK", 0) - ) + flags = os.O_RDONLY | getattr(os, "O_CLOEXEC", 0) | getattr(os, "O_NOFOLLOW", 0) | getattr(os, "O_NONBLOCK", 0) descriptor = os.open(path, flags) opened = os.fstat(descriptor) chunks: list[bytes] = [] @@ -1356,9 +1344,7 @@ def _stable_read_non_symlink( chunks.append(chunk) total += len(chunk) if total > byte_limit: - raise ExactColorIntegrityError( - f"{label} exceeds its bounded byte length" - ) + raise ExactColorIntegrityError(f"{label} exceeds its bounded byte length") after = os.fstat(descriptor) final = path.lstat() except ExactColorIntegrityError: @@ -1387,13 +1373,9 @@ def _assert_paths_unchanged( try: current = path.lstat() except OSError as error: - raise ExactColorUnavailable( - f"{label} disappeared: {path}: {error}" - ) from error + raise ExactColorUnavailable(f"{label} disappeared: {path}: {error}") from error if stat.S_ISLNK(current.st_mode) or not stat.S_ISREG(current.st_mode) or _file_identity(current) != expected: - raise ExactColorIntegrityError( - f"{label} changed during snapshot: {path}" - ) + raise ExactColorIntegrityError(f"{label} changed during snapshot: {path}") def _file_identity(value: os.stat_result) -> tuple[int, int, int, int, int]: diff --git a/negpy/services/roll/live_evidence.py b/negpy/services/roll/live_evidence.py index 03ffa15e..ab1647b3 100644 --- a/negpy/services/roll/live_evidence.py +++ b/negpy/services/roll/live_evidence.py @@ -257,13 +257,12 @@ def _snapshot_json( ) if hashlib.sha256(payload).hexdigest() != matches[0]["sha256"]: raise InventoryConflict(f"capture evidence changed after hashing: {relative}") + def _reject_duplicate_keys(pairs: list[tuple[str, object]]) -> dict[str, object]: merged: dict[str, object] = {} for key, value in pairs: if key in merged: - raise InventoryConflict( - f"capture evidence JSON repeats key {key!r}: {relative}" - ) + raise InventoryConflict(f"capture evidence JSON repeats key {key!r}: {relative}") merged[key] = value return merged diff --git a/negpy/services/roll/portable_cms.py b/negpy/services/roll/portable_cms.py index 0c77a8f4..ca730292 100644 --- a/negpy/services/roll/portable_cms.py +++ b/negpy/services/roll/portable_cms.py @@ -148,12 +148,9 @@ def __init__( validation_digest = hashlib.sha256(validation_payload).hexdigest() if validation_digest != VALIDATION_RECEIPT_SHA256: raise ExactColorIntegrityError( - "portable CMS validation receipt hash mismatch: " - f"{validation_digest} != {VALIDATION_RECEIPT_SHA256}" + f"portable CMS validation receipt hash mismatch: {validation_digest} != {VALIDATION_RECEIPT_SHA256}" ) - self._validation_summary = MappingProxyType( - _validate_validation_receipt(validation_payload) - ) + self._validation_summary = MappingProxyType(_validate_validation_receipt(validation_payload)) oracle = _load_verified_oracle() payloads = _read_verified_assets(self.assets_dir) self._transforms = _LoadedTransforms(payloads, oracle) @@ -204,12 +201,8 @@ def evaluate( "upstream_builder_included": False, "validation": { "events": self._validation_summary["events"], - "full_payload_mismatched_bytes": self._validation_summary[ - "full_payload_mismatched_bytes" - ], - "full_payload_total_bytes": self._validation_summary[ - "full_payload_total_bytes" - ], + "full_payload_mismatched_bytes": self._validation_summary["full_payload_mismatched_bytes"], + "full_payload_total_bytes": self._validation_summary["full_payload_total_bytes"], "mismatched_u16": self._validation_summary["mismatched_u16"], "receipt_sha256": ORACLE_VALIDATION_RECEIPT_SHA256, "total_u16": self._validation_summary["total_u16"], @@ -286,9 +279,7 @@ def _read_verified_payload( if stat.S_ISLNK(before.st_mode) or not stat.S_ISREG(before.st_mode): raise OSError("not a regular non-symlink file") if expected_bytes is not None and before.st_size != expected_bytes: - raise ExactColorIntegrityError( - f"{label} byte size mismatch: {before.st_size} != {expected_bytes}" - ) + raise ExactColorIntegrityError(f"{label} byte size mismatch: {before.st_size} != {expected_bytes}") flags = os.O_RDONLY | getattr(os, "O_CLOEXEC", 0) | getattr(os, "O_NOFOLLOW", 0) descriptor = os.open(path, flags) opened = os.fstat(descriptor) @@ -320,18 +311,14 @@ def _validate_validation_receipt(payload: bytes) -> dict[str, int]: parse_constant=_reject_json_constant, ) except (UnicodeDecodeError, json.JSONDecodeError, ValueError) as error: - raise ExactColorIntegrityError( - f"portable CMS validation receipt is invalid JSON: {error}" - ) from error + raise ExactColorIntegrityError(f"portable CMS validation receipt is invalid JSON: {error}") from error if type(document) is not dict or set(document) != { "all_12_events", "all_12_full_payloads", "stage1", "stage2", }: - raise ExactColorIntegrityError( - "portable CMS validation receipt top-level schema is incomplete" - ) + raise ExactColorIntegrityError("portable CMS validation receipt top-level schema is incomplete") stage_totals: list[int] = [] stage_full_totals: list[int] = [] @@ -342,14 +329,10 @@ def _validate_validation_receipt(payload: bytes) -> dict[str, int]: "per_event", "total", }: - raise ExactColorIntegrityError( - f"portable CMS validation receipt {stage_name} schema is incomplete" - ) + raise ExactColorIntegrityError(f"portable CMS validation receipt {stage_name} schema is incomplete") per_event = stage.get("per_event") if type(per_event) is not dict or set(per_event) != set(expected_events): - raise ExactColorIntegrityError( - f"portable CMS validation receipt {stage_name} event inventory is incomplete" - ) + raise ExactColorIntegrityError(f"portable CMS validation receipt {stage_name} event inventory is incomplete") event_total = 0 event_full_total = 0 for event, (expected_total, expected_full_total) in expected_events.items(): @@ -361,14 +344,9 @@ def _validate_validation_receipt(payload: bytes) -> dict[str, int]: "mismatched_u16", "total_u16", }: - raise ExactColorIntegrityError( - f"portable CMS validation receipt event {event} schema is incomplete" - ) + raise ExactColorIntegrityError(f"portable CMS validation receipt event {event} schema is incomplete") _require_zero_metrics( - { - key: row[key] - for key in ("mae", "max_abs", "mismatched_u16", "total_u16") - }, + {key: row[key] for key in ("mae", "max_abs", "mismatched_u16", "total_u16")}, expected_total=expected_total, label=f"event {event}", ) @@ -409,9 +387,7 @@ def _validate_validation_receipt(payload: bytes) -> dict[str, int]: "total_bytes", "total_u16", }: - raise ExactColorIntegrityError( - "portable CMS validation receipt full-payload schema is incomplete" - ) + raise ExactColorIntegrityError("portable CMS validation receipt full-payload schema is incomplete") _require_zero_metrics( {key: full_payload[key] for key in ("mae", "max_abs", "mismatched_u16", "total_u16")}, expected_total=full_total_u16, @@ -423,9 +399,7 @@ def _validate_validation_receipt(payload: bytes) -> dict[str, int]: or type(full_payload.get("mismatched_bytes")) is not int or full_payload.get("mismatched_bytes") != 0 ): - raise ExactColorIntegrityError( - "portable CMS validation receipt byte totals are inconsistent" - ) + raise ExactColorIntegrityError("portable CMS validation receipt byte totals are inconsistent") return { "events": sum(len(events) for events in _VALIDATION_EVENTS.values()), "mismatched_u16": 0, @@ -442,9 +416,7 @@ def _require_zero_metrics( label: str, ) -> None: if type(value) is not dict: - raise ExactColorIntegrityError( - f"portable CMS validation receipt {label} is not an exact zero-mismatch result" - ) + raise ExactColorIntegrityError(f"portable CMS validation receipt {label} is not an exact zero-mismatch result") metrics = cast(dict[str, object], value) if ( set(metrics) != {"mae", "max_abs", "mismatched_u16", "total_u16"} @@ -457,9 +429,7 @@ def _require_zero_metrics( or type(metrics.get("mae")) is not float or metrics.get("mae") != 0.0 ): - raise ExactColorIntegrityError( - f"portable CMS validation receipt {label} is not an exact zero-mismatch result" - ) + raise ExactColorIntegrityError(f"portable CMS validation receipt {label} is not an exact zero-mismatch result") def _reject_duplicate_keys(pairs: list[tuple[str, object]]) -> dict[str, object]: diff --git a/tests/roll/conftest.py b/tests/roll/conftest.py index 2c9b9ff2..ffdf88c6 100644 --- a/tests/roll/conftest.py +++ b/tests/roll/conftest.py @@ -128,11 +128,7 @@ def restore_preview_session(self, payload, slots=None): requested = None if slots is None else tuple(slots) self.restore_preview_session_calls.append((payload, requested)) wanted = None if requested is None else set(requested) - return [ - thumbnail - for thumbnail in self._thumbnails - if wanted is None or thumbnail.slot in wanted - ] + return [thumbnail for thumbnail in self._thumbnails if wanted is None or thumbnail.slot in wanted] def set_spacing_offset(self, slot, offset_rows) -> None: self.spacing_offsets[slot] = offset_rows diff --git a/tests/roll/test_coolscanpy_roll.py b/tests/roll/test_coolscanpy_roll.py index 56392345..2949d87f 100644 --- a/tests/roll/test_coolscanpy_roll.py +++ b/tests/roll/test_coolscanpy_roll.py @@ -157,21 +157,13 @@ def test_restore_preview_session_passthrough(self, fake_coolscanpy) -> None: result = handle.restore_preview_session("saved-session", [1, 3]) assert [thumbnail.slot for thumbnail in result] == [1, 3] - assert roll.restore_preview_session_calls == [ - ("saved-session", (1, 3)) - ] + assert roll.restore_preview_session_calls == [("saved-session", (1, 3))] - def test_restore_preview_session_exception_translated( - self, fake_coolscanpy - ) -> None: - error = fake_coolscanpy.module.PyCoolscanError( - "saved preview hash mismatch" - ) + def test_restore_preview_session_exception_translated(self, fake_coolscanpy) -> None: + error = fake_coolscanpy.module.PyCoolscanError("saved preview hash mismatch") handle, _roll, _device = self._handle( fake_coolscanpy, - fake_coolscanpy.Roll( - raise_on={"restore_preview_session": error} - ), + fake_coolscanpy.Roll(raise_on={"restore_preview_session": error}), ) with pytest.raises(RuntimeError, match="saved preview hash mismatch") as excinfo: @@ -179,9 +171,7 @@ def test_restore_preview_session_exception_translated( assert excinfo.value.__cause__ is error - def test_approve_returns_underlying_content_bound_receipt( - self, fake_coolscanpy - ) -> None: + def test_approve_returns_underlying_content_bound_receipt(self, fake_coolscanpy) -> None: approval = object() class ReturningApprovalRoll(fake_coolscanpy.Roll): @@ -239,9 +229,7 @@ def test_close_releases_roll_then_device(self, fake_coolscanpy) -> None: assert roll.closed is True assert device.closed is True - def test_close_retains_device_when_roll_ownership_is_uncertain( - self, fake_coolscanpy - ) -> None: + def test_close_retains_device_when_roll_ownership_is_uncertain(self, fake_coolscanpy) -> None: ownership_error = RuntimeError("USB ownership is retained") class UncertainRoll(fake_coolscanpy.Roll): diff --git a/tests/roll/test_fauxice_bridge.py b/tests/roll/test_fauxice_bridge.py index cbf61e71..81bab3a1 100644 --- a/tests/roll/test_fauxice_bridge.py +++ b/tests/roll/test_fauxice_bridge.py @@ -152,9 +152,7 @@ def fake_repair(rgb, ir, **kwargs): assert result.mode_resolved is roll_repair.RepairMode.HYBRID assert result.degraded is False assert result.routed_native_synthesis_mask_png == native_mask_png - assert result.routed_native_synthesis_mask_sha256 == hashlib.sha256( - native_mask_png - ).hexdigest() + assert result.routed_native_synthesis_mask_sha256 == hashlib.sha256(native_mask_png).hexdigest() with Image.open(io.BytesIO(result.native_synthesis_mask_png)) as image: final_native_mask = np.asarray(image.convert("L")) != 0 np.testing.assert_array_equal(final_native_mask, applied_mask) diff --git a/tests/roll/test_live_acceptance.py b/tests/roll/test_live_acceptance.py index c2ac7d37..038148db 100644 --- a/tests/roll/test_live_acceptance.py +++ b/tests/roll/test_live_acceptance.py @@ -1565,13 +1565,20 @@ def test_parser_requires_attempts_root() -> None: from negpy.services.roll import live_acceptance as module argv = [ - "--device-id", "usb:2:7", - "--preview-session", "session.json", - "--preview-session-sha256", "0" * 64, - "--reviewed-approval", "review.json", - "--reviewed-approval-sha256", "0" * 64, - "--output-dir", "outputs", - "--run-receipt", "receipt.json", + "--device-id", + "usb:2:7", + "--preview-session", + "session.json", + "--preview-session-sha256", + "0" * 64, + "--reviewed-approval", + "review.json", + "--reviewed-approval-sha256", + "0" * 64, + "--output-dir", + "outputs", + "--run-receipt", + "receipt.json", "--confirm-live", ] with pytest.raises(SystemExit): diff --git a/tests/roll/test_live_evidence.py b/tests/roll/test_live_evidence.py index 808af377..a24cee55 100644 --- a/tests/roll/test_live_evidence.py +++ b/tests/roll/test_live_evidence.py @@ -601,9 +601,7 @@ def test_duplicate_json_keys_in_capture_evidence_are_rejected(tmp_path: Path) -> document = json.loads(session_path.read_text(encoding="utf-8")) body = json.dumps(document, sort_keys=True) assert body.startswith("{") - duplicated = ( - '{"batch_job_sha256": "' + "0" * 64 + '", ' + body[1:] - ) + duplicated = '{"batch_job_sha256": "' + "0" * 64 + '", ' + body[1:] assert json.loads(duplicated)["batch_job_sha256"] == document["batch_job_sha256"] session_path.write_text(duplicated, encoding="utf-8") diff --git a/tests/roll/test_live_review.py b/tests/roll/test_live_review.py index 2b338a87..2e16c1df 100644 --- a/tests/roll/test_live_review.py +++ b/tests/roll/test_live_review.py @@ -62,14 +62,8 @@ def _approval(slot: int, thumbnail: str, fingerprint: str) -> dict: def _fixture(tmp_path: Path): fingerprint = "c" * 64 - images = { - slot: np.full((2, 3, 3), slot, dtype=np.uint16) - for slot in range(1, 7) - } - approvals = { - slot: _approval(slot, thumbnail_sha256(images[slot]), fingerprint) - for slot in (1, 6) - } + images = {slot: np.full((2, 3, 3), slot, dtype=np.uint16) for slot in range(1, 7)} + approvals = {slot: _approval(slot, thumbnail_sha256(images[slot]), fingerprint) for slot in (1, 6)} session_payload = '{"version":1}' session_path = tmp_path / "session.json" session_path.write_text(session_payload, encoding="utf-8") diff --git a/tests/roll/test_native_builder.py b/tests/roll/test_native_builder.py index a17bb1ee..d35e5af3 100644 --- a/tests/roll/test_native_builder.py +++ b/tests/roll/test_native_builder.py @@ -748,9 +748,7 @@ def test_noncanonical_dice_binding_withholds_positive_but_keeps_native_color_evi assert "Tier 2" in receipt["positive"]["status"] native = receipt["native_color_evidence"] assert native["retained"] is True - assert Path( - native["retained_builder_evidence"]["builder_receipt"]["path"] - ).is_file() + assert Path(native["retained_builder_evidence"]["builder_receipt"]["path"]).is_file() def test_service_refuses_explicit_builder_evidence_without_current_frame_ownership(fake_repair_engine, tmp_path: Path) -> None: diff --git a/tests/roll/test_nikon_icc.py b/tests/roll/test_nikon_icc.py index ba54fbae..06a8fdf3 100644 --- a/tests/roll/test_nikon_icc.py +++ b/tests/roll/test_nikon_icc.py @@ -21,14 +21,10 @@ def test_embedded_profile_is_the_pinned_nikon_scan_rgb_profile() -> None: assert profile[16:20] == b"RGB " assert profile[20:24] == b"XYZ " assert profile[36:40] == b"acsp" - assert hashlib.sha256(profile).hexdigest() == ( - "a8d0d753bd6129357cc2647435ce675e8637a679eb526fa180fba460874ce1d3" - ) + assert hashlib.sha256(profile).hexdigest() == ("a8d0d753bd6129357cc2647435ce675e8637a679eb526fa180fba460874ce1d3") parsed = ImageCms.ImageCmsProfile(BytesIO(profile)) assert ImageCms.getProfileName(parsed).strip() == "Nikon Adobe RGB 4.0.0.3000" - assert ImageCms.getProfileCopyright(parsed).strip() == ( - "Nikon Inc. & Nikon Corporation 2001" - ) + assert ImageCms.getProfileCopyright(parsed).strip() == ("Nikon Inc. & Nikon Corporation 2001") assert nikon_icc.profile_receipt_binding() == { "bytes": 492, "name": "Nikon Adobe RGB 4.0.0.3000", diff --git a/tests/roll/test_service.py b/tests/roll/test_service.py index 8bcb6eca..ea1cbcbe 100644 --- a/tests/roll/test_service.py +++ b/tests/roll/test_service.py @@ -125,9 +125,7 @@ def approve(self, slot): assert service.approve(1) is approval assert roll.approved == [1] - def test_restore_preview_session_delegates_to_open_handle( - self, fake_coolscanpy - ) -> None: + def test_restore_preview_session_delegates_to_open_handle(self, fake_coolscanpy) -> None: thumbnails = [ fake_coolscanpy.Thumbnail( slot=slot, @@ -146,9 +144,7 @@ def test_restore_preview_session_delegates_to_open_handle( result = service.restore_preview_session("saved-session", [2]) assert [thumbnail.slot for thumbnail in result] == [2] - assert roll.restore_preview_session_calls == [ - ("saved-session", (2,)) - ] + assert roll.restore_preview_session_calls == [("saved-session", (2,))] def test_restore_preview_session_requires_an_open_roll(self) -> None: service = RollScanningService() diff --git a/tests/test_controller.py b/tests/test_controller.py index 9246dedb..7b3f6e0f 100644 --- a/tests/test_controller.py +++ b/tests/test_controller.py @@ -187,9 +187,7 @@ def test_coolscan_roll_preview_uses_its_separate_worker_lane(self): request = RollPreviewRequest(device_id="usb:1:2", slots=(1, 2)) controller = SimpleNamespace( _ensure_roll_thread=lambda: events.append("ensure-coolscan-thread"), - roll_preview_requested=SimpleNamespace( - emit=lambda value: events.append(("coolscan-preview", value)) - ), + roll_preview_requested=SimpleNamespace(emit=lambda value: events.append(("coolscan-preview", value))), ) AppController.start_coolscan_roll_preview(controller, request) diff --git a/tests/test_ls5000_frozen_packaging.py b/tests/test_ls5000_frozen_packaging.py index a69c41cd..d87a1a48 100644 --- a/tests/test_ls5000_frozen_packaging.py +++ b/tests/test_ls5000_frozen_packaging.py @@ -77,9 +77,7 @@ def reject_gui_import(name, *args, **kwargs): "--confirm-live", ] - handled = frozen_entry.dispatch_frozen_auxiliary( - ["NegPy", frozen_entry.LIVE_ACCEPTANCE_FLAG, *arguments] - ) + handled = frozen_entry.dispatch_frozen_auxiliary(["NegPy", frozen_entry.LIVE_ACCEPTANCE_FLAG, *arguments]) assert handled is True assert forwarded == [arguments] @@ -101,9 +99,7 @@ def test_live_acceptance_dispatch_propagates_failure_exit_code( ) with pytest.raises(SystemExit) as stopped: - frozen_entry.dispatch_frozen_auxiliary( - ["NegPy", frozen_entry.LIVE_ACCEPTANCE_FLAG] - ) + frozen_entry.dispatch_frozen_auxiliary(["NegPy", frozen_entry.LIVE_ACCEPTANCE_FLAG]) assert stopped.value.code == 23 @@ -639,9 +635,7 @@ def import_with_stale_device(module_name: str): Device=StaleDevice, DigitalIceAcquisition=module.DigitalIceAcquisition, DigitalIceAcquisitionEvidence=module.DigitalIceAcquisitionEvidence, - build_digital_ice_acquisition_evidence=( - module.build_digital_ice_acquisition_evidence - ), + build_digital_ice_acquisition_evidence=(module.build_digital_ice_acquisition_evidence), ) monkeypatch.setattr(build.importlib, "import_module", import_with_stale_device) @@ -855,9 +849,7 @@ class LoadedLibusb: monkeypatch.setattr( build.subprocess, "run", - lambda *_args, **_kwargs: (_ for _ in ()).throw( - AssertionError("live acceptance reached subprocess") - ), + lambda *_args, **_kwargs: (_ for _ in ()).throw(AssertionError("live acceptance reached subprocess")), ) with pytest.raises(RuntimeError, match="attempted to enable live acceptance"): diff --git a/tests/test_main_window.py b/tests/test_main_window.py index f40f477c..cd2dd711 100644 --- a/tests/test_main_window.py +++ b/tests/test_main_window.py @@ -74,14 +74,10 @@ def test_unresolved_scanner_close_ignores_window_close(self): raise_=MagicMock(), activateWindow=MagicMock(), ) - stub.request_shutdown_for_exit = lambda: MainWindow.request_shutdown_for_exit( - stub - ) + stub.request_shutdown_for_exit = lambda: MainWindow.request_shutdown_for_exit(stub) event = MagicMock() - with patch( - "negpy.desktop.view.main_window.QMessageBox.critical" - ) as critical: + with patch("negpy.desktop.view.main_window.QMessageBox.critical") as critical: MainWindow.closeEvent(stub, event) event.ignore.assert_called_once_with() diff --git a/tests/test_wheel_packaging.py b/tests/test_wheel_packaging.py index 0d5f27aa..90e66715 100644 --- a/tests/test_wheel_packaging.py +++ b/tests/test_wheel_packaging.py @@ -72,10 +72,7 @@ def test_wheel_contains_and_loads_all_negpy_packages_and_portable_assets(tmp_pat packaged = set(archive.namelist()) packaged_python = {name for name in packaged if name.startswith("negpy/") and name.endswith(".py")} packaged_assets = { - name - for name in packaged - if name.startswith("negpy/assets/portable_") - and Path(name).suffix in {".bin", ".json"} + name for name in packaged if name.startswith("negpy/assets/portable_") and Path(name).suffix in {".bin", ".json"} } assert packaged_python == expected_python assert ROLL_MODULES <= packaged @@ -142,10 +139,7 @@ def test_wheel_contains_and_loads_all_negpy_packages_and_portable_assets(tmp_pat env.pop("PYTHONPATH", None) _run([str(venv_python), "-c", smoke, str(venv_site)], cwd=tmp_path, env=env) - validation_path = ( - venv_site - / "negpy/assets/portable_cms/portable-oracle-receipt.json" - ) + validation_path = venv_site / "negpy/assets/portable_cms/portable-oracle-receipt.json" validation_payload = validation_path.read_bytes() for failure, expected in ( ("missing", "portable CMS validation receipt is unavailable"), From 53617530eec01b2f2f6d5a4469d5bc4b87272103 Mon Sep 17 00:00:00 2001 From: Rohan Pandula Date: Wed, 22 Jul 2026 23:45:16 -0700 Subject: [PATCH 32/48] build(repair): pin Digital FauxIce 0.3.1 --- pyproject.toml | 4 ++-- uv.lock | 18 +++++++++--------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index e47f3ec4..82f66dde 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -63,10 +63,10 @@ coolscan-roll = [ "coolscanpy @ git+https://github.com/rohanpandula/coolscanpy.git@e42c68fb53b74aee9fc29eae432d092de351001d", ] fauxice = [ - "portable-digital-ice @ https://github.com/rohanpandula/digital-fauxice/releases/download/v0.3.0/portable_digital_ice-0.3.0-py3-none-any.whl", + "portable-digital-ice @ https://github.com/rohanpandula/digital-fauxice/releases/download/v0.3.1/portable_digital_ice-0.3.1-py3-none-any.whl", ] fauxice-hybrid = [ - "fauxce-hybrid @ https://github.com/rohanpandula/digital-fauxice/releases/download/v0.3.0/fauxce_hybrid-0.3.0-py3-none-any.whl", + "fauxce-hybrid @ https://github.com/rohanpandula/digital-fauxice/releases/download/v0.3.1/fauxce_hybrid-0.3.1-py3-none-any.whl", ] [project.urls] diff --git a/uv.lock b/uv.lock index 62a54370..28390ade 100644 --- a/uv.lock +++ b/uv.lock @@ -158,8 +158,8 @@ wheels = [ [[package]] name = "fauxce-hybrid" -version = "0.3.0" -source = { url = "https://github.com/rohanpandula/digital-fauxice/releases/download/v0.3.0/fauxce_hybrid-0.3.0-py3-none-any.whl" } +version = "0.3.1" +source = { url = "https://github.com/rohanpandula/digital-fauxice/releases/download/v0.3.1/fauxce_hybrid-0.3.1-py3-none-any.whl" } dependencies = [ { name = "imageio" }, { name = "jsonschema" }, @@ -168,7 +168,7 @@ dependencies = [ { name = "scipy" }, ] wheels = [ - { url = "https://github.com/rohanpandula/digital-fauxice/releases/download/v0.3.0/fauxce_hybrid-0.3.0-py3-none-any.whl", hash = "sha256:c732bbfd64883eec52f57a15cfb0f2d1cb71981142e977ca653b7e0471d2371c" }, + { url = "https://github.com/rohanpandula/digital-fauxice/releases/download/v0.3.1/fauxce_hybrid-0.3.1-py3-none-any.whl", hash = "sha256:f36c66d8e4e2d2a2b32caf79bc35241aaf92b0453781f713ffd5e5345ae3032f" }, ] [package.metadata] @@ -176,7 +176,7 @@ requires-dist = [ { name = "imageio", specifier = ">=2.37,<3" }, { name = "jsonschema", specifier = ">=4.25,<5" }, { name = "numpy", specifier = ">=2.4,<3" }, - { name = "portable-digital-ice", extras = ["fast"], specifier = "==0.3.0" }, + { name = "portable-digital-ice", extras = ["fast"], specifier = "==0.3.1" }, { name = "pytest", marker = "extra == 'test'", specifier = ">=9,<10" }, { name = "ruff", marker = "extra == 'test'", specifier = ">=0.11" }, { name = "scipy", specifier = ">=1.16,<2" }, @@ -440,8 +440,8 @@ dev = [ { name = "ruff", specifier = "==0.14.10" }, { name = "ty", specifier = ">=0.0.26" }, ] -fauxice = [{ name = "portable-digital-ice", url = "https://github.com/rohanpandula/digital-fauxice/releases/download/v0.3.0/portable_digital_ice-0.3.0-py3-none-any.whl" }] -fauxice-hybrid = [{ name = "fauxce-hybrid", url = "https://github.com/rohanpandula/digital-fauxice/releases/download/v0.3.0/fauxce_hybrid-0.3.0-py3-none-any.whl" }] +fauxice = [{ name = "portable-digital-ice", url = "https://github.com/rohanpandula/digital-fauxice/releases/download/v0.3.1/portable_digital_ice-0.3.1-py3-none-any.whl" }] +fauxice-hybrid = [{ name = "fauxce-hybrid", url = "https://github.com/rohanpandula/digital-fauxice/releases/download/v0.3.1/fauxce_hybrid-0.3.1-py3-none-any.whl" }] scanner = [{ name = "python-sane", specifier = ">=2.9" }] [[package]] @@ -632,13 +632,13 @@ wheels = [ [[package]] name = "portable-digital-ice" -version = "0.3.0" -source = { url = "https://github.com/rohanpandula/digital-fauxice/releases/download/v0.3.0/portable_digital_ice-0.3.0-py3-none-any.whl" } +version = "0.3.1" +source = { url = "https://github.com/rohanpandula/digital-fauxice/releases/download/v0.3.1/portable_digital_ice-0.3.1-py3-none-any.whl" } dependencies = [ { name = "numpy" }, ] wheels = [ - { url = "https://github.com/rohanpandula/digital-fauxice/releases/download/v0.3.0/portable_digital_ice-0.3.0-py3-none-any.whl", hash = "sha256:d3cb4145a66f28a7eb795aac112a638cc9a2c11b2000907508b2490d84c8b5bb" }, + { url = "https://github.com/rohanpandula/digital-fauxice/releases/download/v0.3.1/portable_digital_ice-0.3.1-py3-none-any.whl", hash = "sha256:54fc0cfd42456abdc91a33a5530b5a615a6ec05a98577d195f2b9d8d080bcfa4" }, ] [package.optional-dependencies] From 1f773e9cdcb48bdca4ebd7741201bbd2480eb334 Mon Sep 17 00:00:00 2001 From: Rohan Pandula Date: Thu, 23 Jul 2026 01:57:19 -0700 Subject: [PATCH 33/48] feat(scanner): add safe full-roll eject control --- docs/COOLSCANPY_ROLL_SCANNING.md | 28 +++- negpy/desktop/controller.py | 12 ++ negpy/desktop/view/sidebar/coolscan_roll.py | 110 +++++++++++++- negpy/desktop/workers/roll_worker.py | 22 +++ negpy/infrastructure/roll/coolscanpy_roll.py | 8 + negpy/infrastructure/scanners/sane_backend.py | 45 +++++- negpy/services/roll/service.py | 7 + tests/roll/test_sidebar.py | 141 +++++++++++++++++- tests/roll/test_worker.py | 74 +++++++++ tests/scanners/test_sane_session.py | 1 + tests/scanners/test_scanner_eject.py | 31 ++++ 11 files changed, 465 insertions(+), 14 deletions(-) diff --git a/docs/COOLSCANPY_ROLL_SCANNING.md b/docs/COOLSCANPY_ROLL_SCANNING.md index 1fc95c2e..b7623303 100644 --- a/docs/COOLSCANPY_ROLL_SCANNING.md +++ b/docs/COOLSCANPY_ROLL_SCANNING.md @@ -470,7 +470,18 @@ missing gphoto2. The panel covers device selection, a whole-roll preview rendered as a thumbnail contact sheet, a spacing-offset spinner and an approve button for whichever slot is selected, and a batch scan of every selected slot -with a progress bar and a Safe Stop button. +with a progress bar and a Safe Stop button. Select All Frames makes a +full-roll batch explicit instead of requiring every thumbnail to be +clicked individually. + +Eject Roll uses the same proven SANE `scanimage --eject` transport action +as NegPy's ordinary scanner panel. It first closes the coolscanpy roll +reservation so the direct SANE command never competes for the scanner. +Successful ejection discards the contact sheet because the film's +registration is no longer valid. If the command returns an error, the +panel deliberately disables preview and further eject attempts for the +rest of that app session: the physical result is uncertain and a blind +retry could move film twice. The Output section has three checkboxes, one per tier, plus a repair-mode dropdown that governs Tier 2. Any combination of the three checkboxes is @@ -491,13 +502,14 @@ actually does. The panel keeps a roll reservation open for the life of the app session, including across a tab switch. It closes the current reservation only when the user picks a different device, opening the new one in its place. -There is no separate close button. This differs from the camera route, -where the tethered session is deliberately released once neither the -live-view window nor the calibration window is open, because some camera -bodies get stuck in a tethered-capture state if a session is left open -past the window that used it. coolscanpy's roll reservation has no -equivalent failure mode, so there is nothing to protect against by -closing it early. +Eject Roll also closes it immediately before handing transport control to +SANE. There is no separate close-without-eject button. This differs from +the camera route, where the tethered session is deliberately released +once neither the live-view window nor the calibration window is open, +because some camera bodies get stuck in a tethered-capture state if a +session is left open past the window that used it. coolscanpy's roll +reservation has no equivalent failure mode, so there is nothing to +protect against by closing it early. The panel does not yet expose a material picker and opens a roll as color negative. Coolscanpy's B&W batch route is implemented and covered offline, diff --git a/negpy/desktop/controller.py b/negpy/desktop/controller.py index 7a770627..24e3e21d 100644 --- a/negpy/desktop/controller.py +++ b/negpy/desktop/controller.py @@ -267,6 +267,9 @@ class AppController(QObject): roll_frame_written = pyqtSignal(object) # RollFrameOutput roll_finished = pyqtSignal(list) # list[RollFrameOutput] roll_cancelled = pyqtSignal() + roll_eject_requested = pyqtSignal(str) + roll_ejected = pyqtSignal(bool) + roll_eject_error = pyqtSignal(str) roll_error = pyqtSignal(str) roll_status = pyqtSignal(str) @@ -586,6 +589,9 @@ def _connect_signals(self) -> None: self.roll_worker.frame_written.connect(self.roll_frame_written.emit) self.roll_worker.finished.connect(self._on_roll_scan_finished) self.roll_worker.cancelled.connect(self.roll_cancelled.emit) + self.roll_eject_requested.connect(self.roll_worker.eject) + self.roll_worker.ejected.connect(self.roll_ejected.emit) + self.roll_worker.eject_error.connect(self.roll_eject_error.emit) self.roll_worker.error.connect(self.roll_error.emit) self.roll_worker.status.connect(self.roll_status.emit) @@ -2354,6 +2360,12 @@ def start_roll_scan(self, req: RollBatchScanRequest) -> None: def roll_safe_stop(self) -> None: self.roll_worker.safe_stop() + def eject_roll(self, device_id: str) -> None: + """Release and eject the selected Coolscan roll on its worker thread.""" + + self._ensure_roll_thread() + self.roll_eject_requested.emit(device_id) + @property def shutdown_block_reason(self) -> str | None: return self._shutdown_block_reason diff --git a/negpy/desktop/view/sidebar/coolscan_roll.py b/negpy/desktop/view/sidebar/coolscan_roll.py index edd1d372..2416712b 100644 --- a/negpy/desktop/view/sidebar/coolscan_roll.py +++ b/negpy/desktop/view/sidebar/coolscan_roll.py @@ -30,6 +30,7 @@ QLineEdit, QListWidget, QListWidgetItem, + QMessageBox, QProgressBar, QPushButton, QDoubleSpinBox, @@ -90,6 +91,10 @@ def __init__(self, controller) -> None: self._thumbnails: dict[int, "coolscanpy.Thumbnail"] = {} self._scanning = False self._stopping = False # safe-stop acknowledged, waiting for the in-flight frame + self._preview_pending = False + self._eject_pending = False + self._eject_latched = False + self._eject_failed = False self._active_scan_request: RollBatchScanRequest | None = None self._init_ui() @@ -164,8 +169,13 @@ def _init_ui(self) -> None: self.refresh_btn.setIcon(qta.icon("fa5s.redo", color=THEME.text_secondary)) self.refresh_btn.setToolTip("Refresh device list") self.refresh_btn.setFixedWidth(32) + self.eject_btn = QPushButton(qta.icon("fa5s.eject", color=THEME.text_secondary), " Eject Roll…") + self.eject_btn.setToolTip( + "Release the scanner reservation and eject the loaded roll. This invalidates the current preview and frame registration." + ) device_row.addWidget(self.device_combo, 1) device_row.addWidget(self.refresh_btn) + device_row.addWidget(self.eject_btn) layout.addLayout(device_row) # ── OUTPUT ────────────────────────────────────────── @@ -287,6 +297,14 @@ def _init_ui(self) -> None: self.contact_sheet.setToolTip("Click a slot to nudge its spacing or approve it; select several, then Scan Selected.") layout.addWidget(self.contact_sheet) + selection_row = QHBoxLayout() + self.select_all_btn = QPushButton("Select All Frames") + self.select_all_btn.setToolTip("Select every frame found by the current roll preview") + self.clear_selection_btn = QPushButton("Clear Selection") + selection_row.addWidget(self.select_all_btn) + selection_row.addWidget(self.clear_selection_btn) + layout.addLayout(selection_row) + slot_row = QFormLayout() slot_row.setSpacing(6) self.slot_label = QLabel("—") @@ -324,6 +342,7 @@ def _init_ui(self) -> None: def _connect_signals(self) -> None: self.refresh_btn.clicked.connect(self._on_refresh) + self.eject_btn.clicked.connect(self._on_eject_clicked) self.device_combo.currentIndexChanged.connect(self._on_device_changed) self.folder_browse.clicked.connect(self._on_browse_folder) for w in (self.folder_edit, self.pattern_edit): @@ -335,6 +354,8 @@ def _connect_signals(self) -> None: self.hybrid_synthesis_limit_spin.valueChanged.connect(self._update_settings_from_ui) self.preview_btn.clicked.connect(self._on_preview_clicked) self.contact_sheet.itemSelectionChanged.connect(self._on_selection_changed) + self.select_all_btn.clicked.connect(self.contact_sheet.selectAll) + self.clear_selection_btn.clicked.connect(self.contact_sheet.clearSelection) self.offset_apply_btn.clicked.connect(self._on_apply_offset) self.approve_btn.clicked.connect(self._on_approve) self.scan_btn.clicked.connect(self._on_scan_clicked) @@ -349,6 +370,8 @@ def _connect_signals(self) -> None: self.controller.roll_frame_written.connect(self._on_frame_written) self.controller.roll_finished.connect(self._on_finished) self.controller.roll_cancelled.connect(self._on_cancelled) + self.controller.roll_ejected.connect(self._on_ejected) + self.controller.roll_eject_error.connect(self._on_eject_error) self.controller.roll_error.connect(self._on_error) self.controller.roll_status.connect(self._on_status) @@ -375,6 +398,28 @@ def _on_refresh(self) -> None: self._devices_loaded = False self._request_devices() + def _on_eject_clicked(self) -> None: + device_id = self._current_device_id() + if not device_id or self._scanning or self._preview_pending or self._eject_pending or self._eject_latched or self._eject_failed: + return + answer = QMessageBox.question( + self, + "Eject roll?", + "Eject the loaded roll?\n\n" + "The current preview and frame registration will be discarded. " + "If eject reports an uncertain outcome, do not press it again.", + QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No, + QMessageBox.StandardButton.No, + ) + if answer != QMessageBox.StandardButton.Yes: + return + self._eject_pending = True + self._eject_latched = True + self._clear_contact_sheet() + self._set_status("Ejecting roll…") + self._apply_gating() + self.controller.eject_roll(device_id) + @pyqtSlot(list) def _on_devices_ready(self, devices: "list[coolscanpy.DeviceInfo]") -> None: self._devices = devices @@ -420,19 +465,28 @@ def _clear_contact_sheet(self) -> None: def _on_preview_clicked(self) -> None: device_id = self._current_device_id() - if not device_id: + if not device_id or self._scanning or self._preview_pending or self._eject_pending or self._eject_failed: return from negpy.desktop.workers.roll_worker import RollPreviewRequest + self._preview_pending = True self._clear_contact_sheet() self._set_status("Reading roll transport…") self.progress_bar.setVisible(True) self.progress_bar.setValue(0) + self._apply_gating() self.controller.start_coolscan_roll_preview(RollPreviewRequest(device_id=device_id)) @pyqtSlot(list) def _on_preview_ready(self, thumbnails: "list[coolscanpy.Thumbnail]") -> None: self.progress_bar.setVisible(False) + if self._eject_pending or self._eject_failed or (self._eject_latched and not self._preview_pending): + self._preview_pending = False + self._clear_contact_sheet() + self._apply_gating() + return + self._preview_pending = False + self._eject_latched = False self._thumbnails = {t.slot: t for t in thumbnails} self.contact_sheet.clear() for t in sorted(self._thumbnails): @@ -519,7 +573,17 @@ def _on_scan_clicked(self) -> None: device_id = self._current_device_id() slots = self._selected_slots() output_folder = self.folder_edit.text().strip() - if not device_id or not slots or not output_folder or not self._any_tier_selected(): + if ( + not device_id + or not slots + or not output_folder + or not self._any_tier_selected() + or self._scanning + or self._preview_pending + or self._eject_pending + or self._eject_latched + or self._eject_failed + ): return from negpy.desktop.workers.roll_worker import RollBatchScanRequest @@ -575,9 +639,31 @@ def _on_cancelled(self) -> None: self.set_scanning(False) self._set_status("Stopped.") + @pyqtSlot(bool) + def _on_ejected(self, triggered: bool) -> None: + self._eject_pending = False + self._eject_latched = True + self._clear_contact_sheet() + if triggered: + self._set_status("Eject started. Wait until the roll is fully out; preview registration cleared.") + else: + self._eject_failed = True + self._set_status("This device reported no eject action. Check the film physically; do not retry in this session.") + self._apply_gating() + + @pyqtSlot(str) + def _on_eject_error(self, message: str) -> None: + self._eject_pending = False + self._eject_latched = True + self._eject_failed = True + self._clear_contact_sheet() + self._set_status(f"Eject outcome uncertain: {message}. Check the film physically; do not retry in this session.") + self._apply_gating() + @pyqtSlot(str) def _on_error(self, msg: str) -> None: self._active_scan_request = None + self._preview_pending = False self.set_scanning(False) self.progress_bar.setVisible(False) self._set_status(f"Error: {msg}") @@ -641,6 +727,8 @@ def _missing_for_preview(self) -> list[str]: m.append("install coolscanpy") if not self._current_device_id(): m.append("select a device") + if self._eject_failed: + m.append("restart after physically checking the uncertain eject") return m def _any_tier_selected(self) -> bool: @@ -688,9 +776,23 @@ def _update_hybrid_guidance(self) -> None: def _apply_gating(self) -> None: missing_preview = self._missing_for_preview() missing_scan = self._missing_for_scan() - self.preview_btn.setEnabled(not missing_preview and not self._scanning) - self.scan_btn.setEnabled(not missing_scan and not self._scanning) + registration_locked = self._preview_pending or self._eject_pending or self._eject_latched or self._eject_failed + self.preview_btn.setEnabled(not missing_preview and not self._scanning and not self._preview_pending and not self._eject_pending) + self.scan_btn.setEnabled(not missing_scan and not self._scanning and not registration_locked) self.safe_stop_btn.setEnabled(self._scanning) + self.device_combo.setEnabled(not self._scanning and not self._preview_pending and not self._eject_pending) + self.refresh_btn.setEnabled(not self._scanning and not self._preview_pending and not self._eject_pending) + self.contact_sheet.setEnabled(not self._scanning and not registration_locked) + self.select_all_btn.setEnabled(bool(self._thumbnails) and not self._scanning and not registration_locked) + self.clear_selection_btn.setEnabled(bool(self._selected_slots()) and not self._scanning and not registration_locked) + self.eject_btn.setEnabled( + bool(self._current_device_id()) + and not self._scanning + and not self._preview_pending + and not self._eject_pending + and not self._eject_latched + and not self._eject_failed + ) self._update_tier_hint() self._update_hybrid_guidance() if missing_scan: diff --git a/negpy/desktop/workers/roll_worker.py b/negpy/desktop/workers/roll_worker.py index b891d6ad..627fb1a5 100644 --- a/negpy/desktop/workers/roll_worker.py +++ b/negpy/desktop/workers/roll_worker.py @@ -78,6 +78,8 @@ class RollWorker(QObject): frame_written = pyqtSignal(object) # RollFrameOutput finished = pyqtSignal(list) # list[RollFrameOutput] written this batch cancelled = pyqtSignal() + ejected = pyqtSignal(bool) + eject_error = pyqtSignal(str) error = pyqtSignal(str) status = pyqtSignal(str) @@ -145,6 +147,26 @@ def close_roll(self) -> bool: self.closed.emit() return True + @pyqtSlot(str) + def eject(self, device_id: str) -> None: + """Release the roll reservation, then issue one intentional eject.""" + + try: + with self._operation_lock: + self._reject_if_shutting_down() + if self._open_device_id is not None and self._open_device_id != device_id: + raise RuntimeError(f"open roll belongs to {self._open_device_id!r}, not requested device {device_id!r}") + if self._open_device_id is not None: + self._service.close() + self._open_device_id = None + self.closed.emit() + self.status.emit("Ejecting roll…") + triggered = self._service.eject(device_id) + self.ejected.emit(triggered) + except Exception as error: + logger.exception("roll eject failed") + self.eject_error.emit(str(error)) + # ----- preview / approval ----- @pyqtSlot(RollPreviewRequest) diff --git a/negpy/infrastructure/roll/coolscanpy_roll.py b/negpy/infrastructure/roll/coolscanpy_roll.py index 95bf77cd..528510b4 100644 --- a/negpy/infrastructure/roll/coolscanpy_roll.py +++ b/negpy/infrastructure/roll/coolscanpy_roll.py @@ -154,6 +154,14 @@ def list_devices() -> "list[coolscanpy.DeviceInfo]": return coolscanpy.get_devices() +def eject(device_id: str) -> bool: + """Eject one direct-USB roll after its coolscanpy reservation is closed.""" + + from negpy.infrastructure.scanners.sane_backend import scanimage_eject_direct_usb + + return scanimage_eject_direct_usb(device_id) + + def open_roll( device_id: str | None = None, *, diff --git a/negpy/infrastructure/scanners/sane_backend.py b/negpy/infrastructure/scanners/sane_backend.py index 53b1284a..0ab0aac3 100644 --- a/negpy/infrastructure/scanners/sane_backend.py +++ b/negpy/infrastructure/scanners/sane_backend.py @@ -1,7 +1,10 @@ import math +import re +import shutil import subprocess import sys import threading +from pathlib import Path from typing import Callable import numpy as np @@ -56,6 +59,12 @@ # "out of documents"; the eject has already fired, so that exit is expected. _EJECT_TIMEOUT_S = 30.0 _EJECT_BENIGN_STDERR_MARKERS = ("out of documents", "no documents", "no more documents") +_SCANIMAGE_FALLBACK_PATHS = ( + "/opt/homebrew/bin/scanimage", + "/usr/local/bin/scanimage", + "/usr/bin/scanimage", +) +_DIRECT_USB_DEVICE_ID = re.compile(r"usb:(?P[1-9][0-9]*):(?P

[1-9][0-9]*)\Z") # Standard SANE option-unit enum values. Kept local so capability detection # does not require importing the optional python-sane extension. @@ -314,6 +323,18 @@ def _find_eject_option(opt) -> str | None: return None +def _scanimage_executable() -> str: + """Resolve scanimage even when a frozen Finder app has a minimal PATH.""" + + discovered = shutil.which("scanimage") + if discovered: + return discovered + for candidate in _SCANIMAGE_FALLBACK_PATHS: + if Path(candidate).is_file(): + return candidate + raise RuntimeError("Cannot eject: `scanimage` (sane-utils) is not installed") + + def _scanimage_eject(device_id: str) -> None: """Press the vendor eject button via `scanimage --eject`. @@ -325,7 +346,7 @@ def _scanimage_eject(device_id: str) -> None: """ try: proc = subprocess.run( - ["scanimage", "-d", device_id, "--eject"], + [_scanimage_executable(), "-d", device_id, "--eject"], capture_output=True, text=True, timeout=_EJECT_TIMEOUT_S, @@ -344,6 +365,28 @@ def _scanimage_eject(device_id: str) -> None: raise RuntimeError(f"scanimage --eject failed for {device_id!r}: {detail}") +def scanimage_eject_direct_usb(device_id: str) -> bool: + """Eject a direct-USB Coolscan device through its exact SANE topology. + + The accepted roll path identifies the scanner as ``usb:BUS:ADDRESS`` + because it captures directly over USB. The proven eject path is + coolscan3's ``scanimage --eject`` action, whose device name carries the + same bus and address as zero-padded decimal fields. Reject every other + spelling so an Eject button can never drift onto a different scanner. + """ + + match = _DIRECT_USB_DEVICE_ID.fullmatch(device_id) + if match is None: + raise ValueError(f"direct Coolscan eject requires usb:BUS:ADDRESS, got {device_id!r}") + bus = int(match.group("bus")) + address = int(match.group("address")) + if bus > 999 or address > 999: + raise ValueError(f"direct Coolscan eject topology is out of range: {device_id!r}") + sane_id = f"coolscan3:usb:libusb:{bus:03d}:{address:03d}" + _scanimage_eject(sane_id) + return True + + def _sane_container_depth(requested_depth: int) -> int: """The container SANE ships `requested_depth` samples in — 8 or 16 bits. diff --git a/negpy/services/roll/service.py b/negpy/services/roll/service.py index b025492a..01dfaee5 100644 --- a/negpy/services/roll/service.py +++ b/negpy/services/roll/service.py @@ -791,6 +791,13 @@ def close(self) -> None: self._roll.close() self._roll = None + def eject(self, device_id: str) -> bool: + """Eject a released direct-USB roll through the proven SANE action.""" + + if self._roll is not None: + raise RollScanningError("close the roll reservation before ejecting") + return coolscanpy_roll.eject(device_id) + def __enter__(self) -> "RollScanningService": return self diff --git a/tests/roll/test_sidebar.py b/tests/roll/test_sidebar.py index 17d42bff..194ce175 100644 --- a/tests/roll/test_sidebar.py +++ b/tests/roll/test_sidebar.py @@ -14,7 +14,7 @@ from PyQt6.QtWidgets import QApplication from unittest.mock import MagicMock -from negpy.desktop.view.sidebar.coolscan_roll import CoolscanRollSidebar +from negpy.desktop.view.sidebar.coolscan_roll import CoolscanRollSidebar, QMessageBox from negpy.infrastructure.roll import coolscanpy_roll if not QApplication.instance(): @@ -50,6 +50,9 @@ def test_sidebar_builds_with_all_controls(self) -> None: "preview_btn", "scan_btn", "safe_stop_btn", + "eject_btn", + "select_all_btn", + "clear_selection_btn", "device_combo", "refresh_btn", "folder_edit", @@ -111,6 +114,8 @@ def test_preview_click_starts_request_for_selected_device(self, fake_coolscanpy) req = w.controller.start_coolscan_roll_preview.call_args[0][0] assert isinstance(req, RollPreviewRequest) assert req.device_id == "dev-42" + assert w._preview_pending is True + assert w.eject_btn.isEnabled() is False def test_preview_click_without_a_device_is_a_noop(self) -> None: w = _sidebar() @@ -161,6 +166,140 @@ def test_new_preview_clears_the_old_contact_sheet(self, fake_coolscanpy) -> None assert w.contact_sheet.count() == 1 assert list(w._thumbnails.keys()) == [9] + def test_select_all_frames_prepares_a_full_roll_selection( + self, + fake_coolscanpy, + tmp_path, + ) -> None: + w = _sidebar() + _pick_device(w) + w.folder_edit.setText(str(tmp_path)) + w._on_preview_ready([_thumb(fake_coolscanpy, slot) for slot in range(1, 37)]) + + w.select_all_btn.click() + + assert w._selected_slots() == list(range(1, 37)) + assert w.scan_btn.isEnabled() is True + assert w.clear_selection_btn.isEnabled() is True + + +class TestEject: + def test_preview_in_flight_blocks_eject(self, fake_coolscanpy, monkeypatch) -> None: + w = _sidebar() + _pick_device(w, device_id="usb:2:7") + w._on_preview_clicked() + monkeypatch.setattr( + QMessageBox, + "question", + lambda *args, **kwargs: QMessageBox.StandardButton.Yes, + ) + + w._on_eject_clicked() + + w.controller.eject_roll.assert_not_called() + assert w._preview_pending is True + assert w.eject_btn.isEnabled() is False + + def test_confirmation_ejects_selected_direct_usb_device( + self, + fake_coolscanpy, + monkeypatch, + ) -> None: + w = _sidebar() + _pick_device(w, device_id="usb:2:7") + w._on_preview_ready([_thumb(fake_coolscanpy, 1)]) + monkeypatch.setattr( + QMessageBox, + "question", + lambda *args, **kwargs: QMessageBox.StandardButton.Yes, + ) + + w._on_eject_clicked() + + w.controller.eject_roll.assert_called_once_with("usb:2:7") + assert w.contact_sheet.count() == 0 + assert w.eject_btn.isEnabled() is False + assert w.preview_btn.isEnabled() is False + assert "Ejecting" in w.status_label.text() + + def test_confirmation_cancel_does_not_eject(self, monkeypatch) -> None: + w = _sidebar() + _pick_device(w, device_id="usb:2:7") + monkeypatch.setattr( + QMessageBox, + "question", + lambda *args, **kwargs: QMessageBox.StandardButton.No, + ) + + w._on_eject_clicked() + + w.controller.eject_roll.assert_not_called() + assert w.eject_btn.isEnabled() is True + + def test_success_clears_registration_and_latches_against_second_eject( + self, + fake_coolscanpy, + ) -> None: + w = _sidebar() + _pick_device(w, device_id="usb:2:7") + w._on_preview_ready([_thumb(fake_coolscanpy, 1)]) + w._eject_pending = True + + w._on_ejected(True) + + assert w.contact_sheet.count() == 0 + assert w.eject_btn.isEnabled() is False + assert "Eject started" in w.status_label.text() + + def test_late_preview_after_eject_cannot_restore_registration( + self, + fake_coolscanpy, + tmp_path, + ) -> None: + w = _sidebar() + _pick_device(w, device_id="usb:2:7") + w.folder_edit.setText(str(tmp_path)) + w._eject_latched = True + + w._on_preview_ready([_thumb(fake_coolscanpy, 1)]) + w.contact_sheet.selectAll() + w._on_scan_clicked() + + assert w.contact_sheet.count() == 0 + assert w._thumbnails == {} + assert w.select_all_btn.isEnabled() is False + assert w.scan_btn.isEnabled() is False + w.controller.start_roll_scan.assert_not_called() + + def test_fresh_preview_after_refeed_reestablishes_registration( + self, + fake_coolscanpy, + ) -> None: + w = _sidebar() + _pick_device(w, device_id="usb:2:7") + w._eject_latched = True + w._apply_gating() + + w._on_preview_clicked() + w._on_preview_ready([_thumb(fake_coolscanpy, 1)]) + + assert w._preview_pending is False + assert w._eject_latched is False + assert w.contact_sheet.count() == 1 + assert w.eject_btn.isEnabled() is True + + def test_uncertain_failure_blocks_preview_and_retry(self) -> None: + w = _sidebar() + _pick_device(w, device_id="usb:2:7") + w._eject_pending = True + + w._on_eject_error("transport status unknown") + + assert w.eject_btn.isEnabled() is False + assert w.preview_btn.isEnabled() is False + assert "do not retry" in w.status_label.text() + assert "physically checking" in w._missing_for_preview()[-1] + class TestSpacingAndApproval: def test_apply_offset_forwards_the_spinner_value(self, fake_coolscanpy) -> None: diff --git a/tests/roll/test_worker.py b/tests/roll/test_worker.py index 1fc3b433..d7ae8174 100644 --- a/tests/roll/test_worker.py +++ b/tests/roll/test_worker.py @@ -219,6 +219,80 @@ def test_close_roll_failure_retains_identity_and_does_not_emit_closed( assert errors and "ownership remains uncertain" in errors[-1] +class TestEject: + def test_eject_without_an_open_roll_emits_success(self) -> None: + worker = RollWorker() + worker._service.eject = MagicMock(return_value=True) + results: list[bool] = [] + worker.ejected.connect(results.append) + + worker.eject("usb:2:7") + + worker._service.eject.assert_called_once_with("usb:2:7") + assert results == [True] + + def test_eject_closes_the_matching_roll_before_motion( + self, + fake_coolscanpy, + ) -> None: + _open(fake_coolscanpy) + worker = RollWorker() + worker.run_preview(RollPreviewRequest(device_id="usb:2:7")) + events: list[str] = [] + real_close = worker._service.close + + def close() -> None: + events.append("close") + real_close() + + def eject(device_id: str) -> bool: + events.append(f"eject:{device_id}") + return True + + worker._service.close = close + worker._service.eject = eject + + worker.eject("usb:2:7") + + assert events == ["close", "eject:usb:2:7"] + assert worker._open_device_id is None + + def test_close_failure_preserves_identity_and_never_ejects( + self, + fake_coolscanpy, + ) -> None: + _open(fake_coolscanpy) + worker = RollWorker() + worker.run_preview(RollPreviewRequest(device_id="usb:2:7")) + worker._service.close = MagicMock(side_effect=RuntimeError("reservation uncertain")) + worker._service.eject = MagicMock(return_value=True) + errors: list[str] = [] + worker.eject_error.connect(errors.append) + + worker.eject("usb:2:7") + + assert worker._open_device_id == "usb:2:7" + worker._service.eject.assert_not_called() + assert errors == ["reservation uncertain"] + + def test_refuses_to_eject_a_device_other_than_the_open_roll( + self, + fake_coolscanpy, + ) -> None: + _open(fake_coolscanpy) + worker = RollWorker() + worker.run_preview(RollPreviewRequest(device_id="usb:2:7")) + worker._service.eject = MagicMock(return_value=True) + errors: list[str] = [] + worker.eject_error.connect(errors.append) + + worker.eject("usb:2:8") + + worker._service.eject.assert_not_called() + assert worker._open_device_id == "usb:2:7" + assert errors and "not requested device" in errors[0] + + class TestSpacingAndApproval: def test_set_spacing_offset_echoes_the_applied_value(self, fake_coolscanpy) -> None: roll, _device = _open(fake_coolscanpy) diff --git a/tests/scanners/test_sane_session.py b/tests/scanners/test_sane_session.py index ba4e5a2d..bdf6c740 100644 --- a/tests/scanners/test_sane_session.py +++ b/tests/scanners/test_sane_session.py @@ -252,6 +252,7 @@ def _held(self, monkeypatch: pytest.MonkeyPatch, *, eject: bool): session = backend.open_session(_DEV_ID) observed: dict[str, int] = {} run = FakeRun(on_call=lambda: observed.__setitem__("close_calls_at_press", module.dev.close_calls)) + monkeypatch.setattr(sane_backend, "_scanimage_executable", lambda: "scanimage") monkeypatch.setattr(sane_backend.subprocess, "run", run) return backend, module, session, run, observed diff --git a/tests/scanners/test_scanner_eject.py b/tests/scanners/test_scanner_eject.py index 104616a5..0bd975cb 100644 --- a/tests/scanners/test_scanner_eject.py +++ b/tests/scanners/test_scanner_eject.py @@ -138,6 +138,7 @@ def __call__(self, args: list[str], **kwargs: Any) -> _FakeCompleted: def _patch_run(monkeypatch: pytest.MonkeyPatch, **kwargs: Any) -> FakeRun: fake = FakeRun(**kwargs) + monkeypatch.setattr(sane_backend, "_scanimage_executable", lambda: "scanimage") monkeypatch.setattr(sane_backend.subprocess, "run", fake) return fake @@ -175,6 +176,36 @@ def test_matches_hyphenated_spelling(self) -> None: assert _find_eject_option({"eject": FakeOption()}) == "eject" +class TestDirectUsbEject: + def test_maps_the_exact_direct_usb_topology_to_coolscan3( + self, + monkeypatch: pytest.MonkeyPatch, + ) -> None: + calls: list[str] = [] + monkeypatch.setattr(sane_backend, "_scanimage_eject", calls.append) + + assert sane_backend.scanimage_eject_direct_usb("usb:2:7") is True + assert calls == ["coolscan3:usb:libusb:002:007"] + + @pytest.mark.parametrize( + "device_id", + ( + "ls5000", + "usb:2", + "usb:0:7", + "usb:2:0", + "usb:2:7:extra", + "usb:1000:7", + ), + ) + def test_rejects_ambiguous_or_out_of_range_topologies( + self, + device_id: str, + ) -> None: + with pytest.raises(ValueError, match="direct Coolscan eject"): + sane_backend.scanimage_eject_direct_usb(device_id) + + class TestSaneBackendEject: def test_presses_eject_via_scanimage_and_returns_true(self, monkeypatch: pytest.MonkeyPatch) -> None: dev = FakeSaneDev(dict(COOLSCAN3_OPT_WITH_EJECT)) From b1313673bc597abc45aef589ca940887092d1ca9 Mon Sep 17 00:00:00 2001 From: Rohan Pandula Date: Thu, 23 Jul 2026 03:47:43 -0700 Subject: [PATCH 34/48] fix(scanner): resume batch after short startup table --- pyproject.toml | 2 +- uv.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 82f66dde..ebfe3303 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -60,7 +60,7 @@ coolscan-roll = [ # Pin the reviewed color-negative density/DICE/direct-USB implementation # until its first tagged release. # No SANE dependency for this workflow; see docs/COOLSCANPY_ROLL_SCANNING.md. - "coolscanpy @ git+https://github.com/rohanpandula/coolscanpy.git@e42c68fb53b74aee9fc29eae432d092de351001d", + "coolscanpy @ git+https://github.com/rohanpandula/coolscanpy.git@586f575cde5ca33a0119b0c4c9d37f48ef9be1c2", ] fauxice = [ "portable-digital-ice @ https://github.com/rohanpandula/digital-fauxice/releases/download/v0.3.1/portable_digital_ice-0.3.1-py3-none-any.whl", diff --git a/uv.lock b/uv.lock index 28390ade..1bc032e5 100644 --- a/uv.lock +++ b/uv.lock @@ -77,7 +77,7 @@ wheels = [ [[package]] name = "coolscanpy" version = "0.1.3" -source = { git = "https://github.com/rohanpandula/coolscanpy.git?rev=e42c68fb53b74aee9fc29eae432d092de351001d#e42c68fb53b74aee9fc29eae432d092de351001d" } +source = { git = "https://github.com/rohanpandula/coolscanpy.git?rev=586f575cde5ca33a0119b0c4c9d37f48ef9be1c2#586f575cde5ca33a0119b0c4c9d37f48ef9be1c2" } dependencies = [ { name = "imagecodecs" }, { name = "jinja2" }, @@ -432,7 +432,7 @@ requires-dist = [ [package.metadata.requires-dev] camera = [{ name = "gphoto2", marker = "sys_platform != 'win32'", specifier = ">=2.5" }] -coolscan-roll = [{ name = "coolscanpy", git = "https://github.com/rohanpandula/coolscanpy.git?rev=e42c68fb53b74aee9fc29eae432d092de351001d" }] +coolscan-roll = [{ name = "coolscanpy", git = "https://github.com/rohanpandula/coolscanpy.git?rev=586f575cde5ca33a0119b0c4c9d37f48ef9be1c2" }] dev = [ { name = "pyinstaller", specifier = ">=6.18.0" }, { name = "pytest", specifier = "==9.0.2" }, From 5eeb655bed2309dd07d6e2cae8f7cfedfc429bb1 Mon Sep 17 00:00:00 2001 From: Rohan Pandula Date: Thu, 23 Jul 2026 03:58:18 -0700 Subject: [PATCH 35/48] feat(scanner): center whole-roll preview --- negpy/desktop/view/main_window.py | 16 ++- negpy/desktop/view/sidebar/coolscan_roll.py | 117 +++++++++++++++++--- tests/roll/test_sidebar.py | 51 +++++++++ 3 files changed, 168 insertions(+), 16 deletions(-) diff --git a/negpy/desktop/view/main_window.py b/negpy/desktop/view/main_window.py index 75ef9777..7d5daa34 100644 --- a/negpy/desktop/view/main_window.py +++ b/negpy/desktop/view/main_window.py @@ -8,6 +8,7 @@ QApplication, QMainWindow, QMessageBox, + QStackedWidget, QStatusBar, QVBoxLayout, QWidget, @@ -253,7 +254,9 @@ def _init_ui(self) -> None: self.loading_overlay = LoadingOverlay(self.canvas) self.loading_overlay.raise_() - self.central_layout.addWidget(self.canvas, stretch=1) + self.central_stack = QStackedWidget() + self.central_stack.addWidget(self.canvas) + self.central_layout.addWidget(self.central_stack, stretch=1) self.setCentralWidget(self.central_widget) @@ -266,6 +269,8 @@ def _init_ui(self) -> None: self.drawer.setAllowedAreas(Qt.DockWidgetArea.LeftDockWidgetArea | Qt.DockWidgetArea.RightDockWidgetArea) self.right_panel = RightPanel(self.controller) + self.roll_preview_workspace = self.right_panel.coolscan_roll_sidebar.preview_workspace + self.central_stack.addWidget(self.roll_preview_workspace) # Back-compat alias: tutorial, keyboard shortcuts, and _sync_tool_buttons reach feature sidebars here. self.controls_panel = self.right_panel.controls_panel @@ -379,6 +384,8 @@ def reset_panel_layout(self) -> None: def _connect_signals(self) -> None: """Wire controller and view.""" self.controller.session.state_changed.connect(self._update_title) + self.right_panel.coolscan_roll_sidebar.workspace_requested.connect(self._show_roll_preview_workspace) + self.roll_preview_workspace.back_requested.connect(self._show_image_workspace) # visibilityChanged only mirrors the button — it also fires on close/minimize, # so we persist in the toggle methods to avoid clobbering the saved state on exit. @@ -398,6 +405,7 @@ def _connect_signals(self) -> None: # Metadata updates only on persistent history changes or file selection self.controller.session.history_changed.connect(self._refresh_image_info) self.controller.session.file_selected.connect(lambda _: self._refresh_image_info()) + self.controller.session.file_selected.connect(lambda _: self._show_image_workspace()) self.controller.session.session_emptied.connect(self._on_session_emptied) self.canvas.clicked.connect(self.controller.handle_canvas_clicked) @@ -443,6 +451,12 @@ def _connect_signals(self) -> None: def _refresh_dashboard(self) -> None: self.toolbar.refresh_gpu_status() + def _show_roll_preview_workspace(self) -> None: + self.central_stack.setCurrentWidget(self.roll_preview_workspace) + + def _show_image_workspace(self) -> None: + self.central_stack.setCurrentWidget(self.canvas) + def _display_buffer_for_canvas(self, buffer): if isinstance(buffer, GPUTexture): buffer = buffer.readback() diff --git a/negpy/desktop/view/sidebar/coolscan_roll.py b/negpy/desktop/view/sidebar/coolscan_roll.py index 2416712b..1dbb782e 100644 --- a/negpy/desktop/view/sidebar/coolscan_roll.py +++ b/negpy/desktop/view/sidebar/coolscan_roll.py @@ -17,7 +17,7 @@ import numpy as np import qtawesome as qta -from PyQt6.QtCore import QSize, Qt, pyqtSlot +from PyQt6.QtCore import QSize, Qt, pyqtSignal, pyqtSlot from PyQt6.QtGui import QColor, QIcon, QImage, QPixmap from PyQt6.QtWidgets import ( QAbstractItemView, @@ -54,10 +54,10 @@ _SLOT_ROLE = Qt.ItemDataRole.UserRole _WARN_COLOR = "#C8922E" # matches ScanlightSidebar's advisory tone -_THUMBNAIL_SIZE = QSize(96, 96) +_THUMBNAIL_SIZE = QSize(220, 150) -def _thumbnail_pixmap(image: "np.ndarray") -> QPixmap: +def _thumbnail_pixmap(image: "np.ndarray", *, inverted: bool = False) -> QPixmap: """A `coolscanpy.Thumbnail.image` array as a displayable QPixmap. Deliberately dumb: this is a raw scanner preview frame for framing and @@ -74,14 +74,79 @@ def _thumbnail_pixmap(image: "np.ndarray") -> QPixmap: scale = 255.0 if peak <= 1.0 else (255.0 / peak if peak > 255.0 else 1.0) arr = np.clip(arr.astype(np.float64) * scale, 0, 255).astype(np.uint8) arr = np.ascontiguousarray(arr[..., :3]) + if inverted: + # Display only. The scanner thumbnail remains untouched and this never + # enters the capture or output-color pipelines. + arr = np.ascontiguousarray(255 - arr) h, w = arr.shape[:2] qimg = QImage(arr.data, w, h, w * 3, QImage.Format.Format_RGB888).copy() # copy: detach from arr's buffer return QPixmap.fromImage(qimg) +class RollPreviewWorkspace(QWidget): + """Center-stage contact sheet for the whole-roll workflow.""" + + back_requested = pyqtSignal() + + def __init__(self) -> None: + super().__init__() + layout = QVBoxLayout(self) + layout.setContentsMargins(24, 18, 24, 18) + layout.setSpacing(12) + + header = QHBoxLayout() + title_col = QVBoxLayout() + title_col.setSpacing(2) + title = QLabel("Roll preview") + title.setStyleSheet(f"color: {THEME.text_primary}; font-size: 20px; font-weight: 600;") + subtitle = QLabel("Review, approve, and select frames here. Display mode does not change captured or saved files.") + subtitle.setStyleSheet(f"color: {THEME.text_muted}; font-size: {THEME.font_size_small}px;") + subtitle.setWordWrap(True) + title_col.addWidget(title) + title_col.addWidget(subtitle) + header.addLayout(title_col, 1) + + mode_label = QLabel("Show as") + mode_label.setStyleSheet(f"color: {THEME.text_secondary};") + self.display_mode_combo = QComboBox() + self.display_mode_combo.setObjectName("roll_preview_display_mode") + self.display_mode_combo.addItem("Positive (inverted)", True) + self.display_mode_combo.addItem("Negative (raw)", False) + self.display_mode_combo.setMinimumHeight(40) + self.display_mode_combo.setMinimumWidth(180) + self.display_mode_combo.setToolTip( + "Display only: Positive inverts the preview for easier review. " + "Negative shows the scanner thumbnail without inversion. Neither changes capture bytes or output color." + ) + self.back_btn = QPushButton("Back to image editor") + self.back_btn.setMinimumHeight(40) + self.back_btn.clicked.connect(lambda _checked=False: self.back_requested.emit()) + header.addWidget(mode_label) + header.addWidget(self.display_mode_combo) + header.addWidget(self.back_btn) + layout.addLayout(header) + + self.contact_sheet = QListWidget() + self.contact_sheet.setObjectName("roll_preview_contact_sheet") + self.contact_sheet.setViewMode(QListWidget.ViewMode.IconMode) + self.contact_sheet.setResizeMode(QListWidget.ResizeMode.Adjust) + self.contact_sheet.setMovement(QListWidget.Movement.Static) + self.contact_sheet.setWrapping(True) + self.contact_sheet.setSpacing(12) + self.contact_sheet.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection) + self.contact_sheet.setIconSize(_THUMBNAIL_SIZE) + self.contact_sheet.setMinimumHeight(360) + self.contact_sheet.setToolTip( + "Click a slot to nudge its spacing or approve it; select several, then use Scan Selected in the controls panel." + ) + layout.addWidget(self.contact_sheet, 1) + + class CoolscanRollSidebar(QWidget): """Whole-roll preview, spacing/approval and batch fine-scan panel.""" + workspace_requested = pyqtSignal() + def __init__(self, controller) -> None: super().__init__() self.controller = controller @@ -97,6 +162,9 @@ def __init__(self, controller) -> None: self._eject_failed = False self._active_scan_request: RollBatchScanRequest | None = None + self.preview_workspace = RollPreviewWorkspace() + self.contact_sheet = self.preview_workspace.contact_sheet + self.preview_display_combo = self.preview_workspace.display_mode_combo self._init_ui() self._connect_signals() @@ -286,17 +354,13 @@ def _init_ui(self) -> None: self.status_label.setVisible(False) layout.addWidget(self.status_label) - # ── CONTACT SHEET ───────────────────────────────────── - layout.addWidget(section_subheader("CONTACT SHEET")) - self.contact_sheet = QListWidget() - self.contact_sheet.setViewMode(QListWidget.ViewMode.IconMode) - self.contact_sheet.setResizeMode(QListWidget.ResizeMode.Adjust) - self.contact_sheet.setSelectionMode(QAbstractItemView.SelectionMode.ExtendedSelection) - self.contact_sheet.setIconSize(_THUMBNAIL_SIZE) - self.contact_sheet.setMinimumHeight(160) - self.contact_sheet.setToolTip("Click a slot to nudge its spacing or approve it; select several, then Scan Selected.") - layout.addWidget(self.contact_sheet) - + # The images live in the main center workspace; the sidebar contains + # only the actions and details needed to operate on that selection. + layout.addWidget(section_subheader("FRAME REVIEW")) + self.open_preview_workspace_btn = QPushButton(qta.icon("fa5s.th", color=THEME.text_secondary), " Open Roll Preview") + self.open_preview_workspace_btn.setMinimumHeight(40) + self.open_preview_workspace_btn.setToolTip("Show the roll thumbnails in the main workspace") + layout.addWidget(self.open_preview_workspace_btn) selection_row = QHBoxLayout() self.select_all_btn = QPushButton("Select All Frames") self.select_all_btn.setToolTip("Select every frame found by the current roll preview") @@ -353,6 +417,8 @@ def _connect_signals(self) -> None: self.repair_mode_combo.currentIndexChanged.connect(self._update_settings_from_ui) self.hybrid_synthesis_limit_spin.valueChanged.connect(self._update_settings_from_ui) self.preview_btn.clicked.connect(self._on_preview_clicked) + self.open_preview_workspace_btn.clicked.connect(lambda _checked=False: self.workspace_requested.emit()) + self.preview_display_combo.currentIndexChanged.connect(self._on_preview_display_changed) self.contact_sheet.itemSelectionChanged.connect(self._on_selection_changed) self.select_all_btn.clicked.connect(self.contact_sheet.selectAll) self.clear_selection_btn.clicked.connect(self.contact_sheet.clearSelection) @@ -470,6 +536,7 @@ def _on_preview_clicked(self) -> None: from negpy.desktop.workers.roll_worker import RollPreviewRequest self._preview_pending = True + self.workspace_requested.emit() self._clear_contact_sheet() self._set_status("Reading roll transport…") self.progress_bar.setVisible(True) @@ -492,10 +559,11 @@ def _on_preview_ready(self, thumbnails: "list[coolscanpy.Thumbnail]") -> None: for t in sorted(self._thumbnails): self._add_slot_item(self._thumbnails[t]) self._show_slot_detail(None) + self.workspace_requested.emit() self._apply_gating() def _add_slot_item(self, thumb: "coolscanpy.Thumbnail") -> None: - pixmap = _thumbnail_pixmap(thumb.image).scaled( + pixmap = _thumbnail_pixmap(thumb.image, inverted=self._preview_is_inverted()).scaled( _THUMBNAIL_SIZE, Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation ) item = QListWidgetItem(QIcon(pixmap), f"Slot {thumb.slot}" + (" ⚠" if thumb.needs_approval else "")) @@ -504,6 +572,24 @@ def _add_slot_item(self, thumb: "coolscanpy.Thumbnail") -> None: item.setForeground(QColor(_WARN_COLOR)) self.contact_sheet.addItem(item) + def _preview_is_inverted(self) -> bool: + return bool(self.preview_display_combo.currentData()) + + def _on_preview_display_changed(self) -> None: + for i in range(self.contact_sheet.count()): + item = self.contact_sheet.item(i) + if item is None: + continue + thumb = self._thumbnails.get(item.data(_SLOT_ROLE)) + if thumb is None: + continue + pixmap = _thumbnail_pixmap(thumb.image, inverted=self._preview_is_inverted()).scaled( + _THUMBNAIL_SIZE, + Qt.AspectRatioMode.KeepAspectRatio, + Qt.TransformationMode.SmoothTransformation, + ) + item.setIcon(QIcon(pixmap)) + def _on_selection_changed(self) -> None: current = self.contact_sheet.currentItem() slot = current.data(_SLOT_ROLE) if current is not None else None @@ -783,6 +869,7 @@ def _apply_gating(self) -> None: self.device_combo.setEnabled(not self._scanning and not self._preview_pending and not self._eject_pending) self.refresh_btn.setEnabled(not self._scanning and not self._preview_pending and not self._eject_pending) self.contact_sheet.setEnabled(not self._scanning and not registration_locked) + self.open_preview_workspace_btn.setEnabled(not self._eject_failed) self.select_all_btn.setEnabled(bool(self._thumbnails) and not self._scanning and not registration_locked) self.clear_selection_btn.setEnabled(bool(self._selected_slots()) and not self._scanning and not registration_locked) self.eject_btn.setEnabled( diff --git a/tests/roll/test_sidebar.py b/tests/roll/test_sidebar.py index 194ce175..f82e2a1a 100644 --- a/tests/roll/test_sidebar.py +++ b/tests/roll/test_sidebar.py @@ -65,8 +65,14 @@ def test_sidebar_builds_with_all_controls(self) -> None: "status_label", "hybrid_synthesis_limit_spin", "hybrid_guidance", + "preview_workspace", + "preview_display_combo", + "open_preview_workspace_btn", ): assert hasattr(w, attr), attr + assert w.contact_sheet is w.preview_workspace.contact_sheet + assert w.contact_sheet.minimumHeight() >= 360 + assert w.contact_sheet.iconSize().width() >= 200 def test_missing_coolscanpy_disables_preview_and_shows_hint(self, monkeypatch) -> None: # The optional group may be installed by this test run, so exercise @@ -122,6 +128,19 @@ def test_preview_click_without_a_device_is_a_noop(self) -> None: w._on_preview_clicked() w.controller.start_coolscan_roll_preview.assert_not_called() + def test_workspace_buttons_switch_between_roll_preview_and_editor(self) -> None: + w = _sidebar() + opened: list[bool] = [] + closed: list[bool] = [] + w.workspace_requested.connect(lambda: opened.append(True)) + w.preview_workspace.back_requested.connect(lambda: closed.append(True)) + + w.open_preview_workspace_btn.click() + w.preview_workspace.back_btn.click() + + assert opened == [True] + assert closed == [True] + def test_preview_ready_populates_the_contact_sheet(self, fake_coolscanpy) -> None: w = _sidebar() thumbs = [_thumb(fake_coolscanpy, s) for s in (2, 1, 3)] @@ -136,6 +155,38 @@ def test_preview_ready_populates_the_contact_sheet(self, fake_coolscanpy) -> Non listed = [w.contact_sheet.item(i).data(_SLOT_ROLE) for i in range(w.contact_sheet.count())] assert listed == [1, 2, 3] + def test_display_toggle_updates_icons_without_mutating_capture_data(self, fake_coolscanpy) -> None: + w = _sidebar() + image = np.zeros((8, 12, 3), dtype=np.uint8) + image[:, :6, :] = (10, 20, 30) + image[:, 6:, :] = (180, 190, 200) + original = image.copy() + + w._on_preview_ready( + [ + fake_coolscanpy.Thumbnail( + slot=1, + image=image, + boundary_rows=(0, 8), + spacing_offset=0, + needs_approval=False, + ) + ] + ) + + assert w.preview_display_combo.currentData() is True + positive_key = w.contact_sheet.item(0).icon().cacheKey() + w.preview_display_combo.setCurrentIndex(1) + raw_key = w.contact_sheet.item(0).icon().cacheKey() + w.preview_display_combo.setCurrentIndex(0) + positive_again_key = w.contact_sheet.item(0).icon().cacheKey() + + assert positive_key != raw_key + assert raw_key != positive_again_key + np.testing.assert_array_equal(w._thumbnails[1].image, original) + w.controller.start_coolscan_roll_preview.assert_not_called() + w.controller.start_roll_scan.assert_not_called() + def test_needs_approval_slot_is_marked_and_shows_approve_button(self, fake_coolscanpy) -> None: w = _sidebar() w._on_preview_ready([_thumb(fake_coolscanpy, 5, needs_approval=True)]) From d5324ad9fd8b1704e22155e450205175e91a5376 Mon Sep 17 00:00:00 2001 From: Rohan Pandula Date: Thu, 23 Jul 2026 04:22:31 -0700 Subject: [PATCH 36/48] chore(scanner): pin preview settle fix --- pyproject.toml | 2 +- uv.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index ebfe3303..0cf0a9f0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -60,7 +60,7 @@ coolscan-roll = [ # Pin the reviewed color-negative density/DICE/direct-USB implementation # until its first tagged release. # No SANE dependency for this workflow; see docs/COOLSCANPY_ROLL_SCANNING.md. - "coolscanpy @ git+https://github.com/rohanpandula/coolscanpy.git@586f575cde5ca33a0119b0c4c9d37f48ef9be1c2", + "coolscanpy @ git+https://github.com/rohanpandula/coolscanpy.git@ef4e7d383d0a8e71e64e445a2b370ebaba80ac2b", ] fauxice = [ "portable-digital-ice @ https://github.com/rohanpandula/digital-fauxice/releases/download/v0.3.1/portable_digital_ice-0.3.1-py3-none-any.whl", diff --git a/uv.lock b/uv.lock index 1bc032e5..d2c0af01 100644 --- a/uv.lock +++ b/uv.lock @@ -77,7 +77,7 @@ wheels = [ [[package]] name = "coolscanpy" version = "0.1.3" -source = { git = "https://github.com/rohanpandula/coolscanpy.git?rev=586f575cde5ca33a0119b0c4c9d37f48ef9be1c2#586f575cde5ca33a0119b0c4c9d37f48ef9be1c2" } +source = { git = "https://github.com/rohanpandula/coolscanpy.git?rev=ef4e7d383d0a8e71e64e445a2b370ebaba80ac2b#ef4e7d383d0a8e71e64e445a2b370ebaba80ac2b" } dependencies = [ { name = "imagecodecs" }, { name = "jinja2" }, @@ -432,7 +432,7 @@ requires-dist = [ [package.metadata.requires-dev] camera = [{ name = "gphoto2", marker = "sys_platform != 'win32'", specifier = ">=2.5" }] -coolscan-roll = [{ name = "coolscanpy", git = "https://github.com/rohanpandula/coolscanpy.git?rev=586f575cde5ca33a0119b0c4c9d37f48ef9be1c2" }] +coolscan-roll = [{ name = "coolscanpy", git = "https://github.com/rohanpandula/coolscanpy.git?rev=ef4e7d383d0a8e71e64e445a2b370ebaba80ac2b" }] dev = [ { name = "pyinstaller", specifier = ">=6.18.0" }, { name = "pytest", specifier = "==9.0.2" }, From 2ab81081525153a1318f89b65fdb50ff160ca0d4 Mon Sep 17 00:00:00 2001 From: Rohan Pandula Date: Thu, 23 Jul 2026 04:39:56 -0700 Subject: [PATCH 37/48] chore(scanner): pin Nikon preview timing fix --- pyproject.toml | 2 +- uv.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 0cf0a9f0..9ef15793 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -60,7 +60,7 @@ coolscan-roll = [ # Pin the reviewed color-negative density/DICE/direct-USB implementation # until its first tagged release. # No SANE dependency for this workflow; see docs/COOLSCANPY_ROLL_SCANNING.md. - "coolscanpy @ git+https://github.com/rohanpandula/coolscanpy.git@ef4e7d383d0a8e71e64e445a2b370ebaba80ac2b", + "coolscanpy @ git+https://github.com/rohanpandula/coolscanpy.git@d72008d0f772821e0c257e06304c4a0d0f0dd22d", ] fauxice = [ "portable-digital-ice @ https://github.com/rohanpandula/digital-fauxice/releases/download/v0.3.1/portable_digital_ice-0.3.1-py3-none-any.whl", diff --git a/uv.lock b/uv.lock index d2c0af01..5b38c06c 100644 --- a/uv.lock +++ b/uv.lock @@ -77,7 +77,7 @@ wheels = [ [[package]] name = "coolscanpy" version = "0.1.3" -source = { git = "https://github.com/rohanpandula/coolscanpy.git?rev=ef4e7d383d0a8e71e64e445a2b370ebaba80ac2b#ef4e7d383d0a8e71e64e445a2b370ebaba80ac2b" } +source = { git = "https://github.com/rohanpandula/coolscanpy.git?rev=d72008d0f772821e0c257e06304c4a0d0f0dd22d#d72008d0f772821e0c257e06304c4a0d0f0dd22d" } dependencies = [ { name = "imagecodecs" }, { name = "jinja2" }, @@ -432,7 +432,7 @@ requires-dist = [ [package.metadata.requires-dev] camera = [{ name = "gphoto2", marker = "sys_platform != 'win32'", specifier = ">=2.5" }] -coolscan-roll = [{ name = "coolscanpy", git = "https://github.com/rohanpandula/coolscanpy.git?rev=ef4e7d383d0a8e71e64e445a2b370ebaba80ac2b" }] +coolscan-roll = [{ name = "coolscanpy", git = "https://github.com/rohanpandula/coolscanpy.git?rev=d72008d0f772821e0c257e06304c4a0d0f0dd22d" }] dev = [ { name = "pyinstaller", specifier = ">=6.18.0" }, { name = "pytest", specifier = "==9.0.2" }, From 055b742fcecfc04912286da196e5b0427139efa9 Mon Sep 17 00:00:00 2001 From: Rohan Pandula Date: Thu, 23 Jul 2026 05:03:08 -0700 Subject: [PATCH 38/48] chore(scanner): pin startup-table diagnostic --- pyproject.toml | 2 +- uv.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 9ef15793..137fdfe5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -60,7 +60,7 @@ coolscan-roll = [ # Pin the reviewed color-negative density/DICE/direct-USB implementation # until its first tagged release. # No SANE dependency for this workflow; see docs/COOLSCANPY_ROLL_SCANNING.md. - "coolscanpy @ git+https://github.com/rohanpandula/coolscanpy.git@d72008d0f772821e0c257e06304c4a0d0f0dd22d", + "coolscanpy @ git+https://github.com/rohanpandula/coolscanpy.git@6234550", ] fauxice = [ "portable-digital-ice @ https://github.com/rohanpandula/digital-fauxice/releases/download/v0.3.1/portable_digital_ice-0.3.1-py3-none-any.whl", diff --git a/uv.lock b/uv.lock index 5b38c06c..3a087050 100644 --- a/uv.lock +++ b/uv.lock @@ -77,7 +77,7 @@ wheels = [ [[package]] name = "coolscanpy" version = "0.1.3" -source = { git = "https://github.com/rohanpandula/coolscanpy.git?rev=d72008d0f772821e0c257e06304c4a0d0f0dd22d#d72008d0f772821e0c257e06304c4a0d0f0dd22d" } +source = { git = "https://github.com/rohanpandula/coolscanpy.git?rev=6234550#6234550982b5b2ab000077828de7609ea2be14d7" } dependencies = [ { name = "imagecodecs" }, { name = "jinja2" }, @@ -432,7 +432,7 @@ requires-dist = [ [package.metadata.requires-dev] camera = [{ name = "gphoto2", marker = "sys_platform != 'win32'", specifier = ">=2.5" }] -coolscan-roll = [{ name = "coolscanpy", git = "https://github.com/rohanpandula/coolscanpy.git?rev=d72008d0f772821e0c257e06304c4a0d0f0dd22d" }] +coolscan-roll = [{ name = "coolscanpy", git = "https://github.com/rohanpandula/coolscanpy.git?rev=6234550" }] dev = [ { name = "pyinstaller", specifier = ">=6.18.0" }, { name = "pytest", specifier = "==9.0.2" }, From 02ef491a101db60beb74632e5edc4a9f100f654c Mon Sep 17 00:00:00 2001 From: Rohan Pandula Date: Thu, 23 Jul 2026 05:50:23 -0700 Subject: [PATCH 39/48] chore(scanner): pin dynamic full-roll preview --- docs/COOLSCANPY_ROLL_SCANNING.md | 2 +- pyproject.toml | 2 +- uv.lock | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/COOLSCANPY_ROLL_SCANNING.md b/docs/COOLSCANPY_ROLL_SCANNING.md index b7623303..afee89e4 100644 --- a/docs/COOLSCANPY_ROLL_SCANNING.md +++ b/docs/COOLSCANPY_ROLL_SCANNING.md @@ -73,7 +73,7 @@ so a signed build fails before release if the JIT exception is absent. If you manage your own environment instead, install the package directly: ``` -pip install "coolscanpy @ git+https://github.com/rohanpandula/coolscanpy.git@e42c68fb53b74aee9fc29eae432d092de351001d" +pip install "coolscanpy @ git+https://github.com/rohanpandula/coolscanpy.git@8a54e462acb2f5a3e9f9550b4625bbc077449f56" ``` The accepted color-negative roll workflow has no SANE dependency. diff --git a/pyproject.toml b/pyproject.toml index 137fdfe5..7dff5672 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -60,7 +60,7 @@ coolscan-roll = [ # Pin the reviewed color-negative density/DICE/direct-USB implementation # until its first tagged release. # No SANE dependency for this workflow; see docs/COOLSCANPY_ROLL_SCANNING.md. - "coolscanpy @ git+https://github.com/rohanpandula/coolscanpy.git@6234550", + "coolscanpy @ git+https://github.com/rohanpandula/coolscanpy.git@8a54e46", ] fauxice = [ "portable-digital-ice @ https://github.com/rohanpandula/digital-fauxice/releases/download/v0.3.1/portable_digital_ice-0.3.1-py3-none-any.whl", diff --git a/uv.lock b/uv.lock index 3a087050..7044faac 100644 --- a/uv.lock +++ b/uv.lock @@ -77,7 +77,7 @@ wheels = [ [[package]] name = "coolscanpy" version = "0.1.3" -source = { git = "https://github.com/rohanpandula/coolscanpy.git?rev=6234550#6234550982b5b2ab000077828de7609ea2be14d7" } +source = { git = "https://github.com/rohanpandula/coolscanpy.git?rev=8a54e46#8a54e462acb2f5a3e9f9550b4625bbc077449f56" } dependencies = [ { name = "imagecodecs" }, { name = "jinja2" }, @@ -432,7 +432,7 @@ requires-dist = [ [package.metadata.requires-dev] camera = [{ name = "gphoto2", marker = "sys_platform != 'win32'", specifier = ">=2.5" }] -coolscan-roll = [{ name = "coolscanpy", git = "https://github.com/rohanpandula/coolscanpy.git?rev=6234550" }] +coolscan-roll = [{ name = "coolscanpy", git = "https://github.com/rohanpandula/coolscanpy.git?rev=8a54e46" }] dev = [ { name = "pyinstaller", specifier = ">=6.18.0" }, { name = "pytest", specifier = "==9.0.2" }, From a05b71fafa92da3afee76661168aaf569368855f Mon Sep 17 00:00:00 2001 From: Rohan Pandula Date: Thu, 23 Jul 2026 06:16:12 -0700 Subject: [PATCH 40/48] chore(scanner): pin full-roll mapping fix --- docs/COOLSCANPY_ROLL_SCANNING.md | 2 +- pyproject.toml | 2 +- uv.lock | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/COOLSCANPY_ROLL_SCANNING.md b/docs/COOLSCANPY_ROLL_SCANNING.md index afee89e4..cc3b3d05 100644 --- a/docs/COOLSCANPY_ROLL_SCANNING.md +++ b/docs/COOLSCANPY_ROLL_SCANNING.md @@ -73,7 +73,7 @@ so a signed build fails before release if the JIT exception is absent. If you manage your own environment instead, install the package directly: ``` -pip install "coolscanpy @ git+https://github.com/rohanpandula/coolscanpy.git@8a54e462acb2f5a3e9f9550b4625bbc077449f56" +pip install "coolscanpy @ git+https://github.com/rohanpandula/coolscanpy.git@12793304be8ca5d7848f269014f721ea09772ca3" ``` The accepted color-negative roll workflow has no SANE dependency. diff --git a/pyproject.toml b/pyproject.toml index 7dff5672..38e5259b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -60,7 +60,7 @@ coolscan-roll = [ # Pin the reviewed color-negative density/DICE/direct-USB implementation # until its first tagged release. # No SANE dependency for this workflow; see docs/COOLSCANPY_ROLL_SCANNING.md. - "coolscanpy @ git+https://github.com/rohanpandula/coolscanpy.git@8a54e46", + "coolscanpy @ git+https://github.com/rohanpandula/coolscanpy.git@1279330", ] fauxice = [ "portable-digital-ice @ https://github.com/rohanpandula/digital-fauxice/releases/download/v0.3.1/portable_digital_ice-0.3.1-py3-none-any.whl", diff --git a/uv.lock b/uv.lock index 7044faac..83e0f930 100644 --- a/uv.lock +++ b/uv.lock @@ -77,7 +77,7 @@ wheels = [ [[package]] name = "coolscanpy" version = "0.1.3" -source = { git = "https://github.com/rohanpandula/coolscanpy.git?rev=8a54e46#8a54e462acb2f5a3e9f9550b4625bbc077449f56" } +source = { git = "https://github.com/rohanpandula/coolscanpy.git?rev=1279330#12793304be8ca5d7848f269014f721ea09772ca3" } dependencies = [ { name = "imagecodecs" }, { name = "jinja2" }, @@ -432,7 +432,7 @@ requires-dist = [ [package.metadata.requires-dev] camera = [{ name = "gphoto2", marker = "sys_platform != 'win32'", specifier = ">=2.5" }] -coolscan-roll = [{ name = "coolscanpy", git = "https://github.com/rohanpandula/coolscanpy.git?rev=8a54e46" }] +coolscan-roll = [{ name = "coolscanpy", git = "https://github.com/rohanpandula/coolscanpy.git?rev=1279330" }] dev = [ { name = "pyinstaller", specifier = ">=6.18.0" }, { name = "pytest", specifier = "==9.0.2" }, From 49d58c26296a67855ee5ac40be4bc08eda9bf083 Mon Sep 17 00:00:00 2001 From: Rohan Pandula Date: Thu, 23 Jul 2026 06:54:51 -0700 Subject: [PATCH 41/48] fix(scanner): accept proven full-roll density geometry --- negpy/services/roll/exact_color.py | 6 ++++- negpy/services/roll/native_builder.py | 8 +++++-- tests/roll/test_native_builder.py | 32 +++++++++++++++++++++++++-- 3 files changed, 41 insertions(+), 5 deletions(-) diff --git a/negpy/services/roll/exact_color.py b/negpy/services/roll/exact_color.py index 3851ad6b..9e3c17e7 100644 --- a/negpy/services/roll/exact_color.py +++ b/negpy/services/roll/exact_color.py @@ -43,6 +43,9 @@ _MAX_RECEIPT_BYTES = 1024 * 1024 _BUILDER_CHANNELS = ("r", "g", "b") _PRE_F_LUT_BYTES = 65_536 * 2 +# Keep this in lockstep with native_builder: both producer-side and serialized +# receipt validation accept only the two proven LS-5000 97-dpi geometries. +_SUPPORTED_DENSITY_SOURCE_WIRE_BYTES = frozenset((6_250_496, 5_804_032)) BUILDER_RECEIPT_SCHEMA = "negpy.validated-stage1-builder" STAGE3_REPORT_SCHEMA = "nikonre.ls5000_stage3_validation" FIXED_COMPOSITION_SHA256 = "8729cae5a7aa551ae35926b80d097d73d92ecb6bde471130d6344c6c10ecbe7a" @@ -888,7 +891,8 @@ def _validate_native_evidence_document( or density_evidence_document.get("schema_version") != 1 or density_evidence_document.get("scope") != "reservation-preview" or density_evidence_document.get("per_frame_binding_status") != "requires-explicit-frame-ownership-receipt" - or density_evidence_document.get("source_payload_bytes") != 6_250_496 + or density_evidence_document.get("source_payload_bytes") + not in _SUPPORTED_DENSITY_SOURCE_WIRE_BYTES or density_evidence_document.get("preview_identity_sha256") != receipt.preview_identity_sha256 ): raise ExactColorIntegrityError("native density evidence belongs to another preview or reservation") diff --git a/negpy/services/roll/native_builder.py b/negpy/services/roll/native_builder.py index 03e4adc2..e7d174e6 100644 --- a/negpy/services/roll/native_builder.py +++ b/negpy/services/roll/native_builder.py @@ -20,7 +20,10 @@ ANALYZER_SHAPE: Final = (425, 281, 3) ANALYZER_RESOLUTION_DPI: Final = 285 DENSITY_SOURCE_RESOLUTION_DPI: Final = 97 -DENSITY_SOURCE_WIRE_BYTES: Final = 6_250_496 +# The scanner has two proven 97-dpi roll-preview geometries. The shorter +# variant is produced by the live 37-record full-roll table; accepting it is +# deliberately a closed whitelist, not a variable-length tolerance. +SUPPORTED_DENSITY_SOURCE_WIRE_BYTES: Final = frozenset((6_250_496, 5_804_032)) DENSITY_ARITHMETIC: Final = "ls5000-md3-10088810-layout1-u16-proven-inputs-macos-binary64-exact-v6" FRAME_OWNERSHIP_STATUS: Final = "proven-exact-reservation-preview-registration-and-transport" DENSITY_PER_FRAME_BINDING_STATUS: Final = "requires-explicit-frame-ownership-receipt" @@ -545,7 +548,8 @@ def _validate_density_receipt_binding( or density_receipt.get("scope") != "reservation-preview" or density_receipt.get("per_frame_binding_status") != DENSITY_PER_FRAME_BINDING_STATUS or density_receipt.get("preview_identity_sha256") != evidence.preview_identity_sha256 - or density_receipt.get("source_payload_bytes") != DENSITY_SOURCE_WIRE_BYTES + or density_receipt.get("source_payload_bytes") + not in SUPPORTED_DENSITY_SOURCE_WIRE_BYTES or any(identity != calibration_identity for identity in binding_identities) or calibration_identity[0] != evidence.reservation_id or calibration_identity[2] != evidence.scan_identity diff --git a/tests/roll/test_native_builder.py b/tests/roll/test_native_builder.py index d35e5af3..54bfb087 100644 --- a/tests/roll/test_native_builder.py +++ b/tests/roll/test_native_builder.py @@ -82,6 +82,7 @@ def _density_document( *, numerators: tuple[int, int, int] = (57_114, 48_036, 32_683), density_f03: tuple[int, int, int] = (70_307, 136_614, 125_470), + source_payload_bytes: int = 6_250_496, densities: tuple[float, float, float] = ( struct.unpack(">d", bytes.fromhex("3fd8b159777b9d5f"))[0], struct.unpack(">d", bytes.fromhex("3fe9cc75f7f6705a"))[0], @@ -98,7 +99,7 @@ def _density_document( "scope": "reservation-preview", "per_frame_binding_status": "requires-explicit-frame-ownership-receipt", "preview_identity_sha256": PREVIEW_IDENTITY_SHA256, - "source_payload_bytes": 6_250_496, + "source_payload_bytes": source_payload_bytes, "calibration_binding": { "calibration": { "session_id": RESERVATION_ID, @@ -251,7 +252,9 @@ def _capture_d_evidence() -> NativeBuilderEvidence: ) -def _synthetic_native_evidence() -> NativeBuilderEvidence: +def _synthetic_native_evidence( + *, source_payload_bytes: int = 6_250_496 +) -> NativeBuilderEvidence: """Small deterministic provenance fixture whose analyzer is self-contained.""" numerators = (57_114, 48_036, 32_683) @@ -268,6 +271,7 @@ def _synthetic_native_evidence() -> NativeBuilderEvidence: _density_document( numerators=numerators, density_f03=density_f03, + source_payload_bytes=source_payload_bytes, densities=densities, ) ) @@ -469,6 +473,30 @@ def test_archived_capture_builder_math_reproduces_all_three_pref_luts_byte_exact assert payload["density_source"]["f03_denominators_raw_10ns_rgb"] != payload["analyzer_source"]["final_f02_denominators_raw_10ns_rgb"] +@pytest.mark.parametrize("source_payload_bytes", (6_250_496, 5_804_032)) +def test_native_builder_accepts_each_proven_roll_preview_geometry( + source_payload_bytes: int, +) -> None: + receipt = build_native_builder_receipt( + _synthetic_native_evidence(source_payload_bytes=source_payload_bytes) + ) + + assert exact_color.builder_receipt_payload(receipt)["native_per_acquisition_builder"] is True + + +@pytest.mark.parametrize("source_payload_bytes", (5_804_031, 5_804_033, 6_250_495, 6_250_497)) +def test_native_builder_rejects_near_miss_roll_preview_geometry( + source_payload_bytes: int, +) -> None: + with pytest.raises( + exact_color.ExactColorUnavailable, + match="density evidence belongs to a different preview or reservation", + ): + build_native_builder_receipt( + _synthetic_native_evidence(source_payload_bytes=source_payload_bytes) + ) + + def test_native_receipt_runs_through_stage1_and_cms_with_native_application_binding() -> None: receipt = build_native_builder_receipt(_capture_d_evidence()) source = np.array([[[0, 1, 2], [65_535, 42_000, 17]]], dtype=np.uint16) From 53fee1db20ccd8152bd11d354bd4e31c463a67c7 Mon Sep 17 00:00:00 2001 From: Rohan Pandula Date: Thu, 23 Jul 2026 07:40:45 -0700 Subject: [PATCH 42/48] fix(scanner): retain proven frame approvals --- docs/COOLSCANPY_ROLL_SCANNING.md | 2 +- pyproject.toml | 2 +- uv.lock | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/COOLSCANPY_ROLL_SCANNING.md b/docs/COOLSCANPY_ROLL_SCANNING.md index cc3b3d05..03600c73 100644 --- a/docs/COOLSCANPY_ROLL_SCANNING.md +++ b/docs/COOLSCANPY_ROLL_SCANNING.md @@ -73,7 +73,7 @@ so a signed build fails before release if the JIT exception is absent. If you manage your own environment instead, install the package directly: ``` -pip install "coolscanpy @ git+https://github.com/rohanpandula/coolscanpy.git@12793304be8ca5d7848f269014f721ea09772ca3" +pip install "coolscanpy @ git+https://github.com/rohanpandula/coolscanpy.git@e27f5f773c36f46b8ec6000df2c84eb2813a3dab" ``` The accepted color-negative roll workflow has no SANE dependency. diff --git a/pyproject.toml b/pyproject.toml index 38e5259b..3e03efa0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -60,7 +60,7 @@ coolscan-roll = [ # Pin the reviewed color-negative density/DICE/direct-USB implementation # until its first tagged release. # No SANE dependency for this workflow; see docs/COOLSCANPY_ROLL_SCANNING.md. - "coolscanpy @ git+https://github.com/rohanpandula/coolscanpy.git@1279330", + "coolscanpy @ git+https://github.com/rohanpandula/coolscanpy.git@e27f5f7", ] fauxice = [ "portable-digital-ice @ https://github.com/rohanpandula/digital-fauxice/releases/download/v0.3.1/portable_digital_ice-0.3.1-py3-none-any.whl", diff --git a/uv.lock b/uv.lock index 83e0f930..adad5c86 100644 --- a/uv.lock +++ b/uv.lock @@ -77,7 +77,7 @@ wheels = [ [[package]] name = "coolscanpy" version = "0.1.3" -source = { git = "https://github.com/rohanpandula/coolscanpy.git?rev=1279330#12793304be8ca5d7848f269014f721ea09772ca3" } +source = { git = "https://github.com/rohanpandula/coolscanpy.git?rev=e27f5f7#e27f5f773c36f46b8ec6000df2c84eb2813a3dab" } dependencies = [ { name = "imagecodecs" }, { name = "jinja2" }, @@ -432,7 +432,7 @@ requires-dist = [ [package.metadata.requires-dev] camera = [{ name = "gphoto2", marker = "sys_platform != 'win32'", specifier = ">=2.5" }] -coolscan-roll = [{ name = "coolscanpy", git = "https://github.com/rohanpandula/coolscanpy.git?rev=1279330" }] +coolscan-roll = [{ name = "coolscanpy", git = "https://github.com/rohanpandula/coolscanpy.git?rev=e27f5f7" }] dev = [ { name = "pyinstaller", specifier = ">=6.18.0" }, { name = "pytest", specifier = "==9.0.2" }, From 14375ff0fff152734e2e8c502eb04b9ef6246ff7 Mon Sep 17 00:00:00 2001 From: Rohan Pandula Date: Thu, 23 Jul 2026 08:06:05 -0700 Subject: [PATCH 43/48] fix(scanner): accept live startup tables --- docs/COOLSCANPY_ROLL_SCANNING.md | 2 +- pyproject.toml | 2 +- uv.lock | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/COOLSCANPY_ROLL_SCANNING.md b/docs/COOLSCANPY_ROLL_SCANNING.md index 03600c73..2027d74d 100644 --- a/docs/COOLSCANPY_ROLL_SCANNING.md +++ b/docs/COOLSCANPY_ROLL_SCANNING.md @@ -73,7 +73,7 @@ so a signed build fails before release if the JIT exception is absent. If you manage your own environment instead, install the package directly: ``` -pip install "coolscanpy @ git+https://github.com/rohanpandula/coolscanpy.git@e27f5f773c36f46b8ec6000df2c84eb2813a3dab" +pip install "coolscanpy @ git+https://github.com/rohanpandula/coolscanpy.git@274da866778f23628f41c0161d7baa3bdbd0854e" ``` The accepted color-negative roll workflow has no SANE dependency. diff --git a/pyproject.toml b/pyproject.toml index 3e03efa0..6f02e3c1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -60,7 +60,7 @@ coolscan-roll = [ # Pin the reviewed color-negative density/DICE/direct-USB implementation # until its first tagged release. # No SANE dependency for this workflow; see docs/COOLSCANPY_ROLL_SCANNING.md. - "coolscanpy @ git+https://github.com/rohanpandula/coolscanpy.git@e27f5f7", + "coolscanpy @ git+https://github.com/rohanpandula/coolscanpy.git@274da86", ] fauxice = [ "portable-digital-ice @ https://github.com/rohanpandula/digital-fauxice/releases/download/v0.3.1/portable_digital_ice-0.3.1-py3-none-any.whl", diff --git a/uv.lock b/uv.lock index adad5c86..fc50c926 100644 --- a/uv.lock +++ b/uv.lock @@ -77,7 +77,7 @@ wheels = [ [[package]] name = "coolscanpy" version = "0.1.3" -source = { git = "https://github.com/rohanpandula/coolscanpy.git?rev=e27f5f7#e27f5f773c36f46b8ec6000df2c84eb2813a3dab" } +source = { git = "https://github.com/rohanpandula/coolscanpy.git?rev=274da86#274da866778f23628f41c0161d7baa3bdbd0854e" } dependencies = [ { name = "imagecodecs" }, { name = "jinja2" }, @@ -432,7 +432,7 @@ requires-dist = [ [package.metadata.requires-dev] camera = [{ name = "gphoto2", marker = "sys_platform != 'win32'", specifier = ">=2.5" }] -coolscan-roll = [{ name = "coolscanpy", git = "https://github.com/rohanpandula/coolscanpy.git?rev=e27f5f7" }] +coolscan-roll = [{ name = "coolscanpy", git = "https://github.com/rohanpandula/coolscanpy.git?rev=274da86" }] dev = [ { name = "pyinstaller", specifier = ">=6.18.0" }, { name = "pytest", specifier = "==9.0.2" }, From 62b24e1bac98bf47177e4ca998f4ff719e1ea473 Mon Sep 17 00:00:00 2001 From: Rohan Pandula Date: Thu, 23 Jul 2026 08:37:27 -0700 Subject: [PATCH 44/48] fix(scanner): pin reviewed leading-anchor capture --- docs/COOLSCANPY_ROLL_SCANNING.md | 2 +- pyproject.toml | 2 +- uv.lock | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/COOLSCANPY_ROLL_SCANNING.md b/docs/COOLSCANPY_ROLL_SCANNING.md index 2027d74d..8c2cb737 100644 --- a/docs/COOLSCANPY_ROLL_SCANNING.md +++ b/docs/COOLSCANPY_ROLL_SCANNING.md @@ -73,7 +73,7 @@ so a signed build fails before release if the JIT exception is absent. If you manage your own environment instead, install the package directly: ``` -pip install "coolscanpy @ git+https://github.com/rohanpandula/coolscanpy.git@274da866778f23628f41c0161d7baa3bdbd0854e" +pip install "coolscanpy @ git+https://github.com/rohanpandula/coolscanpy.git@970d18e4b092737a633bd20141d3eaa62b25dcc6" ``` The accepted color-negative roll workflow has no SANE dependency. diff --git a/pyproject.toml b/pyproject.toml index 6f02e3c1..b7819c9f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -60,7 +60,7 @@ coolscan-roll = [ # Pin the reviewed color-negative density/DICE/direct-USB implementation # until its first tagged release. # No SANE dependency for this workflow; see docs/COOLSCANPY_ROLL_SCANNING.md. - "coolscanpy @ git+https://github.com/rohanpandula/coolscanpy.git@274da86", + "coolscanpy @ git+https://github.com/rohanpandula/coolscanpy.git@970d18e", ] fauxice = [ "portable-digital-ice @ https://github.com/rohanpandula/digital-fauxice/releases/download/v0.3.1/portable_digital_ice-0.3.1-py3-none-any.whl", diff --git a/uv.lock b/uv.lock index fc50c926..9cfb2626 100644 --- a/uv.lock +++ b/uv.lock @@ -77,7 +77,7 @@ wheels = [ [[package]] name = "coolscanpy" version = "0.1.3" -source = { git = "https://github.com/rohanpandula/coolscanpy.git?rev=274da86#274da866778f23628f41c0161d7baa3bdbd0854e" } +source = { git = "https://github.com/rohanpandula/coolscanpy.git?rev=970d18e#970d18e4b092737a633bd20141d3eaa62b25dcc6" } dependencies = [ { name = "imagecodecs" }, { name = "jinja2" }, @@ -432,7 +432,7 @@ requires-dist = [ [package.metadata.requires-dev] camera = [{ name = "gphoto2", marker = "sys_platform != 'win32'", specifier = ">=2.5" }] -coolscan-roll = [{ name = "coolscanpy", git = "https://github.com/rohanpandula/coolscanpy.git?rev=274da86" }] +coolscan-roll = [{ name = "coolscanpy", git = "https://github.com/rohanpandula/coolscanpy.git?rev=970d18e" }] dev = [ { name = "pyinstaller", specifier = ">=6.18.0" }, { name = "pytest", specifier = "==9.0.2" }, From a27638346635b24c72675252b9bf43a949f36816 Mon Sep 17 00:00:00 2001 From: Rohan Pandula Date: Thu, 23 Jul 2026 09:10:33 -0700 Subject: [PATCH 45/48] fix(repair): allow verified Hybrid context pixels --- .../services/repair/fauxice_hybrid_runner.py | 1 - negpy/services/roll/service.py | 1 - tests/repair/test_fauxice_hybrid_runner.py | 49 +++++++++++++++++-- tests/roll/test_service.py | 32 +++++++++--- 4 files changed, 72 insertions(+), 11 deletions(-) diff --git a/negpy/services/repair/fauxice_hybrid_runner.py b/negpy/services/repair/fauxice_hybrid_runner.py index 4502c482..4cfc0018 100644 --- a/negpy/services/repair/fauxice_hybrid_runner.py +++ b/negpy/services/repair/fauxice_hybrid_runner.py @@ -982,7 +982,6 @@ def _validate_receipt_bindings( or counts["synthesis_pixels"] != pixel_count or counts["frame_pixels"] != frame_pixels or counts["at_floor_pixels"] > frame_pixels - or counts["synthesis_pixels"] > counts["at_floor_pixels"] or counts["final_regions"] > counts["synthesis_pixels"] or (counts["final_regions"] == 0) != (pixel_count == 0) ): diff --git a/negpy/services/roll/service.py b/negpy/services/roll/service.py index 01dfaee5..90424ffa 100644 --- a/negpy/services/roll/service.py +++ b/negpy/services/roll/service.py @@ -2176,7 +2176,6 @@ def _validate_repair_result_binding( or counts.get("synthesis_pixels") != routed_pixels or counts.get("frame_pixels") != frame_pixels or counts["at_floor_pixels"] > frame_pixels - or counts["synthesis_pixels"] > counts["at_floor_pixels"] or counts["final_regions"] > counts["synthesis_pixels"] or (counts["final_regions"] == 0) != (routed_pixels == 0) ): diff --git a/tests/repair/test_fauxice_hybrid_runner.py b/tests/repair/test_fauxice_hybrid_runner.py index 8aed0b42..13dfe90e 100644 --- a/tests/repair/test_fauxice_hybrid_runner.py +++ b/tests/repair/test_fauxice_hybrid_runner.py @@ -66,8 +66,19 @@ def _rgbi(height: int, width: int, base: int) -> np.ndarray: return np.full((height, width, 4), base, dtype=np.uint16) -def _receipt(*, requested: str = "cpu", used: str = "cpu", fraction: float = 0.01) -> dict: - return {"fraction": fraction, "requested": requested, "used": used} +def _receipt( + *, + requested: str = "cpu", + used: str = "cpu", + fraction: float = 0.01, + at_floor_pixels: int | None = None, +) -> dict: + return { + "at_floor_pixels": at_floor_pixels, + "fraction": fraction, + "requested": requested, + "used": used, + } def _arg(argv: list[str], flag: str) -> str: @@ -171,7 +182,11 @@ def _write_success_outputs( }, "routing": { "counts": { - "at_floor_pixels": synthesis_pixels, + "at_floor_pixels": ( + synthesis_pixels + if receipt_options["at_floor_pixels"] is None + else receipt_options["at_floor_pixels"] + ), "final_regions": 1 if synthesis_pixels else 0, "frame_pixels": int(mask.size), "synthesis_pixels": synthesis_pixels, @@ -444,6 +459,34 @@ def test_receipt_fields_are_extracted(self, tmp_path: Path) -> None: "at_floor_pixels": 1, } + def test_accepts_synthesis_context_outside_at_floor_evidence( + self, + tmp_path: Path, + ) -> None: + hybrid_rgb16 = np.zeros((2, 2, 3), dtype=np.uint16) + runner = _RecordingRunner( + hybrid_rgb16=hybrid_rgb16, + receipt=_receipt(fraction=0.5, at_floor_pixels=1), + ) + + result = run_hybrid_repair( + _rgbi(2, 2, 1000), + _rgbi(2, 2, 500), + same_frame_id="context-halo", + backend="cpu", + runtime=_runtime_config(tmp_path), + scratch_dir=tmp_path, + runner=runner, + receipt_verifier=_test_receipt_verifier, + ) + + assert result.routing_counts == { + "final_regions": 1, + "synthesis_pixels": 2, + "frame_pixels": 4, + "at_floor_pixels": 1, + } + def test_rejects_receipt_that_omits_routing_counts(self, tmp_path: Path) -> None: hybrid_rgb16 = np.zeros((2, 2, 3), dtype=np.uint16) base_runner = _RecordingRunner(hybrid_rgb16=hybrid_rgb16, receipt=_receipt()) diff --git a/tests/roll/test_service.py b/tests/roll/test_service.py index ea1cbcbe..d450f83a 100644 --- a/tests/roll/test_service.py +++ b/tests/roll/test_service.py @@ -412,10 +412,13 @@ def _tier_frame( def _valid_hybrid_result( acquisition: roll_repair.RepairAcquisition, + *, + routed_pixel_count: int = 1, + at_floor_pixel_count: int = 1, ) -> roll_repair.RepairResult: native_rgb = np.ascontiguousarray(acquisition.main_rgbi[..., :3] + 3) routed_mask = np.zeros(acquisition.main_rgbi.shape[:2], dtype=np.bool_) - routed_mask.reshape(-1)[0] = True + routed_mask.reshape(-1)[:routed_pixel_count] = True applied_mask = np.ascontiguousarray(routed_mask & acquisition.ir_validity) def png(mask: np.ndarray) -> bytes: @@ -432,10 +435,10 @@ def png(mask: np.ndarray) -> bytes: storage_png = png(storage_mask) native_hash = sha256(native_rgb.astype(" bytes: "routing": {"counts": counts}, "schema": "fauxce-hybrid-receipt-v2", "synthesis": { - "fraction": 1 / routed_mask.size, + "fraction": routed_pixel_count / routed_mask.size, "frame_pixel_count": routed_mask.size, - "pixel_count": 1, + "pixel_count": routed_pixel_count, "within_budget": True, }, } @@ -1168,6 +1171,23 @@ def fail_after_partial_hybrid_evidence(path, contents): replaced_payload = self._receipt(replacement.receipt_path)["outputs"] assert replaced_payload["repaired"]["status"] == "not selected" + def test_hybrid_result_binding_accepts_synthesis_context_outside_at_floor_evidence( + self, + fake_coolscanpy, + ) -> None: + acquisition = _tier_frame(fake_coolscanpy).prepare_digital_ice() + result = _valid_hybrid_result( + acquisition, + routed_pixel_count=2, + at_floor_pixel_count=1, + ) + + roll_service._validate_repair_result_binding( + acquisition, + result, + requested_mode=roll_repair.RepairMode.HYBRID, + ) + @pytest.mark.parametrize( ("tamper", "match"), [ From c918696da156616a68e49b10d10074360f9cacaa Mon Sep 17 00:00:00 2001 From: Rohan Pandula Date: Thu, 23 Jul 2026 09:25:58 -0700 Subject: [PATCH 46/48] fix(ui): auto-tone roll preview exposure --- negpy/desktop/view/sidebar/coolscan_roll.py | 78 +++++++++++++++------ tests/roll/test_sidebar.py | 32 ++++++++- 2 files changed, 86 insertions(+), 24 deletions(-) diff --git a/negpy/desktop/view/sidebar/coolscan_roll.py b/negpy/desktop/view/sidebar/coolscan_roll.py index 1dbb782e..a554ed14 100644 --- a/negpy/desktop/view/sidebar/coolscan_roll.py +++ b/negpy/desktop/view/sidebar/coolscan_roll.py @@ -57,27 +57,59 @@ _THUMBNAIL_SIZE = QSize(220, 150) -def _thumbnail_pixmap(image: "np.ndarray", *, inverted: bool = False) -> QPixmap: - """A `coolscanpy.Thumbnail.image` array as a displayable QPixmap. - - Deliberately dumb: this is a raw scanner preview frame for framing and - review, not a NegPy pipeline buffer, so it does not go through - `ImageConverter.to_qimage`'s color management -- that helper assumes a - working-space buffer this array isn't. Normalizes whatever numeric - range/dtype/channel-count arrives to 8-bit RGB. +def _thumbnail_rgb8( + image: "np.ndarray", + *, + positive: bool = False, +) -> np.ndarray: + """Tone a scanner-linear roll thumbnail for display without mutating it. + + The 97-dpi index is scanner-linear negative transmission, not a display + image. A peak-normalize followed by ``255 - value`` makes ordinary C-41 + frames look pale cyan and badly overexposed, especially when one hot + pixel or rail sets the peak. Raw display therefore ignores only the + extreme highlight tail. Positive display converts transmission to + optical density, applies robust per-channel endpoints, then a display + gamma. This is intentionally an auto-toned review preview; the saved + Nikon-exact positive still comes from the acquisition-bound builder/CMS. """ arr = np.asarray(image) if arr.ndim == 2: arr = np.stack([arr] * 3, axis=-1) - if arr.dtype != np.uint8: - peak = float(arr.max()) if arr.size else 1.0 - scale = 255.0 if peak <= 1.0 else (255.0 / peak if peak > 255.0 else 1.0) - arr = np.clip(arr.astype(np.float64) * scale, 0, 255).astype(np.uint8) - arr = np.ascontiguousarray(arr[..., :3]) - if inverted: - # Display only. The scanner thumbnail remains untouched and this never - # enters the capture or output-color pipelines. - arr = np.ascontiguousarray(255 - arr) + if arr.ndim != 3 or arr.shape[2] < 3: + raise ValueError("roll thumbnail must be a grayscale or RGB image") + rgb = np.asarray(arr[..., :3], dtype=np.float64) + if not rgb.size: + return np.empty((*rgb.shape[:2], 3), dtype=np.uint8) + finite = np.where(np.isfinite(rgb), rgb, 0.0) + finite = np.maximum(finite, 0.0) + + if not positive: + white = max(float(np.percentile(finite, 99.5)), 1.0) + display = np.clip(finite / white, 0.0, 1.0) + else: + channel_white = np.maximum( + np.percentile(finite, 99.5, axis=(0, 1)), + 1.0, + ) + transmission = np.clip( + finite / channel_white, + 1.0 / 65535.0, + 1.0, + ) + density = -np.log10(transmission) + black = np.percentile(density, 1.0, axis=(0, 1)) + white = np.percentile(density, 99.0, axis=(0, 1)) + span = np.maximum(white - black, np.finfo(np.float64).eps) + display = np.clip((density - black) / span, 0.0, 1.0) + display = np.sqrt(display) + + return np.ascontiguousarray(np.rint(display * 255.0).astype(np.uint8)) + + +def _thumbnail_pixmap(image: "np.ndarray", *, positive: bool = False) -> QPixmap: + """A `coolscanpy.Thumbnail.image` array as a displayable QPixmap.""" + arr = _thumbnail_rgb8(image, positive=positive) h, w = arr.shape[:2] qimg = QImage(arr.data, w, h, w * 3, QImage.Format.Format_RGB888).copy() # copy: detach from arr's buffer return QPixmap.fromImage(qimg) @@ -110,13 +142,13 @@ def __init__(self) -> None: mode_label.setStyleSheet(f"color: {THEME.text_secondary};") self.display_mode_combo = QComboBox() self.display_mode_combo.setObjectName("roll_preview_display_mode") - self.display_mode_combo.addItem("Positive (inverted)", True) + self.display_mode_combo.addItem("Positive preview (auto tone)", True) self.display_mode_combo.addItem("Negative (raw)", False) self.display_mode_combo.setMinimumHeight(40) self.display_mode_combo.setMinimumWidth(180) self.display_mode_combo.setToolTip( - "Display only: Positive inverts the preview for easier review. " - "Negative shows the scanner thumbnail without inversion. Neither changes capture bytes or output color." + "Display only: Positive preview applies optical-density auto tone for exposure and framing review. " + "Negative shows the scanner-linear thumbnail. Neither changes capture bytes or the saved Nikon-exact color." ) self.back_btn = QPushButton("Back to image editor") self.back_btn.setMinimumHeight(40) @@ -563,7 +595,7 @@ def _on_preview_ready(self, thumbnails: "list[coolscanpy.Thumbnail]") -> None: self._apply_gating() def _add_slot_item(self, thumb: "coolscanpy.Thumbnail") -> None: - pixmap = _thumbnail_pixmap(thumb.image, inverted=self._preview_is_inverted()).scaled( + pixmap = _thumbnail_pixmap(thumb.image, positive=self._preview_is_positive()).scaled( _THUMBNAIL_SIZE, Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation ) item = QListWidgetItem(QIcon(pixmap), f"Slot {thumb.slot}" + (" ⚠" if thumb.needs_approval else "")) @@ -572,7 +604,7 @@ def _add_slot_item(self, thumb: "coolscanpy.Thumbnail") -> None: item.setForeground(QColor(_WARN_COLOR)) self.contact_sheet.addItem(item) - def _preview_is_inverted(self) -> bool: + def _preview_is_positive(self) -> bool: return bool(self.preview_display_combo.currentData()) def _on_preview_display_changed(self) -> None: @@ -583,7 +615,7 @@ def _on_preview_display_changed(self) -> None: thumb = self._thumbnails.get(item.data(_SLOT_ROLE)) if thumb is None: continue - pixmap = _thumbnail_pixmap(thumb.image, inverted=self._preview_is_inverted()).scaled( + pixmap = _thumbnail_pixmap(thumb.image, positive=self._preview_is_positive()).scaled( _THUMBNAIL_SIZE, Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation, diff --git a/tests/roll/test_sidebar.py b/tests/roll/test_sidebar.py index f82e2a1a..48aa48eb 100644 --- a/tests/roll/test_sidebar.py +++ b/tests/roll/test_sidebar.py @@ -14,7 +14,11 @@ from PyQt6.QtWidgets import QApplication from unittest.mock import MagicMock -from negpy.desktop.view.sidebar.coolscan_roll import CoolscanRollSidebar, QMessageBox +from negpy.desktop.view.sidebar.coolscan_roll import ( + CoolscanRollSidebar, + QMessageBox, + _thumbnail_rgb8, +) from negpy.infrastructure.roll import coolscanpy_roll if not QApplication.instance(): @@ -109,6 +113,32 @@ def test_approve_button_hidden_without_a_selection(self) -> None: class TestPreview: + def test_positive_preview_auto_tones_scanner_linear_negative(self) -> None: + levels = np.linspace(0.0, 1.0, 64, dtype=np.float64).reshape(8, 8, 1) + black = np.array([500.0, 350.0, 250.0]).reshape(1, 1, 3) + film_base = np.array([35_000.0, 27_000.0, 22_000.0]).reshape(1, 1, 3) + scanner_linear = np.rint(black + levels * (film_base - black)).astype( + np.uint16 + ) + original = scanner_linear.copy() + + positive = _thumbnail_rgb8(scanner_linear, positive=True) + + assert positive.dtype == np.uint8 + assert positive.shape == scanner_linear.shape + assert int(np.percentile(positive, 5)) < 80 + assert int(np.percentile(positive, 95)) > 200 + np.testing.assert_array_equal(scanner_linear, original) + + def test_raw_preview_ignores_one_hot_pixel(self) -> None: + scanner_linear = np.full((20, 20, 3), 16_000, dtype=np.uint16) + scanner_linear[0, 0] = 65_535 + + raw = _thumbnail_rgb8(scanner_linear, positive=False) + + assert int(np.median(raw)) > 240 + assert raw[0, 0].tolist() == [255, 255, 255] + def test_preview_click_starts_request_for_selected_device(self, fake_coolscanpy) -> None: w = _sidebar() _pick_device(w, device_id="dev-42") From 932139450f82fb9d8faec874dc33f5362557e0d9 Mon Sep 17 00:00:00 2001 From: Rohan Pandula Date: Thu, 23 Jul 2026 10:28:04 -0700 Subject: [PATCH 47/48] fix(scanner): accept dated roll output names --- docs/CHANGELOG.md | 3 +++ docs/COOLSCANPY_ROLL_SCANNING.md | 35 +++++++++++++++----------- negpy/services/roll/deep_acceptance.py | 11 +++++--- tests/roll/test_deep_acceptance.py | 2 +- 4 files changed, 32 insertions(+), 19 deletions(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 4e8d9a66..2e01cdd1 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -3,6 +3,9 @@ ## 0.41.0 - New: **Stitch multi-shot scans** — select overlapping shots of one frame (e.g. a 6×6 scanned in two halves) on the contact sheet and pick **Stitch selected frames**. Alignment, exposure matching and blending happen on the linear scan data before conversion, so the result develops like a single raw. No new file is written: the composite edits and exports like any frame, and **Unstitch** restores the parts. IR dust data is kept when all parts have it. +- New: **Direct Nikon LS-5000 whole-roll scanning** — NegPy can drive a converted SA-21/SA-30 feeder through CoolscanPy, preview and approve the roll, batch-capture 16-bit RGBI, retain scanner evidence, and publish unrepaired, Digital ICE/Hybrid-repaired, and byte-verified Nikon-colour positive tiers. Hybrid synthesis is configurable and fail-closed at a maximum of 10%, with a per-frame recommendation explaining when it is useful. +- New: **Roll-scanner controls that match the workflow** — the contact sheet is shown in the central viewer, with a raw-negative/auto-toned-positive preview toggle, automatic crop and orientation support, Safe Stop, and a one-click **Eject Roll** action that invalidates the old registration after a confirmed eject. +- Fix: **Roll previews no longer look washed out** — preview exposure now ignores isolated hot pixels and the positive view tones optical density per channel. This changes display thumbnails only; saved Nikon-exact TIFF pixels and profiles are untouched. ## 0.40.0 diff --git a/docs/COOLSCANPY_ROLL_SCANNING.md b/docs/COOLSCANPY_ROLL_SCANNING.md index 8c2cb737..9601d273 100644 --- a/docs/COOLSCANPY_ROLL_SCANNING.md +++ b/docs/COOLSCANPY_ROLL_SCANNING.md @@ -37,8 +37,8 @@ gphoto2: uv sync --group coolscan-roll ``` -That group pins the reviewed immutable commit -`e42c68fb53b74aee9fc29eae432d092de351001d`, built on the fixed-size +That group pins the live-validated immutable commit +`970d18e4b092737a633bd20141d3eaa62b25dcc6`, built on the fixed-size Nikon frame-table repair and the merged streaming finalization from [PR #1](https://github.com/rohanpandula/coolscanpy/pull/1). The pin includes exact USB-topology ownership, six-strip leading-edge @@ -85,12 +85,12 @@ the coolscanpy README for that setup. coolscanpy, and by extension this integration, has been validated against one specific setup: a Nikon Super Coolscan 5000 ED (LS-5000) running firmware 1.03, with an SA-21 roll feeder wired for SA-30 compatibility. -Live hardware validation on 2026-07-18 confirmed USB enumeration, device -open, and a full roll preview with correct spacing and manual-review -flagging. Fine scanning and infrared capture through the roll engine have -not yet been re-run live since coolscanpy was extracted into its own -package. That is the remaining validation step on the coolscanpy side, not -something specific to this NegPy integration. +Live hardware acceptance completed on 2026-07-23: USB enumeration, device +ownership, a 36-frame preview with manual-review flagging, and 36 fine scans +at 4,000 dpi/16-bit RGBI all completed against that pin. Every frame recorded +2,980 reads and 619,458,560 capture bytes. The retained journals and scanner +evidence are banked under +`reverse_engineering/results/NEGPY-LS5000-FULL-ROLL-LIVE-20260723-A/evidence/`. Every other Coolscan model and every roll feeder other than the SA-21/SA-30 combination above is untested. The transport protocol is not assumed to be @@ -468,11 +468,13 @@ coolscanpy is not importable, the same way `ScanlightSidebar` handles a missing gphoto2. The panel covers device selection, a whole-roll preview rendered as a -thumbnail contact sheet, a spacing-offset spinner and an approve button -for whichever slot is selected, and a batch scan of every selected slot -with a progress bar and a Safe Stop button. Select All Frames makes a -full-roll batch explicit instead of requiring every thumbnail to be -clicked individually. +thumbnail contact sheet in the app's central viewing area, a spacing-offset +spinner and an approve button for whichever slot is selected, and a batch +scan of every selected slot with a progress bar and a Safe Stop button. +Select All Frames makes a full-roll batch explicit instead of requiring +every thumbnail to be clicked individually. The preview can be toggled +between the scanner negative and an auto-toned positive display; this is a +display-only transform and never changes the Nikon-exact saved TIFF. Eject Roll uses the same proven SANE `scanimage --eject` transport action as NegPy's ordinary scanner panel. It first closes the coolscanpy roll @@ -520,5 +522,8 @@ not on a missing fine-scan implementation. A contact sheet thumbnail is a raw scanner preview frame, not a NegPy pipeline buffer, so it is converted to a displayable image with a small local helper rather than going through NegPy's color-managed -`ImageConverter`. That converter assumes a working-space buffer a -scanner's preview array is not. +`ImageConverter`. Raw display exposure uses a robust high percentile, while +positive display uses optical-density inversion, robust per-channel +endpoints, and a display gamma. Hot pixels therefore cannot wash out the +entire contact sheet. `ImageConverter` is not used because it assumes a +working-space buffer a scanner's preview array is not. diff --git a/negpy/services/roll/deep_acceptance.py b/negpy/services/roll/deep_acceptance.py index 490de50e..4accc2ec 100644 --- a/negpy/services/roll/deep_acceptance.py +++ b/negpy/services/roll/deep_acceptance.py @@ -1271,9 +1271,14 @@ def _validate_frame( acquisition = roll_service.load_repair_acquisition_evidence(binding_path) except (OSError, TypeError, ValueError) as error: raise DeepAcceptanceError(f"{label}: DICE replay failed: {error}") from error - expected_rgb_name = f"acceptance_slot{expected_slot:02d}.tif" - expected_dice_token = _sha256(f"{acquisition.acquisition_id}\0acceptance_slot{expected_slot:02d}".encode("utf-8")) - if rgb_path.name != expected_rgb_name or binding_path.parent.name != expected_dice_token: + # Production frame validation is also used for ordinary dated output + # names. The live-acceptance inventory separately enforces its fixed + # ``acceptance_slotNN`` pattern; the archive itself is content-addressed + # against whichever basename RollScanningService actually published. + expected_dice_token = _sha256( + f"{acquisition.acquisition_id}\0{rgb_path.stem}".encode("utf-8") + ) + if binding_path.parent.name != expected_dice_token: _fail(label, "DICE evidence directory is not content-addressed") acquisition_entry = _require_dict(repaired.get("acquisition"), label=f"{label} repaired acquisition") expected_acquisition = { diff --git a/tests/roll/test_deep_acceptance.py b/tests/roll/test_deep_acceptance.py index 6a73ad20..e21a5ba1 100644 --- a/tests/roll/test_deep_acceptance.py +++ b/tests/roll/test_deep_acceptance.py @@ -718,7 +718,7 @@ def repair( output = RollScanningService(hybrid_runtime=runtime).write_frame( frame, str(tmp_path), - 'acceptance_slot{{ "%02d" % seq }}', + '20260723_{{ "%03d" % seq }}', write_unrepaired=True, write_repaired=True, write_positive=True, From 49cd69ae14379909149f56d720d25ee50354abe1 Mon Sep 17 00:00:00 2001 From: Rohan Pandula Date: Thu, 23 Jul 2026 10:41:39 -0700 Subject: [PATCH 48/48] fix(scanner): clarify uncertain eject timeout --- docs/CHANGELOG.md | 1 + docs/COOLSCANPY_ROLL_SCANNING.md | 6 +++++ negpy/desktop/view/sidebar/coolscan_roll.py | 2 +- negpy/infrastructure/scanners/sane_backend.py | 5 +++- tests/roll/test_sidebar.py | 1 + tests/scanners/test_scanner_eject.py | 25 ++++++++++++++++++- 6 files changed, 37 insertions(+), 3 deletions(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 2e01cdd1..1bef3f92 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -6,6 +6,7 @@ - New: **Direct Nikon LS-5000 whole-roll scanning** — NegPy can drive a converted SA-21/SA-30 feeder through CoolscanPy, preview and approve the roll, batch-capture 16-bit RGBI, retain scanner evidence, and publish unrepaired, Digital ICE/Hybrid-repaired, and byte-verified Nikon-colour positive tiers. Hybrid synthesis is configurable and fail-closed at a maximum of 10%, with a per-frame recommendation explaining when it is useful. - New: **Roll-scanner controls that match the workflow** — the contact sheet is shown in the central viewer, with a raw-negative/auto-toned-positive preview toggle, automatic crop and orientation support, Safe Stop, and a one-click **Eject Roll** action that invalidates the old registration after a confirmed eject. - Fix: **Roll previews no longer look washed out** — preview exposure now ignores isolated hot pixels and the positive view tones optical density per channel. This changes display thumbnails only; saved Nikon-exact TIFF pixels and profiles are untouched. +- Fix: **A timed-out roll eject no longer reads like a definite failure** — the button stays latched against a second transport action and explains that the eject may already have been dispatched, so the operator checks the film physically instead of retrying. ## 0.40.0 diff --git a/docs/COOLSCANPY_ROLL_SCANNING.md b/docs/COOLSCANPY_ROLL_SCANNING.md index 9601d273..7749e0dd 100644 --- a/docs/COOLSCANPY_ROLL_SCANNING.md +++ b/docs/COOLSCANPY_ROLL_SCANNING.md @@ -485,6 +485,12 @@ panel deliberately disables preview and further eject attempts for the rest of that app session: the physical result is uncertain and a blind retry could move film twice. +The 30-second software wait is only a bound on how long NegPy waits for +`scanimage` to confirm the action; it is not proof that the transport failed. +If that wait expires, the button action may already have been dispatched. +The panel keeps the operation latched, reports the outcome as uncertain, and +requires a physical check rather than a software retry. + The Output section has three checkboxes, one per tier, plus a repair-mode dropdown that governs Tier 2. Any combination of the three checkboxes is valid, and at least one must be checked before Scan Selected enables. diff --git a/negpy/desktop/view/sidebar/coolscan_roll.py b/negpy/desktop/view/sidebar/coolscan_roll.py index a554ed14..29fb1c55 100644 --- a/negpy/desktop/view/sidebar/coolscan_roll.py +++ b/negpy/desktop/view/sidebar/coolscan_roll.py @@ -514,7 +514,7 @@ def _on_eject_clicked(self) -> None: self._eject_pending = True self._eject_latched = True self._clear_contact_sheet() - self._set_status("Ejecting roll…") + self._set_status("Ejecting roll… Do not press Eject again; wait for the film or a result.") self._apply_gating() self.controller.eject_roll(device_id) diff --git a/negpy/infrastructure/scanners/sane_backend.py b/negpy/infrastructure/scanners/sane_backend.py index 0ab0aac3..fb1b44c4 100644 --- a/negpy/infrastructure/scanners/sane_backend.py +++ b/negpy/infrastructure/scanners/sane_backend.py @@ -354,7 +354,10 @@ def _scanimage_eject(device_id: str) -> None: except FileNotFoundError as exc: raise RuntimeError("Cannot eject: `scanimage` (sane-utils) is not installed") from exc except subprocess.TimeoutExpired as exc: - raise RuntimeError(f"Eject timed out after {_EJECT_TIMEOUT_S:g}s for {device_id!r}") from exc + raise RuntimeError( + f"Eject confirmation timed out after {_EJECT_TIMEOUT_S:g}s for {device_id!r}; " + "the button action may already have been dispatched. Check the film physically and do not retry" + ) from exc if proc.returncode == 0: return diff --git a/tests/roll/test_sidebar.py b/tests/roll/test_sidebar.py index 48aa48eb..0cf03015 100644 --- a/tests/roll/test_sidebar.py +++ b/tests/roll/test_sidebar.py @@ -302,6 +302,7 @@ def test_confirmation_ejects_selected_direct_usb_device( assert w.eject_btn.isEnabled() is False assert w.preview_btn.isEnabled() is False assert "Ejecting" in w.status_label.text() + assert "do not press eject again" in w.status_label.text().lower() def test_confirmation_cancel_does_not_eject(self, monkeypatch) -> None: w = _sidebar() diff --git a/tests/scanners/test_scanner_eject.py b/tests/scanners/test_scanner_eject.py index 0bd975cb..798796e0 100644 --- a/tests/scanners/test_scanner_eject.py +++ b/tests/scanners/test_scanner_eject.py @@ -235,13 +235,36 @@ def test_detection_handle_is_closed_before_scanimage_opens_it(self, monkeypatch: def test_spurious_out_of_documents_exit_is_treated_as_success(self, monkeypatch: pytest.MonkeyPatch) -> None: dev = FakeSaneDev(dict(COOLSCAN3_OPT_WITH_EJECT)) backend = _make_backend(FakeSaneModule(dev)) - _patch_run( + run = _patch_run( monkeypatch, returncode=7, stderr="scanimage: sane_start: Document feeder out of documents\n", ) assert backend.eject(_DEV) is True + assert run.calls[0][1]["timeout"] == sane_backend._EJECT_TIMEOUT_S + + def test_timeout_reports_that_eject_may_have_dispatched_and_forbids_retry( + self, + monkeypatch: pytest.MonkeyPatch, + ) -> None: + dev = FakeSaneDev(dict(COOLSCAN3_OPT_WITH_EJECT)) + backend = _make_backend(FakeSaneModule(dev)) + _patch_run( + monkeypatch, + raises=sane_backend.subprocess.TimeoutExpired( + ["scanimage", "-d", _DEV, "--eject"], + timeout=sane_backend._EJECT_TIMEOUT_S, + ), + ) + + with pytest.raises(RuntimeError) as exc_info: + backend.eject(_DEV) + + message = str(exc_info.value) + assert "may already have been dispatched" in message + assert "Check the film physically" in message + assert "do not retry" in message def test_capability_gated_skip_when_device_has_no_eject_option(self, monkeypatch: pytest.MonkeyPatch) -> None: dev = FakeSaneDev(dict(COOLSCAN3_OPT_NO_EJECT))