From 4018e886686c1fa3125f6c1e9a912ce124e366b2 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Tue, 29 Sep 2026 14:27:58 -0400 Subject: [PATCH 01/19] Fix zero-overlap and zero-jitter synthetic plate generation (#172) --- .../input_preparation_checkpoint_20260929.md | 69 ++++++++++++++ openhcs/demo/synthetic_data.py | 60 +++--------- ...t_synthetic_plate_zero_geometry_journey.py | 94 +++++++++++++++++++ .../test_synthetic_generator_zero_geometry.py | 64 +++++++++++++ 4 files changed, 242 insertions(+), 45 deletions(-) create mode 100644 docs/plans/input_preparation_checkpoint_20260929.md create mode 100644 tests/unit/agent/test_synthetic_plate_zero_geometry_journey.py create mode 100644 tests/unit/test_synthetic_generator_zero_geometry.py diff --git a/docs/plans/input_preparation_checkpoint_20260929.md b/docs/plans/input_preparation_checkpoint_20260929.md new file mode 100644 index 0000000000..eeb74458b6 --- /dev/null +++ b/docs/plans/input_preparation_checkpoint_20260929.md @@ -0,0 +1,69 @@ +# Input preparation checkpoint + +Implementation owner: Lovelace. Integration owner: OpenHCS coordinator. +Branch: `fix/input-preparation-20260929`. +Worktree: `/home/ts/wt/openhcs-input-preparation-20260929`. +Issues: #172 and #132. + +## First checkpoint: #172 + +Zero pixel overlap uses unrestricted cell positions, not empty random ranges. +Zero stage error uses exact grid positions, without consuming jitter draws. +Both layouts share the existing generator's tile-position behavior. Positive +jitter retains the original MT19937 sequence. No parameters are replaced with +nonzero defaults. + +Focused generator regressions cover ImageXpress and OperaPhenix, native and +vendor metadata, 1x1 and 2x2 grids, requested counts, deterministic pixels and +subpixel overlap. The existing randomness controls are retained. + +The continuous MCP regression uses the current real stdio entrypoint, generates +the issue's 1x1/64px/one-channel/one-Z/four-cell/A01/seed7 plate, inspects it and +samples pixels. It does not replace the transport, services or workspace owners +with mocks. It does not use a GUI, biological input or a paid provider. + +## Continuing scope: #132 + +Push known physical path selections ahead of unrelated store opens using the +source declaration and its existing filter matcher owners. Metadata and axis +selection remain at decoded plane projection. Synthetic tests measure opened +entrypoints and exact projected references; no real 16-CZI run is authorized. + +Contour extraction and ImageJ ZIP encoding belong to the recorded PolyStore +submodule. Coordinate permission before changing it. Fragmented labels are +parent objects with many shapes; ZIP member count is not a biological count. +Remaining work: fragmented fidelity/profile receipt and bounded repeated-work +improvement, review of companion-file selection, coordinator installed journey. + +## Validation and resource boundary + +All eight dependency imports were verified under this worktree's recorded +submodule roots, using `/home/ts/code/projects/openhcs/.venv/bin/python` and an +explicit worktree/submodule `PYTHONPATH`. No package installation or shared +source change. Main was fetched and integrated normally before implementation. + +Focused source evidence: 20 tests passed in 2.32 seconds (zero geometry, +randomness, and preparation selections). This is not a full NRA scan or broad +suite. Source review is limited to the source declaration, store adapters, +workspace handler, synthetic generator and their regression witnesses. + +Real current-entrypoint evidence: four continuous MCP health, orientation, +generate, inspect and sample journeys passed in 23.19 seconds. Layouts: +ImageXpress and OperaPhenix, each with vendor and native OpenHCS metadata. +Each returns one requested uint16 image and an 8x8 pixel sample; native +metadata records one image and the requested 1x1 grid. This uses source overlays +with verified imports, not a changed installed package or an installed acceptance +claim. New test files pass Ruff; full-file Ruff reports existing source debt. + +The resource guard reported 12.8 GiB available RAM with a swap-only warning +(11.7 GiB used). Only small single-process provider-free checks are run; +large/GUI/JVM work is deferred. Tests/MCP acquire the batch's nonblocking +validation lock. Busy lock means independent source work, not polling. + +Scratch owner: Lovelace. Purpose: tiny generated regression plates and pytest +fixtures. Path: `/home/ts/.cache/agent-scratch/openhcs-issue-input-20260929`. +Remove only this owned disposable output after preserving results here. +Source, PR body and evidence stay in the persistent worktree. + +No merge/install performed. Installed acceptance is reserved to the coordinator. +Do not close either issue solely from source tests. diff --git a/openhcs/demo/synthetic_data.py b/openhcs/demo/synthetic_data.py index c87f8b0ee1..8a277bb2ec 100644 --- a/openhcs/demo/synthetic_data.py +++ b/openhcs/demo/synthetic_data.py @@ -240,7 +240,7 @@ def __init__( # Very strongly favor overlap regions with 80% probability # to ensure very high density of features in the 10% overlap region for reliable registration - if self._random.random() < 0.8: + if overlap_x > 0 and overlap_y > 0 and self._random.random() < 0.8: # Position in an overlap region between tiles col = self._random.randint(0, grid_size[1]) row = self._random.randint(0, grid_size[0]) @@ -824,6 +824,18 @@ def _calculate_image_size( return (width, height) + def _site_position(self, row: int, col: int) -> tuple[int, int]: + """Position a tile, preserving an exact grid when stage jitter is zero.""" + x = col * self.step_x + y = row * self.step_y + if self.stage_error_px > 0: + x += self._random.randint(-self.stage_error_px, self.stage_error_px) + y += self._random.randint(-self.stage_error_px, self.stage_error_px) + return ( + max(0, min(x, self.image_size[0] - self.tile_size[0])), + max(0, min(y, self.image_size[1] - self.tile_size[1])), + ) + def generate_dataset(self): """Generate the complete dataset.""" print(f"Generating synthetic microscopy dataset in {self.output_dir}") @@ -860,55 +872,13 @@ def generate_dataset(self): # Get grid position for this field ID grid_row, grid_col = field_pattern[site_index] - # Calculate base position - x = grid_col * self.step_x - y = grid_row * self.step_y - - # Add random stage positioning error - x_error = self._random.randint( - -self.stage_error_px, self.stage_error_px - ) - y_error = self._random.randint( - -self.stage_error_px, self.stage_error_px - ) - - x_pos = x + x_error - y_pos = y + y_error - - # Ensure we don't go out of bounds - x_pos = max(0, min(x_pos, self.image_size[0] - self.tile_size[0])) - y_pos = max(0, min(y_pos, self.image_size[1] - self.tile_size[1])) - - site_positions[site_index] = (x_pos, y_pos) + site_positions[site_index] = self._site_position(grid_row, grid_col) else: # ImageXpress: simple raster pattern site_index = 1 for row in range(self.grid_size[0]): for col in range(self.grid_size[1]): - # Calculate base position - x = col * self.step_x - y = row * self.step_y - - # Add random stage positioning error - x_error = self._random.randint( - -self.stage_error_px, self.stage_error_px - ) - y_error = self._random.randint( - -self.stage_error_px, self.stage_error_px - ) - - x_pos = x + x_error - y_pos = y + y_error - - # Ensure we don't go out of bounds - x_pos = max( - 0, min(x_pos, self.image_size[0] - self.tile_size[0]) - ) - y_pos = max( - 0, min(y_pos, self.image_size[1] - self.tile_size[1]) - ) - - site_positions[site_index] = (x_pos, y_pos) + site_positions[site_index] = self._site_position(row, col) site_index += 1 # For multiple Z-stack levels diff --git a/tests/unit/agent/test_synthetic_plate_zero_geometry_journey.py b/tests/unit/agent/test_synthetic_plate_zero_geometry_journey.py new file mode 100644 index 0000000000..439d49911b --- /dev/null +++ b/tests/unit/agent/test_synthetic_plate_zero_geometry_journey.py @@ -0,0 +1,94 @@ +"""Continuous real MCP generate/inspect/sample for zero-valued geometry.""" + +import json +from pathlib import Path + +import pytest + +from openhcs.mcp.dev_client import McpDevClient + + +def _payload(client, argv): + result = client.execute([*argv, "--json"], timeout_seconds=30) + assert result.returncode == 0, result.rendered_output + payload = result.payload["results"][0]["payloads"][0] + assert not payload.get("errors"), payload + return payload + + +@pytest.mark.parametrize("format", ("ImageXpress", "OperaPhenix")) +@pytest.mark.parametrize("native", (False, True)) +def test_zero_geometry_real_mcp_journey(tmp_path, monkeypatch, format, native): + monkeypatch.setenv("OPENHCS_CPU_ONLY", "true") + monkeypatch.setenv("OPENHCS_AGENT_READ_ROOTS", str(tmp_path)) + monkeypatch.setenv("OPENHCS_AGENT_WRITE_ROOTS", str(tmp_path)) + # Explicit owned stdio process: do not reuse or mutate a resident server. + with McpDevClient(use_resident_server=False) as client: + _payload(client, ["health"]) + _payload(client, ["authoring-context", "first_use"]) + root = tmp_path / "plate" + arguments = [ + "generate-synthetic-plate", + str(root), + "--grid-rows", + "1", + "--grid-cols", + "1", + "--tile-width", + "64", + "--tile-height", + "64", + "--overlap-percent", + "0", + "--stage-error-px", + "0", + "--wavelengths", + "1", + "--z-stack-levels", + "1", + "--num-cells", + "4", + "--well", + "A01", + "--random-seed", + "7", + "--format", + format, + ] + if native: + arguments.append("--openhcs-format") + generated = _payload(client, arguments) + assert generated["image_count"] == 1 + assert generated["overlap_percent"] == generated["stage_error_px"] == 0 + inspected = _payload(client, ["inspect-plate", str(root)]) + assert inspected["image_files"]["count"] == 1 + sampled = _payload( + client, + [ + "sample-plate-image", + str(root), + generated["sampled_image_files"][0], + "--height", + "8", + "--width", + "8", + "--include-array-values", + ], + ) + assert sampled["sample_shape"] == [8, 8] + assert sampled["dtype"] == "uint16" + assert sampled["sample_included"] + assert sampled["sample_values"] + if native: + metadata = json.loads(Path(generated["metadata_file_path"]).read_text()) + assert ( + sum( + len(subdir["image_files"]) + for subdir in metadata["subdirectories"].values() + ) + == 1 + ) + assert all( + subdir["grid_dimensions"] == [1, 1] + for subdir in metadata["subdirectories"].values() + ) diff --git a/tests/unit/test_synthetic_generator_zero_geometry.py b/tests/unit/test_synthetic_generator_zero_geometry.py new file mode 100644 index 0000000000..00a60746cb --- /dev/null +++ b/tests/unit/test_synthetic_generator_zero_geometry.py @@ -0,0 +1,64 @@ +"""Zero overlap/jitter retain literal geometry in both declared plate layouts.""" + +import numpy as np +import pytest +import tifffile + +from openhcs.demo.synthetic_data import SyntheticMicroscopyGenerator + + +@pytest.mark.parametrize("format", ("ImageXpress", "OperaPhenix")) +@pytest.mark.parametrize("native", (False, True)) +@pytest.mark.parametrize("grid", ((1, 1), (2, 2))) +def test_zero_geometry_generates_deterministic_requested_tiles( + tmp_path, format, native, grid +): + outputs = [] + for run in range(2): + root = tmp_path / str(run) + generator = SyntheticMicroscopyGenerator( + output_dir=str(root), + grid_size=grid, + tile_size=(64, 64), + overlap_percent=0, + stage_error_px=0, + wavelengths=1, + z_stack_levels=1, + num_cells=4, + wells=["A01"], + format=format, + openhcs_format=native, + random_seed=7, + ) + assert generator.image_size == (64 * grid[1], 64 * grid[0]) + for row in range(grid[0]): + for col in range(grid[1]): + assert generator._site_position(row, col) == (64 * col, 64 * row) + generator.generate_dataset() + images = { + path.relative_to(root): tifffile.imread(path) + for path in root.rglob("*.tif*") + } + assert len(images) == grid[0] * grid[1] + assert all(image.shape == (64, 64) for image in images.values()) + outputs.append(images) + assert outputs[0].keys() == outputs[1].keys() + for path in outputs[0]: + np.testing.assert_array_equal(outputs[0][path], outputs[1][path]) + + +def test_subpixel_overlap_does_not_sample_an_empty_pixel_region(tmp_path): + generator = SyntheticMicroscopyGenerator( + output_dir=str(tmp_path), + grid_size=(1, 1), + tile_size=(32, 64), + overlap_percent=1, + stage_error_px=0, + wavelengths=1, + z_stack_levels=1, + num_cells=4, + wells=["A01"], + random_seed=7, + ) + generator.generate_dataset() + assert len(list(tmp_path.rglob("*.tif"))) == 1 From 6dce327e61daa10421f2b2885aca612eed6c3325 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Tue, 29 Sep 2026 14:48:01 -0400 Subject: [PATCH 02/19] Push declared source selections into discovery and verify fragmented ROI identity (#132) --- benchmark/fragmented_roi_profile.py | 75 +++++ .../input_preparation_checkpoint_20260929.md | 98 ++++-- openhcs/core/source_binding_workspace.py | 11 +- openhcs/core/source_bindings.py | 39 +++ openhcs/microscopes/bioformats.py | 33 +- openhcs/microscopes/bioformats_adapter.py | 43 ++- openhcs/microscopes/microscope_base.py | 20 +- .../microscopes/source_bindings_handler.py | 9 +- openhcs/processing/materialization/core.py | 2 +- .../test_bioformats_preparation_selection.py | 283 ++++++++++++++++++ .../test_fragmented_roi_materialization.py | 110 +++++++ 11 files changed, 685 insertions(+), 38 deletions(-) create mode 100644 benchmark/fragmented_roi_profile.py create mode 100644 tests/unit/test_bioformats_preparation_selection.py create mode 100644 tests/unit/test_fragmented_roi_materialization.py diff --git a/benchmark/fragmented_roi_profile.py b/benchmark/fragmented_roi_profile.py new file mode 100644 index 0000000000..d800195c90 --- /dev/null +++ b/benchmark/fragmented_roi_profile.py @@ -0,0 +1,75 @@ +"""Bounded provider-free profile of the declared ROI extraction/ZIP owners.""" + +from __future__ import annotations + +import argparse +import cProfile +import json +import pstats +from collections import Counter +from pathlib import Path +from time import perf_counter +from zipfile import ZipFile + +import numpy as np +from polystore.disk import DiskStorageBackend +from polystore.roi import extract_rois_from_labeled_mask +from polystore.roi_converters import FijiROIConverter + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument( + "output_dir", type=Path, help="Owned disposable scratch directory." + ) + args = parser.parse_args() + args.output_dir.mkdir(parents=True, exist_ok=True) + labels = np.zeros((128, 128), dtype=np.int32) + labels[4:124:4, 4:124:4] = 188 + labels[1:3, 1:3] = 7 + labels[125:127, 125:127] = 42 + start = perf_counter() + extract_rois_from_labeled_mask(labels, min_area=1) + cold_extraction_seconds = perf_counter() - start + profiler = cProfile.Profile() + start = perf_counter() + rois = profiler.runcall(extract_rois_from_labeled_mask, labels, min_area=1) + warm_extraction_seconds = perf_counter() - start + stats = pstats.Stats(profiler) + bbox_scan_calls = sum( + value[1] for key, value in stats.stats.items() if key[2] == "find_objects" + ) + start = perf_counter() + members = FijiROIConverter.rois_to_imagej_members(rois) + encoding_seconds = perf_counter() - start + archive = args.output_dir / "fragmented.roi.zip" + start = perf_counter() + DiskStorageBackend().save(rois, archive) + archive_seconds = perf_counter() - start + with ZipFile(archive) as zipped: + member_count = sum(name.endswith(".roi") for name in zipped.namelist()) + print( + json.dumps( + { + "shape": list(labels.shape), + "parent_labels": [roi.metadata["label"] for roi in rois], + "fragments_by_parent": dict( + Counter(member.metadata["label"] for member in members) + ), + "zip_roi_members": member_count, + "cold_extraction_seconds": cold_extraction_seconds, + "warm_extraction_seconds": warm_extraction_seconds, + "bbox_scan_calls": bbox_scan_calls, + "member_encoding_seconds": encoding_seconds, + "archive_encoding_and_write_seconds": archive_seconds, + }, + sort_keys=True, + ) + ) + # The archive writer includes its own required conversion; this does not + # imply production converts twice. The standalone conversion isolates cost. + stats.strip_dirs().sort_stats("cumulative").print_stats(15) + + +if __name__ == "__main__": + main() diff --git a/docs/plans/input_preparation_checkpoint_20260929.md b/docs/plans/input_preparation_checkpoint_20260929.md index eeb74458b6..96b163490f 100644 --- a/docs/plans/input_preparation_checkpoint_20260929.md +++ b/docs/plans/input_preparation_checkpoint_20260929.md @@ -22,18 +22,78 @@ the issue's 1x1/64px/one-channel/one-Z/four-cell/A01/seed7 plate, inspects it an samples pixels. It does not replace the transport, services or workspace owners with mocks. It does not use a GUI, biological input or a paid provider. -## Continuing scope: #132 +## Second checkpoint: #132 -Push known physical path selections ahead of unrelated store opens using the -source declaration and its existing filter matcher owners. Metadata and axis -selection remain at decoded plane projection. Synthetic tests measure opened -entrypoints and exact projected references; no real 16-CZI run is authorized. +`SourceBindingsConfig.discovery_path_matches` applies the declared global +filters and union of binding-local path selections before unrelated store opens. +The existing filter matcher/target-resolver families still own clause behavior. +An unrestricted alias retains discovery; metadata/component-only selectors are +not guessed from filenames. Explicit source URIs use their existing resolver. +Image-file and NGFF adapters use this declaration directly. The Java adapter +only prunes a mismatching entrypoint when Bio-Formats `isSingleFile` declares it +independent. Compound entrypoints remain discoverable; the existing workspace +projector applies global filters to decoded store provenance, including companion +paths, then resolves binding/metadata/axis selections. -Contour extraction and ImageJ ZIP encoding belong to the recorded PolyStore -submodule. Coordinate permission before changing it. Fragmented labels are -parent objects with many shapes; ZIP member count is not a biological count. -Remaining work: fragmented fidelity/profile receipt and bounded repeated-work -improvement, review of companion-file selection, coordinator installed journey. +The existing polymorphic `MicroscopeHandler.detect` now receives source +declarations, including during AUTO detection. No second detection interface, +filename registry, suffix-to-reader map, or companion-file roster was added. +Controlled-decoder tests use sixteen tiny physical filenames and the real +handler/orchestrator/workspace path: the explicit handler/orchestrator opens only +the selected container once; AUTO opens that selected container twice (detection +and initialization), and never opens the other fifteen. A real TIFF journey +through AUTO orchestrator initialization decodes only the selected file twice, +persists its exact reference, then loads its actual pixels. It has no substituted +decoder, JVM or orchestration implementation. + +The 128x128 fragmented fixture has three parent labels (7, 42, 188), with 900 +disconnected shapes belonging to label 188. Actual ROI materialization, disk ZIP +encoding and native reopen produce 902 members while preserving parent labels, +area, centroid and translated coordinate bounds. Summaries now say parent labels, +not cells. This demonstrates fragmentation, not duplicate biology. The existing +declared output-path selection can retain an exact int32 label TIFF without +calling the unselected ROI writer; its guard now has a regression witness. + +Recorded PolyStore profile: cold extraction 0.292s, warm profiled extraction +0.0195s, standalone member conversion 0.0686s, archive conversion/write 0.129s. +The latter includes its required conversion; standalone conversion isolates a +phase and does not imply production does both. Three contour calls yield 902 +members; two `find_objects` calls confirm a repeated whole-mask bbox scan. +These are single bounded observations, not a large-mask speedup claim. + +Remaining optimization belongs to PolyStore's +`TwoDimensionalLabeledMaskROIExtractor.extract`: `regionprops` already supplies +the bbox and cached region image, but extraction separately calls `find_objects` +and constructs that binary crop again. Proposed in-place change: reuse those +existing properties, retaining labels, bbox, origin and contour ordering. No +external submodule was changed without coordinator coordination. No parallel +extractor/cache will be added in OpenHCS. The coordinator must authorize/assign +this paired submodule change before implementation and validation resume. + +Fidelity limits: this fixture checks disconnected islands, identities, metadata, +coordinates and int32 raster preservation. It does not establish arbitrary +hole/border/topology rasterization equivalence, 3D fidelity, or fidelity of the +private diagnostic archives. No biological inputs or frozen Euler output opened. + +## Essential catalog review (changed surface only) + +Audited against fetched `openhcsdev/main` at `98d9b9d23` and the working #206 +checkpoint, using the owner's current archived catalog. This is source/AST and +owner tracing, not a full NRA scan, proof replay or global clean-debt claim. + +| Pattern | Concrete owner / witness | New-case check and disposition | +| --- | --- | --- | +| IMPL-12, copied procedure | `synthetic_data.py:827`, `SyntheticMicroscopyGenerator._site_position`; both layout loops call it at 875/881. | A shared positioning change now needs one owner edit, not two copied procedures. Zero jitter/overlap and both layout families have deterministic/count tests. Duplicate layout-position procedure removed. | +| IMPL-1/2/3/4, external family dispatch | `source_matching.py:114`, `SourceFilterMatcher`; `SourceFilterTargetResolver:232`; adapter discovery uses the existing `AutoRegisterMeta` registry (`bioformats_adapter.py:555`). | Directory plus OR-group clauses and a second/unrestricted alias require declarations only, no preparation switch edits. Ordinary numeric geometry and filesystem identity checks are not kind dispatch. The speculative second detection method was deleted; existing `detect` owns extension. | +| MEMB-1/2/3, duplicate rosters | `BioFormatsJavaAdapter._is_single_file` at 828 calls the decoder capability, not a hand-maintained extension/companion list. | A compound decoder response survives a nonmatching entrypoint and selects its decoded companion. A new single-file name uses the same clauses; no new filename registry or roster. | +| BOUND-1/2/6/7, bypassing decoded/config owners | `SourceBindingsConfig` at 1833/1842 owns path decisions; `SourceBindingWorkspaceProjector.candidate_matches_binding` at 966 owns decoded selectors. | Exact-source and metadata-only tests preserve URI resolution and defer unknown metadata. No raw configuration dictionary, `getattr` default, attribute-name switch or metadata mirror was introduced. AST guards verify the declaration matcher and single detection contract. | +| IDEN-2/6, conflated count/identity | ROI writer summary at `materialization/core.py:3047`; existing PolyStore parent-label metadata and archive converter retain identities. | Label 188 remains one parent with 900 fragments after archive reopen, not 900 new parents. New parent values need no relabeling/table edit. Summary ambiguity corrected; stronger topology fidelity remains open. | + +Guard results: eleven changed source/test/profile files parse; focused AST checks +verify existing matcher calls/adapter registry, metadata-free `isSingleFile` +instead of `setId` in the prune capability, and absence of a second detection +contract. New tests/profile pass Ruff and `git diff --check` passes. Existing +whole-file debt and PolyStore repeated-work findings remain explicitly scoped. ## Validation and resource boundary @@ -42,10 +102,11 @@ submodule roots, using `/home/ts/code/projects/openhcs/.venv/bin/python` and an explicit worktree/submodule `PYTHONPATH`. No package installation or shared source change. Main was fetched and integrated normally before implementation. -Focused source evidence: 20 tests passed in 2.32 seconds (zero geometry, -randomness, and preparation selections). This is not a full NRA scan or broad -suite. Source review is limited to the source declaration, store adapters, -workspace handler, synthetic generator and their regression witnesses. +First checkpoint: 20 tests passed in 2.32 seconds (zero geometry, randomness, +and initial preparation selections). Second checkpoint: 115 preparation, +Bio-Formats, source-workspace, plane-store and fragmented-materialization tests +passed in 15.54 seconds; the additional real TIFF orchestrator journey passed in +2.79 seconds. These are focused source checks, not a full suite or NRA proof. Real current-entrypoint evidence: four continuous MCP health, orientation, generate, inspect and sample journeys passed in 23.19 seconds. Layouts: @@ -55,10 +116,11 @@ metadata records one image and the requested 1x1 grid. This uses source overlays with verified imports, not a changed installed package or an installed acceptance claim. New test files pass Ruff; full-file Ruff reports existing source debt. -The resource guard reported 12.8 GiB available RAM with a swap-only warning -(11.7 GiB used). Only small single-process provider-free checks are run; -large/GUI/JVM work is deferred. Tests/MCP acquire the batch's nonblocking -validation lock. Busy lock means independent source work, not polling. +Guards reported 12.8-14.8 GiB available RAM with a historical swap warning +(11.5-11.7 GiB used). Only small provider-free checks were run under the batch's +nonblocking lock; busy lock meant independent source work, not polling. The last +live TIFF/profile run finished and released the lock for Euler. Per the H003 +priority checkpoint, no new test/JVM/GUI run starts until Euler releases validation. Scratch owner: Lovelace. Purpose: tiny generated regression plates and pytest fixtures. Path: `/home/ts/.cache/agent-scratch/openhcs-issue-input-20260929`. diff --git a/openhcs/core/source_binding_workspace.py b/openhcs/core/source_binding_workspace.py index 980da5b2f8..b9469510e5 100644 --- a/openhcs/core/source_binding_workspace.py +++ b/openhcs/core/source_binding_workspace.py @@ -678,6 +678,9 @@ def projection_set_for_candidates( ), ) for candidate in candidates + if self.source_bindings.source_path_filters_match( + candidate.source_filter_path_identities() + ) ) candidates = tuple( @@ -902,13 +905,7 @@ def source_candidates( filter_paths = tuple( dict.fromkeys((relative_path, _normalized_source_path(path))) ) - if not any( - source_filters_match( - filter_path, - self.source_bindings.source_filter_declarations, - ) - for filter_path in filter_paths - ): + if not self.source_bindings.source_path_filters_match(filter_paths): continue metadata = self.source_bindings.coerce_metadata( metadata_from_rules( diff --git a/openhcs/core/source_bindings.py b/openhcs/core/source_bindings.py index 56dead442c..85e4a1d5a8 100644 --- a/openhcs/core/source_bindings.py +++ b/openhcs/core/source_bindings.py @@ -1830,6 +1830,45 @@ def source_filter_declarations(self) -> tuple[SourceFilterClause, ...]: return tuple(self.source_filters or ()) + def source_path_filters_match(self, identities: tuple[str, ...]) -> bool: + """Match the declared source universe against physical path identities.""" + from openhcs.core.source_matching import source_filters_match + + return any( + source_filters_match(identity, self.source_filter_declarations) + for identity in identities + ) + + def discovery_path_matches(self, root: Path, path: Path) -> bool: + """Apply known physical-path selections before decoding a source store. + + Metadata and component selectors still belong to plane projection. An + unrestricted binding keeps discovery unrestricted, not just the first + filtered alias. All matching uses the existing source-filter owners. + """ + from openhcs.core.source_matching import source_filters_match + + root = root.resolve(strict=False) + resolved = path.resolve(strict=False) + identities = (resolved.as_posix(),) + if root.is_file(): + identities = (resolved.name, *identities) + elif resolved.is_relative_to(root): + identities = (resolved.relative_to(root).as_posix(), *identities) + if not self.source_path_filters_match(identities): + return False + return not self.binding_declarations or any( + ( + binding.explicit_source is None + or Path(binding.explicit_source.resolved(root).uri).resolve() == resolved + ) + and any( + source_filters_match(identity, binding.selector.filters) + for identity in identities + ) + for binding in self.binding_declarations + ) + def declaration_identity(self) -> str: """Return a stable identity for this complete source declaration.""" diff --git a/openhcs/microscopes/bioformats.py b/openhcs/microscopes/bioformats.py index d739c298f7..2ba34a7942 100644 --- a/openhcs/microscopes/bioformats.py +++ b/openhcs/microscopes/bioformats.py @@ -52,12 +52,19 @@ def extract_component_coordinates(self, component_value: str) -> tuple[str, str] class BioFormatsMetadataHandler(MetadataHandler): """Metadata view over exact store-emitted source planes.""" - def __init__(self, filemanager: FileManager | None = None): + def __init__( + self, + filemanager: FileManager | None = None, + source_bindings: SourceBindingsConfig | None = None, + ): super().__init__() self.filemanager = filemanager + self.source_bindings = source_bindings or SourceBindingsConfig() def source_dataset(self, plate_path: Union[str, Path]) -> SourcePlaneDataset: - return SourcePlaneStoreAdapter.discover_dataset(plate_path) + return SourcePlaneStoreAdapter.discover_dataset( + plate_path, source_bindings=self.source_bindings + ) def source_diagnostics( self, @@ -157,6 +164,22 @@ class BioFormatsHandler(BroadMicroscopeDetector, MicroscopeHandler): _microscope_type = Microscope.BIOFORMATS.value _metadata_handler_class = BioFormatsMetadataHandler + @classmethod + def detect( + cls, + plate_folder: Path, + filemanager: FileManager, + source_bindings_config: SourceBindingsConfig | None = None, + ) -> bool: + """Keep source selections ahead of container opens during auto-detection.""" + try: + BioFormatsMetadataHandler( + filemanager, source_bindings_config + ).find_metadata_file(plate_folder) + except (FileNotFoundError, TypeError): + return False + return True + @classmethod def create( cls, @@ -182,11 +205,13 @@ def __init__( source_bindings_config: SourceBindingsConfig | None = None, ): self.parser = BioFormatsFilenameParser(filemanager, pattern_format) - self.source_metadata_handler = BioFormatsMetadataHandler(filemanager) - self.metadata_handler: MetadataHandler = self.source_metadata_handler self.source_bindings = source_bindings_defaults_to_base( source_bindings_config or SourceBindingsConfig() ) + self.source_metadata_handler = BioFormatsMetadataHandler( + filemanager, self.source_bindings + ) + self.metadata_handler: MetadataHandler = self.source_metadata_handler super().__init__(parser=self.parser, metadata_handler=self.metadata_handler) @property diff --git a/openhcs/microscopes/bioformats_adapter.py b/openhcs/microscopes/bioformats_adapter.py index 0554c1f85d..80f6c4d8d6 100644 --- a/openhcs/microscopes/bioformats_adapter.py +++ b/openhcs/microscopes/bioformats_adapter.py @@ -26,6 +26,7 @@ from openhcs.constants.constants import AllComponents, Backend from openhcs.core.image_file_serialization import ImageFileFormat +from openhcs.core.source_bindings import SourceBindingsConfig from openhcs.core.source_matching import ( merge_source_metadata, with_source_component_metadata, @@ -487,6 +488,17 @@ class SourcePlaneStoreAdapter(ABC, metaclass=AutoRegisterMeta): __skip_if_no_key__ = True registry_key: ClassVar[str | None] = None + def __init__(self, source_bindings: SourceBindingsConfig | None = None): + self.source_bindings = source_bindings or SourceBindingsConfig() + + def selected_source_paths(self, root: Path) -> tuple[Path, ...]: + """Select physical entrypoints before opening unrelated containers.""" + return tuple( + path + for path in _candidate_source_paths(root) + if self.source_bindings.discovery_path_matches(root, path) + ) + def source_metadata_for_path(self, path: Path) -> SourceMetadataMapping: """Enrich a filename-bound physical source without replacing its axes.""" del path @@ -532,13 +544,17 @@ def retain_candidate( return True @classmethod - def discover_dataset(cls, root: str | Path) -> SourcePlaneDataset: + def discover_dataset( + cls, root: str | Path, *, source_bindings: SourceBindingsConfig | None = None + ) -> SourcePlaneDataset: root_path = Path(root).resolve(strict=False) if not root_path.exists(): raise BioFormatsAdapterUnavailableError( f"Plane-store collection does not exist: {root_path}" ) - adapters = tuple(adapter_type() for adapter_type in cls.__registry__.values()) + adapters = tuple( + adapter_type(source_bindings) for adapter_type in cls.__registry__.values() + ) collection_owners = tuple( adapter for adapter in adapters if adapter.claims_collection(root_path) ) @@ -626,6 +642,7 @@ def discover_stores(self, root: Path) -> tuple[SourcePlaneDataset, ...]: return tuple( self._discover_store(root, location) for location in OmeZarrLocation.discover(root) + if self.source_bindings.discovery_path_matches(root, Path(location.path)) ) def _discover_store( @@ -769,6 +786,11 @@ def discover_stores(self, root: Path) -> tuple[SourcePlaneDataset, ...]: continue if not self._declares_path(context, source_path): continue + if ( + not self.source_bindings.discovery_path_matches(root, source_path) + and self._is_single_file(context, source_path) + ): + continue try: dataset = self._discover_container(root, source_path) except BioFormatsNoScalarSourceError as exc: @@ -802,6 +824,21 @@ def discover_stores(self, root: Path) -> tuple[SourcePlaneDataset, ...]: ) return tuple(datasets) + @staticmethod + def _is_single_file(context: BioFormatsJavaContext, path: Path) -> bool: + """Ask the decoder before pruning a potentially compound entrypoint. + + ImageReader.isSingleFile identifies its format without setId/OME + metadata initialization. A companion path can select a multi-file store + whose entrypoint itself does not match; decoded projection decides that. + """ + context.ensure_initialized() + reader = context.ImageReader() + try: + return bool(reader.isSingleFile(str(path))) + finally: + reader.close() + @staticmethod def _declares_path(context: BioFormatsJavaContext, path: Path) -> bool: if ImageFileFormat.is_image_path(path): @@ -877,7 +914,7 @@ def retain_candidate( def discover_stores(self, root: Path) -> tuple[SourcePlaneDataset, ...]: datasets = [] identity = SourceDatasetIdentity.for_root(root) - for source_path in _candidate_source_paths(root): + for source_path in self.selected_source_paths(root): if not ImageFileFormat.is_image_path(source_path): continue image_format = ImageFileFormat.require_path(source_path) diff --git a/openhcs/microscopes/microscope_base.py b/openhcs/microscopes/microscope_base.py index 963a8f3440..78e4d893dc 100644 --- a/openhcs/microscopes/microscope_base.py +++ b/openhcs/microscopes/microscope_base.py @@ -238,8 +238,18 @@ def source_selection_guidance(cls) -> str: ) @classmethod - def detect(cls, plate_folder: Path, filemanager: FileManager) -> bool: - """Return whether this handler can initialize the plate.""" + def detect( + cls, + plate_folder: Path, + filemanager: FileManager, + source_bindings_config: Optional["SourceBindingsConfig"] = None, + ) -> bool: + """Detect this handler; store-backed declarations can narrow discovery. + + Ordinary metadata handlers detect one plate declaration, without opening + its image containers. Store-backed handlers own selected discovery in + their implementation of this same method. + """ from polystore.exceptions import MetadataNotFoundError metadata_handler_class = cls._metadata_handler_class @@ -939,6 +949,7 @@ def create_microscope_handler( plate_folder, filemanager, allowed_types=allowed_auto_types, + source_bindings_config=source_bindings, ) if detected_microscope_type is None: if source_bindings is None or source_bindings.is_empty: @@ -1024,6 +1035,7 @@ def _auto_detect_microscope_type( plate_folder: Path, filemanager: FileManager, allowed_types: Optional[List[str]] = None, + source_bindings_config: Optional["SourceBindingsConfig"] = None, ) -> Optional[str]: """ Auto-detect microscope type using registry iteration. @@ -1064,7 +1076,9 @@ def _auto_detect_microscope_type( for handler_name in detection_order: handler_class = MICROSCOPE_HANDLERS[handler_name] - if handler_class.detect(plate_folder, filemanager): + if handler_class.detect( + plate_folder, filemanager, source_bindings_config + ): logger.info(f"Auto-detected {handler_name} microscope type") return handler_name logger.debug(f"{handler_name} metadata not found in {plate_folder}") diff --git a/openhcs/microscopes/source_bindings_handler.py b/openhcs/microscopes/source_bindings_handler.py index d6af813a8a..7178103625 100644 --- a/openhcs/microscopes/source_bindings_handler.py +++ b/openhcs/microscopes/source_bindings_handler.py @@ -115,8 +115,13 @@ def compatible_backends(self) -> list[Backend]: return [Backend.DISK] @classmethod - def detect(cls, plate_folder: Path, filemanager: FileManager) -> bool: - del plate_folder, filemanager + def detect( + cls, + plate_folder: Path, + filemanager: FileManager, + source_bindings_config: SourceBindingsConfig | None = None, + ) -> bool: + del plate_folder, filemanager, source_bindings_config return False def _replay_persisted_workspace( diff --git a/openhcs/processing/materialization/core.py b/openhcs/processing/materialization/core.py index 6feb2a7bb0..436630a44d 100644 --- a/openhcs/processing/materialization/core.py +++ b/openhcs/processing/materialization/core.py @@ -3044,7 +3044,7 @@ def _write_roi_zip( ) summary = ( - f"Segmentation ROIs: {total_roi_count} cells\n" + f"Segmentation ROIs: {total_roi_count} parent labels\n" "Spatial dimensions: 2D\n" f"Projected source planes: {len(materialization_input.items)}\n" ) diff --git a/tests/unit/test_bioformats_preparation_selection.py b/tests/unit/test_bioformats_preparation_selection.py new file mode 100644 index 0000000000..133cd855c9 --- /dev/null +++ b/tests/unit/test_bioformats_preparation_selection.py @@ -0,0 +1,283 @@ +"""Known physical-source selections reach discovery before container opens.""" + +import json +from pathlib import Path + +import numpy as np +import pytest +import tifffile +from objectstate.lazy_factory import ensure_global_config_context +from polystore.bioformats_java import BioFormatsJavaContext + +from openhcs.constants.constants import AllComponents, Microscope +from openhcs.core.config import ( + GlobalPipelineConfig, + LazySourceBindingsConfig, + PipelineConfig, +) +from openhcs.core.image_file_serialization import TiffImageFileFormat +from openhcs.core.orchestrator.orchestrator import PipelineOrchestrator +from openhcs.core.source_bindings import ( + ImagePlaneSource, + MetadataSelector, + NamedSourceBinding, + SourceBindingsConfig, + SourceFilterClause, + SourceFilterMatchType, + SourceFilterSubject, + SourceSelector, +) +from openhcs.microscopes.bioformats import BioFormatsHandler +from openhcs.microscopes.bioformats_adapter import BioFormatsJavaAdapter +from tests.unit.bioformats_fixture import bioformats_filemanager +from tests.unit.test_bioformats_java_adapter import ( + FakeBioFormatsContext, + FakeBioFormatsMetadata, +) + + +def _file_filter(name): + return SourceFilterClause( + SourceFilterSubject.FILE, SourceFilterMatchType.EQUALS, name + ) + + +class _SelectionContext(FakeBioFormatsContext): + """Controlled decoder response, preserving the real discovery/workspace path.""" + + single_file = True + + def ensure_initialized(self): + pass + + def ImageReader(self): + return self + + def isSingleFile(self, path): + return self.single_file + + def close(self): + pass + + +@pytest.mark.parametrize("selection", ("global", "binding")) +@pytest.mark.parametrize("entrypoint", ("handler", "orchestrator", "auto_orchestrator")) +def test_selected_container_opens_once_and_projects_both_planes( + tmp_path, monkeypatch, selection, entrypoint +): + names = [f"container-{index:02}.czi" for index in range(16)] + metadata = {} + for index, name in enumerate(names): + (tmp_path / name).touch() + metadata[name] = FakeBioFormatsMetadata( + well_column=index, + well_id=f"Well:0:{index}", + sample_id=f"WellSample:{index}", + image_id=f"Image:{index}", + ) + context = _SelectionContext(metadata, declared_suffixes=(".czi",)) + opened = [] + original_open = context.open_reader + + def counted_open(path): + opened.append(Path(path).name) + return original_open(path) + + monkeypatch.setattr(context, "open_reader", counted_open) + monkeypatch.setattr( + BioFormatsJavaContext, "instance", classmethod(lambda cls: context) + ) + filters = (_file_filter(names[7]),) + bindings = SourceBindingsConfig( + source_filters=filters if selection == "global" else (), + bindings=( + NamedSourceBinding( + alias="selected", + selector=SourceSelector( + filters=filters if selection == "binding" else (), + ), + ), + ), + ) + filemanager = bioformats_filemanager() + if entrypoint == "handler": + handler = BioFormatsHandler(filemanager, source_bindings_config=bindings) + assert handler.initialize_workspace(tmp_path, filemanager) == tmp_path + assert opened == [names[7]] + else: + ensure_global_config_context(GlobalPipelineConfig, GlobalPipelineConfig()) + orchestrator = PipelineOrchestrator( + tmp_path, + storage_registry=filemanager.registry, + pipeline_config=PipelineConfig( + microscope=Microscope.AUTO + if entrypoint == "auto_orchestrator" + else Microscope.BIOFORMATS, + source_bindings_config=LazySourceBindingsConfig( + source_filters=bindings.source_filters, + bindings=bindings.bindings, + ), + ), + ).initialize() + assert orchestrator.is_initialized() + assert opened == [names[7]] * (2 if entrypoint == "auto_orchestrator" else 1) + assert len(orchestrator.get_component_keys(AllComponents.WELL)) == 1 + workspace = json.loads((tmp_path / "openhcs_metadata.json").read_text()) + mapping = workspace["subdirectories"]["."]["workspace_mapping"] + assert len(mapping) == 2 + assert all(names[7] in ref["backend_address"] for ref in mapping.values()) + + +def test_real_tiff_orchestrator_only_decodes_selected_source(tmp_path, monkeypatch): + names = [f"source-{index:02}.tif" for index in range(16)] + for index, name in enumerate(names): + tifffile.imwrite(tmp_path / name, np.full((8, 8), index, dtype=np.uint16)) + decoded = [] + original_read = TiffImageFileFormat.read + + def counted_read(self, path): + decoded.append(Path(path).name) + return original_read(self, path) + + monkeypatch.setattr(TiffImageFileFormat, "read", counted_read) + ensure_global_config_context(GlobalPipelineConfig, GlobalPipelineConfig()) + filemanager = bioformats_filemanager() + orchestrator = PipelineOrchestrator( + tmp_path, + storage_registry=filemanager.registry, + pipeline_config=PipelineConfig( + microscope=Microscope.AUTO, + source_bindings_config=LazySourceBindingsConfig( + bindings=( + NamedSourceBinding( + alias="selected", + selector=SourceSelector(filters=(_file_filter(names[7]),)), + ), + ), + ), + ), + ).initialize() + assert orchestrator.is_initialized() + assert decoded == [names[7], names[7]] + workspace = json.loads((tmp_path / "openhcs_metadata.json").read_text()) + (reference,) = workspace["subdirectories"]["."]["workspace_mapping"].values() + assert reference["backend_address"] == names[7] + pixels = filemanager.load(str(tmp_path / reference["backend_address"]), "disk") + np.testing.assert_array_equal(pixels, np.full((8, 8), 7, dtype=np.uint16)) + + +def test_companion_path_selection_does_not_discard_its_container(tmp_path, monkeypatch): + (tmp_path / "plate.fake").touch() + context = _SelectionContext() + context.single_file = False + monkeypatch.setattr( + BioFormatsJavaContext, "instance", classmethod(lambda cls: context) + ) + config = SourceBindingsConfig(source_filters=(_file_filter("well-a01.tif"),)) + (dataset,) = BioFormatsJavaAdapter(config).discover_stores(tmp_path) + assert len(dataset.candidates) == 2 + assert all( + "well-a01.tif" in c.source_filter_path_identities() for c in dataset.candidates + ) + + +def test_global_companion_filter_is_applied_after_container_decode(tmp_path, monkeypatch): + names = ("selected.fake", "unrelated.fake") + metadata = {} + for index, name in enumerate(names): + (tmp_path / name).touch() + metadata[name] = FakeBioFormatsMetadata( + well_column=index, + well_id=f"Well:0:{index}", + sample_id=f"WellSample:{index}", + image_id=f"Image:{index}", + ) + context = _SelectionContext(metadata) + context.single_file = False + original_open = context.open_reader + + def with_companion(path): + opened = original_open(path) + opened.reader.used_files = (Path(path).name, f"{Path(path).stem}.tif") + return opened + + monkeypatch.setattr(context, "open_reader", with_companion) + monkeypatch.setattr( + BioFormatsJavaContext, "instance", classmethod(lambda cls: context) + ) + filemanager = bioformats_filemanager() + handler = BioFormatsHandler( + filemanager, + source_bindings_config=SourceBindingsConfig( + source_filters=(_file_filter("selected.tif"),) + ), + ) + handler.initialize_workspace(tmp_path, filemanager) + workspace = json.loads((tmp_path / "openhcs_metadata.json").read_text()) + mapping = workspace["subdirectories"]["."]["workspace_mapping"] + assert len(mapping) == 2 + assert all("selected.fake" in ref["backend_address"] for ref in mapping.values()) + + +def test_binding_selection_is_union_and_metadata_is_not_guessed(tmp_path): + bindings = SourceBindingsConfig( + bindings=( + NamedSourceBinding( + alias="first", selector=SourceSelector(filters=(_file_filter("a.czi"),)) + ), + NamedSourceBinding( + alias="second", + selector=SourceSelector(filters=(_file_filter("b.czi"),)), + ), + ) + ) + assert bindings.discovery_path_matches(tmp_path, tmp_path / "a.czi") + assert bindings.discovery_path_matches(tmp_path, tmp_path / "b.czi") + assert not bindings.discovery_path_matches(tmp_path, tmp_path / "c.czi") + unrestricted = SourceBindingsConfig( + bindings=(*bindings.bindings, NamedSourceBinding(alias="all")) + ) + assert unrestricted.discovery_path_matches(tmp_path, tmp_path / "c.czi") + + +def test_explicit_source_and_metadata_only_discovery_stay_declared(tmp_path): + selected = tmp_path / "selected.czi" + selected.touch() + exact = SourceBindingsConfig( + bindings=( + NamedSourceBinding( + alias="exact", explicit_source=ImagePlaneSource(uri="selected.czi") + ), + ) + ) + assert exact.discovery_path_matches(tmp_path, selected) + assert not exact.discovery_path_matches(tmp_path, tmp_path / "unrelated.czi") + metadata_only = SourceBindingsConfig( + bindings=( + NamedSourceBinding( + alias="decoded", + selector=SourceSelector(metadata=(MetadataSelector("well", "A01"),)), + ), + ) + ) + # The filename cannot establish which decoded well this container supplies. + assert metadata_only.discovery_path_matches(tmp_path, tmp_path / "unrelated.czi") + + +def test_discovery_reuses_directory_or_group_filter_semantics(tmp_path): + filters = ( + SourceFilterClause( + SourceFilterSubject.DIRECTORY, SourceFilterMatchType.CONTAINS, "selected" + ), + SourceFilterClause( + SourceFilterSubject.FILE, SourceFilterMatchType.EQUALS, "a.czi", any_group=0 + ), + SourceFilterClause( + SourceFilterSubject.FILE, SourceFilterMatchType.EQUALS, "b.czi", any_group=0 + ), + ) + config = SourceBindingsConfig(source_filters=filters) + assert config.discovery_path_matches(tmp_path, tmp_path / "selected" / "a.czi") + assert config.discovery_path_matches(tmp_path, tmp_path / "selected" / "b.czi") + assert not config.discovery_path_matches(tmp_path, tmp_path / "selected" / "c.czi") + assert not config.discovery_path_matches(tmp_path, tmp_path / "other" / "a.czi") diff --git a/tests/unit/test_fragmented_roi_materialization.py b/tests/unit/test_fragmented_roi_materialization.py new file mode 100644 index 0000000000..11b7e91958 --- /dev/null +++ b/tests/unit/test_fragmented_roi_materialization.py @@ -0,0 +1,110 @@ +"""Fragmented diagnostic labels retain parent identity across real materialization.""" + +import json +from collections import Counter +from pathlib import Path +from zipfile import ZipFile + +import numpy as np +import pytest +import tifffile +from polystore.disk import DiskStorageBackend +from polystore.filemanager import FileManager +from polystore.roi import ROI_ZIP_METADATA_MEMBER, load_rois_from_zip + +from openhcs.core.runtime_object_labels import ( + ObjectLabelPayload, + ObjectLabelVariantData, +) +from openhcs.core.source_spatial_domain import SourceSpatialDomain +from openhcs.processing.materialization import ( + ImageFileOptions, + MaterializationSpec, + ROIOptions, + materialization_outputs, +) + + +def fragmented_labels(): + labels = np.zeros((128, 128), dtype=np.int32) + labels[4:124:4, 4:124:4] = 188 + labels[1:3, 1:3] = 7 + labels[125:127, 125:127] = 42 + return labels + + +def _payload(): + return ObjectLabelPayload( + variant_data=ObjectLabelVariantData(labels=fragmented_labels()), + source_path="/synthetic/raw.tif", + source_component_metadata={"well": "A01", "channel": 1}, + source_spatial_domain=SourceSpatialDomain( + origin_yx=(10, 20), + source_shape_yx=(256, 256), + ), + ) + + +def test_archive_fragments_keep_parent_label_and_source_geometry(tmp_path): + filemanager = FileManager({"disk": DiskStorageBackend()}) + outputs = materialization_outputs( + MaterializationSpec(ROIOptions(min_area=1)), + _payload(), + str(tmp_path / "fragmented"), + filemanager, + ) + archive = next(output for output in outputs if output.path.endswith(".roi.zip")) + summary = next(output for output in outputs if output.path.endswith(".txt")) + assert "Segmentation ROIs: 3 parent labels\n" in summary.content + parents = archive.content + assert Counter(roi.metadata["label"] for roi in parents) == {7: 1, 42: 1, 188: 1} + assert Counter({roi.metadata["label"]: len(roi.shapes) for roi in parents}) == { + 7: 1, + 42: 1, + 188: 900, + } + filemanager.save(parents, archive.path, "disk") + with ZipFile(archive.path) as zipped: + members = [name for name in zipped.namelist() if name.endswith(".roi")] + assert len(members) == 902 + metadata = json.loads(zipped.read(ROI_ZIP_METADATA_MEMBER)) + assert Counter(value["label"] for value in metadata.values()) == { + 7: 1, + 42: 1, + 188: 900, + } + # Native reopen flattens shapes, but every member still owns its parent label. + restored = load_rois_from_zip(Path(archive.path)) + assert Counter(roi.metadata["label"] for roi in restored) == {7: 1, 42: 1, 188: 900} + fragmented = next(roi for roi in restored if roi.metadata["label"] == 188) + assert fragmented.metadata["area"] == 900 + assert fragmented.metadata["centroid"] == (72.0, 82.0) + coordinates = np.concatenate( + [roi.shapes[0].coordinates for roi in restored if roi.metadata["label"] == 188] + ) + assert coordinates[:, 0].min() == pytest.approx(13.5) + assert coordinates[:, 0].max() == pytest.approx(130.5) + assert coordinates[:, 1].min() == pytest.approx(23.5) + assert coordinates[:, 1].max() == pytest.approx(140.5) + + +def test_declared_raster_selection_preserves_parent_labels_without_roi_extraction( + tmp_path, monkeypatch +): + monkeypatch.setattr( + "polystore.roi.extract_rois_from_labeled_mask", + lambda *args, **kwargs: pytest.fail("Unselected ROI extraction ran"), + ) + filemanager = FileManager({"disk": DiskStorageBackend()}) + outputs = materialization_outputs( + MaterializationSpec( + ROIOptions(min_area=1), ImageFileOptions(filename_suffix=".tif") + ), + _payload(), + str(tmp_path / "fragmented"), + filemanager, + output_path_filter=lambda path: path.suffix == ".tif", + ) + assert len(outputs) == 1 + filemanager.save(outputs[0].content, outputs[0].path, "disk") + np.testing.assert_array_equal(tifffile.imread(outputs[0].path), fragmented_labels()) From ccdb10eb6070998fea3a4c19568edacc9d6f95ac Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Tue, 29 Sep 2026 14:49:25 -0400 Subject: [PATCH 03/19] Record final scoped review paths and owned scratch cleanup --- .../input_preparation_checkpoint_20260929.md | 26 +++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/docs/plans/input_preparation_checkpoint_20260929.md b/docs/plans/input_preparation_checkpoint_20260929.md index 96b163490f..6fac74c184 100644 --- a/docs/plans/input_preparation_checkpoint_20260929.md +++ b/docs/plans/input_preparation_checkpoint_20260929.md @@ -84,7 +84,7 @@ owner tracing, not a full NRA scan, proof replay or global clean-debt claim. | Pattern | Concrete owner / witness | New-case check and disposition | | --- | --- | --- | | IMPL-12, copied procedure | `synthetic_data.py:827`, `SyntheticMicroscopyGenerator._site_position`; both layout loops call it at 875/881. | A shared positioning change now needs one owner edit, not two copied procedures. Zero jitter/overlap and both layout families have deterministic/count tests. Duplicate layout-position procedure removed. | -| IMPL-1/2/3/4, external family dispatch | `source_matching.py:114`, `SourceFilterMatcher`; `SourceFilterTargetResolver:232`; adapter discovery uses the existing `AutoRegisterMeta` registry (`bioformats_adapter.py:555`). | Directory plus OR-group clauses and a second/unrestricted alias require declarations only, no preparation switch edits. Ordinary numeric geometry and filesystem identity checks are not kind dispatch. The speculative second detection method was deleted; existing `detect` owns extension. | +| IMPL-1/2/3/4, external family dispatch | `source_matching.py:114`, `SourceFilterMatcher`; `SourceFilterTargetResolver:232`; adapter discovery uses the existing `AutoRegisterMeta` registry (`bioformats_adapter.py:556`). | Directory plus OR-group clauses and a second/unrestricted alias require declarations only, no preparation switch edits. Ordinary numeric geometry and filesystem identity checks are not kind dispatch. The speculative second detection method was deleted; existing `detect` owns extension. | | MEMB-1/2/3, duplicate rosters | `BioFormatsJavaAdapter._is_single_file` at 828 calls the decoder capability, not a hand-maintained extension/companion list. | A compound decoder response survives a nonmatching entrypoint and selects its decoded companion. A new single-file name uses the same clauses; no new filename registry or roster. | | BOUND-1/2/6/7, bypassing decoded/config owners | `SourceBindingsConfig` at 1833/1842 owns path decisions; `SourceBindingWorkspaceProjector.candidate_matches_binding` at 966 owns decoded selectors. | Exact-source and metadata-only tests preserve URI resolution and defer unknown metadata. No raw configuration dictionary, `getattr` default, attribute-name switch or metadata mirror was introduced. AST guards verify the declaration matcher and single detection contract. | | IDEN-2/6, conflated count/identity | ROI writer summary at `materialization/core.py:3047`; existing PolyStore parent-label metadata and archive converter retain identities. | Label 188 remains one parent with 900 fragments after archive reopen, not 900 new parents. New parent values need no relabeling/table edit. Summary ambiguity corrected; stronger topology fidelity remains open. | @@ -124,8 +124,30 @@ priority checkpoint, no new test/JVM/GUI run starts until Euler releases validat Scratch owner: Lovelace. Purpose: tiny generated regression plates and pytest fixtures. Path: `/home/ts/.cache/agent-scratch/openhcs-issue-input-20260929`. -Remove only this owned disposable output after preserving results here. +Cleanup completed after preserving results here: only this owned 8.1 MiB +disposable directory was removed. Fixtures can be regenerated by the tests; +there is no retained copy of the disposable output. Source, PR body and evidence stay in the persistent worktree. No merge/install performed. Installed acceptance is reserved to the coordinator. Do not close either issue solely from source tests. + +## Changed paths + +Generation: `openhcs/demo/synthetic_data.py`. + +Preparation: `openhcs/core/source_bindings.py`, +`openhcs/core/source_binding_workspace.py`, `openhcs/microscopes/bioformats.py`, +`openhcs/microscopes/bioformats_adapter.py`, +`openhcs/microscopes/microscope_base.py`, +`openhcs/microscopes/source_bindings_handler.py`. + +ROI summary: `openhcs/processing/materialization/core.py`. + +Regressions: `tests/unit/test_synthetic_generator_zero_geometry.py`, +`tests/unit/agent/test_synthetic_plate_zero_geometry_journey.py`, +`tests/unit/test_bioformats_preparation_selection.py`, +`tests/unit/test_fragmented_roi_materialization.py`. + +Profile/evidence: `benchmark/fragmented_roi_profile.py`, this checkpoint document. +No external submodule source or reserved viewer/measurement files changed. From 808ea7a79daa0e6b75358571451ffb8ec89310e0 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Tue, 29 Sep 2026 14:59:06 -0400 Subject: [PATCH 04/19] Add paired ROI topology raster witness and validation boundary --- .../input_preparation_checkpoint_20260929.md | 46 ++++++++++++++----- .../test_fragmented_roi_materialization.py | 19 ++++++-- 2 files changed, 48 insertions(+), 17 deletions(-) diff --git a/docs/plans/input_preparation_checkpoint_20260929.md b/docs/plans/input_preparation_checkpoint_20260929.md index 6fac74c184..0204446907 100644 --- a/docs/plans/input_preparation_checkpoint_20260929.md +++ b/docs/plans/input_preparation_checkpoint_20260929.md @@ -61,19 +61,38 @@ phase and does not imply production does both. Three contour calls yield 902 members; two `find_objects` calls confirm a repeated whole-mask bbox scan. These are single bounded observations, not a large-mask speedup claim. -Remaining optimization belongs to PolyStore's +The paired optimization belongs to PolyStore's `TwoDimensionalLabeledMaskROIExtractor.extract`: `regionprops` already supplies the bbox and cached region image, but extraction separately calls `find_objects` -and constructs that binary crop again. Proposed in-place change: reuse those -existing properties, retaining labels, bbox, origin and contour ordering. No -external submodule was changed without coordinator coordination. No parallel -extractor/cache will be added in OpenHCS. The coordinator must authorize/assign -this paired submodule change before implementation and validation resume. - -Fidelity limits: this fixture checks disconnected islands, identities, metadata, -coordinates and int32 raster preservation. It does not establish arbitrary -hole/border/topology rasterization equivalence, 3D fidelity, or fidelity of the -private diagnostic archives. No biological inputs or frozen Euler output opened. +and constructs that binary crop again. The owner authorized the in-place change; +it is published in [PolyStore draft #16](https://github.com/OpenHCSDev/PolyStore/pull/16), +commit `8c886e7`, using those public properties and deleting the replaced scan, +crop/equality construction and cast. No new extractor/cache/registry. Source +pruning and provenance are unchanged. Scope excludes coordinator #211's +execution-session/capability write-authority repair and Zeno's #204 runtime repair. + +New child regressions are authored for holes with nested other-parent islands, +all borders, one-pixel bridges, diagonal contact, disconnected border pixels, +zero/nonzero origins, exact contour vertices/order, parent metadata, archive +reopen, exact full-canvas masks and one bbox scan. Parent raster-only TIFF +materialization now has an additional holes/borders fixture. These new checks +have passed source AST/Ruff/diff guards, but have NOT run: the validation lock +probe returned 75 (busy). Euler retains the H003 slot; no test/JVM/GUI started. +Available RAM at that checkpoint: 13.2 GiB, historical swap 11.5 GiB. + +The parent recorded gitlink remains `f94bbbe8631a4c78f7ceaec72fb99671b58c9a18`. +The local isolated child is on published `8c886e7` for the future paired test; +the recorded gitlink will not advance before validated paired coordinator review. +Child durable receipt: `external/PolyStore/docs/roi_region_properties_checkpoint_20260929.md`. + +Fidelity limits: already-run evidence checks disconnected islands, identities, +metadata, coordinates and int32 raster preservation. New hole/border/topology +execution is pending. Existing polygons represent independent contours, without +a declared hole-subtraction relationship. Even if ring coordinates and parent +metadata survive ZIP reopen, that alone is not ZIP-to-raster/viewer equivalence. +MaskShape is unsupported by the existing ImageJ ROI codec. Do not invent an +equivalence algorithm. Exact masks and label TIFFs are separate evidence. 3D and +private diagnostic fidelity remain unverified. No biology/Euler output opened. ## Essential catalog review (changed surface only) @@ -150,4 +169,7 @@ Regressions: `tests/unit/test_synthetic_generator_zero_geometry.py`, `tests/unit/test_fragmented_roi_materialization.py`. Profile/evidence: `benchmark/fragmented_roi_profile.py`, this checkpoint document. -No external submodule source or reserved viewer/measurement files changed. +Paired continuation: `external/PolyStore/src/polystore/roi.py`, +`external/PolyStore/tests/test_roi_region_properties.py`, +`external/PolyStore/docs/roi_region_properties_checkpoint_20260929.md`. +Parent recorded gitlink is preserved. No reserved viewer/measurement/#211 files changed. diff --git a/tests/unit/test_fragmented_roi_materialization.py b/tests/unit/test_fragmented_roi_materialization.py index 11b7e91958..a65486534d 100644 --- a/tests/unit/test_fragmented_roi_materialization.py +++ b/tests/unit/test_fragmented_roi_materialization.py @@ -33,9 +33,11 @@ def fragmented_labels(): return labels -def _payload(): +def _payload(labels=None): return ObjectLabelPayload( - variant_data=ObjectLabelVariantData(labels=fragmented_labels()), + variant_data=ObjectLabelVariantData( + labels=fragmented_labels() if labels is None else labels + ), source_path="/synthetic/raw.tif", source_component_metadata={"well": "A01", "channel": 1}, source_spatial_domain=SourceSpatialDomain( @@ -88,23 +90,30 @@ def test_archive_fragments_keep_parent_label_and_source_geometry(tmp_path): assert coordinates[:, 1].max() == pytest.approx(140.5) +@pytest.mark.parametrize("with_holes_and_borders", (False, True)) def test_declared_raster_selection_preserves_parent_labels_without_roi_extraction( - tmp_path, monkeypatch + tmp_path, monkeypatch, with_holes_and_borders ): monkeypatch.setattr( "polystore.roi.extract_rois_from_labeled_mask", lambda *args, **kwargs: pytest.fail("Unselected ROI extraction ran"), ) + labels = fragmented_labels() + if with_holes_and_borders: + labels[:32, :32] = 7 + labels[8:24, 8:24] = 0 + labels[12:16, 12:16] = 42 + labels[-1, -1] = 188 filemanager = FileManager({"disk": DiskStorageBackend()}) outputs = materialization_outputs( MaterializationSpec( ROIOptions(min_area=1), ImageFileOptions(filename_suffix=".tif") ), - _payload(), + _payload(labels), str(tmp_path / "fragmented"), filemanager, output_path_filter=lambda path: path.suffix == ".tif", ) assert len(outputs) == 1 filemanager.save(outputs[0].content, outputs[0].path, "disk") - np.testing.assert_array_equal(tifffile.imread(outputs[0].path), fragmented_labels()) + np.testing.assert_array_equal(tifffile.imread(outputs[0].path), labels) From 515018f41f812e2b16ce01ceabaebb0b9ef3fff2 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Tue, 29 Sep 2026 15:57:47 -0400 Subject: [PATCH 05/19] Retain bounded paired validation failures and fidelity evidence --- .../input_preparation_checkpoint_20260929.md | 31 ++++++++++++++----- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/docs/plans/input_preparation_checkpoint_20260929.md b/docs/plans/input_preparation_checkpoint_20260929.md index 0204446907..d6c52b364f 100644 --- a/docs/plans/input_preparation_checkpoint_20260929.md +++ b/docs/plans/input_preparation_checkpoint_20260929.md @@ -71,23 +71,36 @@ crop/equality construction and cast. No new extractor/cache/registry. Source pruning and provenance are unchanged. Scope excludes coordinator #211's execution-session/capability write-authority repair and Zeno's #204 runtime repair. -New child regressions are authored for holes with nested other-parent islands, +New child regressions cover holes with nested other-parent islands, all borders, one-pixel bridges, diagonal contact, disconnected border pixels, zero/nonzero origins, exact contour vertices/order, parent metadata, archive reopen, exact full-canvas masks and one bbox scan. Parent raster-only TIFF materialization now has an additional holes/borders fixture. These new checks -have passed source AST/Ruff/diff guards, but have NOT run: the validation lock -probe returned 75 (busy). Euler retains the H003 slot; no test/JVM/GUI started. -Available RAM at that checkpoint: 13.2 GiB, historical swap 11.5 GiB. +passed source AST/Ruff/diff guards. At the handed-off finite validation slot, +all sixteen new child cases and fragmented materialization checks passed, +alongside four fresh real MCP journeys. The combined dependency/application +suite did NOT fully pass: 141 passed / 26 failed in 37.12s. Failed workspace +cases looked for the generic `polystore_metadata.json` after the application +wrote `openhcs_metadata.json`. Collection/bootstrap import order is the source- +supported diagnosis; separate-context preservation verification remains open. +The first attempt had 29 passes / 138 setup errors because its cleaned scratch +parent was missing. No assertions weakened. Phase profile did not run after +the failed suite, so there is no after-change timing claim. + +Both finite commands are terminal; flock released; tagged audit shows no owned +pytest/MCP handles. Entry guard: 13.4 GiB available RAM / historical swap 11.9 +GiB. No JVM/GUI, installation, merge or frozen-harness mutation. Next runtime +check depends on the coordinator's next slot; fresh author now has priority. The parent recorded gitlink remains `f94bbbe8631a4c78f7ceaec72fb99671b58c9a18`. -The local isolated child is on published `8c886e7` for the future paired test; +The local isolated child retains tested source `8c886e7` plus its published +finite-validation receipt for the future paired test; the recorded gitlink will not advance before validated paired coordinator review. Child durable receipt: `external/PolyStore/docs/roi_region_properties_checkpoint_20260929.md`. -Fidelity limits: already-run evidence checks disconnected islands, identities, -metadata, coordinates and int32 raster preservation. New hole/border/topology -execution is pending. Existing polygons represent independent contours, without +Fidelity limits: executed evidence checks disconnected islands, identities, +hole/border/topology contour geometry, parent metadata, archive reopen, exact +masks and int32 TIFF raster preservation. Existing polygons represent independent contours, without a declared hole-subtraction relationship. Even if ring coordinates and parent metadata survive ZIP reopen, that alone is not ZIP-to-raster/viewer equivalence. MaskShape is unsupported by the existing ImageJ ROI codec. Do not invent an @@ -146,6 +159,8 @@ fixtures. Path: `/home/ts/.cache/agent-scratch/openhcs-issue-input-20260929`. Cleanup completed after preserving results here: only this owned 8.1 MiB disposable directory was removed. Fixtures can be regenerated by the tests; there is no retained copy of the disposable output. +The handed-off finite check regenerated 4.2 MiB of synthetic fixtures; only +that disposable directory was removed after retaining the results above. Source, PR body and evidence stay in the persistent worktree. No merge/install performed. Installed acceptance is reserved to the coordinator. From 32a2b27e22220b28f4a75db1977973aa61384a10 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Tue, 29 Sep 2026 16:10:11 -0400 Subject: [PATCH 06/19] Factor preparation admission into binding owners and consume Java capability --- openhcs/core/source_binding_workspace.py | 22 +- openhcs/core/source_bindings.py | 37 ++- openhcs/microscopes/bioformats_adapter.py | 17 +- .../test_bioformats_preparation_selection.py | 11 +- .../test_source_binding_path_admission.py | 244 ++++++++++++++++++ 5 files changed, 280 insertions(+), 51 deletions(-) create mode 100644 tests/unit/test_source_binding_path_admission.py diff --git a/openhcs/core/source_binding_workspace.py b/openhcs/core/source_binding_workspace.py index b9469510e5..02230380d8 100644 --- a/openhcs/core/source_binding_workspace.py +++ b/openhcs/core/source_binding_workspace.py @@ -35,7 +35,6 @@ overlay_source_metadata, semantic_source_metadata_value, source_component_metadata_values, - source_filters_match, source_metadata_component, source_metadata_value, source_metadata_values_equal, @@ -970,22 +969,15 @@ def candidate_matches_binding( source_root: Path, ) -> bool: """Return whether one candidate satisfies one typed binding selector.""" - if binding.explicit_source is not None: - explicit_path = Path( - binding.explicit_source.resolved(source_root).uri - ).resolve() - candidate_path = Path(candidate.relative_path) - if not candidate_path.is_absolute(): - candidate_path = Path(source_root) / candidate_path - if candidate_path.resolve() != explicit_path: - return False + if not binding.physical_path_matches( + source_root, + candidate.relative_path, + candidate.source_filter_path_identities(), + ): + return False selector = binding.selector return ( - any( - source_filters_match(path, selector.filters) - for path in candidate.source_filter_path_identities() - ) - and all( + all( ( value := semantic_source_metadata_value( candidate.metadata, item.field diff --git a/openhcs/core/source_bindings.py b/openhcs/core/source_bindings.py index 85e4a1d5a8..7f2bf54e30 100644 --- a/openhcs/core/source_bindings.py +++ b/openhcs/core/source_bindings.py @@ -703,6 +703,12 @@ def __post_init__(self) -> None: ), ) + def path_filters_match(self, identities: tuple[str, ...]) -> bool: + """Match store-owned path provenance, without guessing decoded identity.""" + from openhcs.core.source_matching import source_filters_match + + return any(source_filters_match(path, self.filters) for path in identities) + def source_alias_measurement_names(alias: str) -> tuple[str, ...]: """Return measurement source-name tokens represented by a source alias.""" @@ -954,6 +960,26 @@ def __post_init__(self) -> None: object.__setattr__(self, "source_channel_axis", source_channel_axis) object.__setattr__(self, "source_channel_counts", source_channel_counts) + def physical_path_matches( + self, + source_root: Path, + source_path: str | Path, + path_identities: tuple[str, ...], + ) -> bool: + """Admit one physical source using exact identity and path provenance. + + Companion identities may satisfy selector filters, but do not replace + the exact source URI. Metadata/components require subsequent decoding. + """ + if self.explicit_source is not None: + explicit_path = Path(self.explicit_source.resolved(source_root).uri) + candidate_path = Path(source_path) + if not candidate_path.is_absolute(): + candidate_path = source_root / candidate_path + if candidate_path.resolve() != explicit_path.resolve(): + return False + return self.selector.path_filters_match(path_identities) + def source_channel_axis_for_shape( self, shape: tuple[int, ...], @@ -1846,8 +1872,6 @@ def discovery_path_matches(self, root: Path, path: Path) -> bool: unrestricted binding keeps discovery unrestricted, not just the first filtered alias. All matching uses the existing source-filter owners. """ - from openhcs.core.source_matching import source_filters_match - root = root.resolve(strict=False) resolved = path.resolve(strict=False) identities = (resolved.as_posix(),) @@ -1858,14 +1882,7 @@ def discovery_path_matches(self, root: Path, path: Path) -> bool: if not self.source_path_filters_match(identities): return False return not self.binding_declarations or any( - ( - binding.explicit_source is None - or Path(binding.explicit_source.resolved(root).uri).resolve() == resolved - ) - and any( - source_filters_match(identity, binding.selector.filters) - for identity in identities - ) + binding.physical_path_matches(root, resolved, identities) for binding in self.binding_declarations ) diff --git a/openhcs/microscopes/bioformats_adapter.py b/openhcs/microscopes/bioformats_adapter.py index 80f6c4d8d6..761723289e 100644 --- a/openhcs/microscopes/bioformats_adapter.py +++ b/openhcs/microscopes/bioformats_adapter.py @@ -788,7 +788,7 @@ def discover_stores(self, root: Path) -> tuple[SourcePlaneDataset, ...]: continue if ( not self.source_bindings.discovery_path_matches(root, source_path) - and self._is_single_file(context, source_path) + and context.is_single_file(source_path) ): continue try: @@ -824,21 +824,6 @@ def discover_stores(self, root: Path) -> tuple[SourcePlaneDataset, ...]: ) return tuple(datasets) - @staticmethod - def _is_single_file(context: BioFormatsJavaContext, path: Path) -> bool: - """Ask the decoder before pruning a potentially compound entrypoint. - - ImageReader.isSingleFile identifies its format without setId/OME - metadata initialization. A companion path can select a multi-file store - whose entrypoint itself does not match; decoded projection decides that. - """ - context.ensure_initialized() - reader = context.ImageReader() - try: - return bool(reader.isSingleFile(str(path))) - finally: - reader.close() - @staticmethod def _declares_path(context: BioFormatsJavaContext, path: Path) -> bool: if ImageFileFormat.is_image_path(path): diff --git a/tests/unit/test_bioformats_preparation_selection.py b/tests/unit/test_bioformats_preparation_selection.py index 133cd855c9..2153a60d86 100644 --- a/tests/unit/test_bioformats_preparation_selection.py +++ b/tests/unit/test_bioformats_preparation_selection.py @@ -47,18 +47,9 @@ class _SelectionContext(FakeBioFormatsContext): single_file = True - def ensure_initialized(self): - pass - - def ImageReader(self): - return self - - def isSingleFile(self, path): + def is_single_file(self, path): return self.single_file - def close(self): - pass - @pytest.mark.parametrize("selection", ("global", "binding")) @pytest.mark.parametrize("entrypoint", ("handler", "orchestrator", "auto_orchestrator")) diff --git a/tests/unit/test_source_binding_path_admission.py b/tests/unit/test_source_binding_path_admission.py new file mode 100644 index 0000000000..7a4359a886 --- /dev/null +++ b/tests/unit/test_source_binding_path_admission.py @@ -0,0 +1,244 @@ +"""Shared declaration-owned path admission before and after source decoding.""" + +import ast +from pathlib import Path + +import pytest +from polystore.virtual_workspace import SourcePixelRef + +from openhcs.constants.constants import AllComponents +from openhcs.core.source_binding_workspace import SourceBindingWorkspaceProjector +from openhcs.core.source_bindings import ( + ComponentSelector, + ImagePlaneSource, + MetadataSelector, + NamedSourceBinding, + SourceBindingsConfig, + SourceFilterClause, + SourceFilterMatchType, + SourceFilterSubject, + SourceSelector, +) +from openhcs.core.source_projection import SourceCandidate + + +def _candidate(path, *, identities=(), metadata=None): + return SourceCandidate( + source_ref=SourcePixelRef("disk", str(path)), + relative_path=str(path), + source_filter_paths=identities, + metadata=metadata or {}, + ) + + +@pytest.mark.parametrize("absolute_candidate", (False, True)) +@pytest.mark.parametrize("explicit_uri", ("relative", "absolute", "file_uri")) +def test_exact_source_admission_agrees_before_and_after_decode( + tmp_path, absolute_candidate, explicit_uri +): + selected = tmp_path / "selected.czi" + selected.touch() + uris = { + "relative": selected.name, + "absolute": str(selected), + "file_uri": selected.as_uri(), + } + binding = NamedSourceBinding( + alias="selected", explicit_source=ImagePlaneSource(uri=uris[explicit_uri]) + ) + config = SourceBindingsConfig(bindings=(binding,)) + projector = SourceBindingWorkspaceProjector(config) + for path, expected in ((selected, True), (tmp_path / "other.czi", False)): + candidate = _candidate(path if absolute_candidate else path.name) + assert config.discovery_path_matches(tmp_path, path) is expected + assert ( + projector.candidate_matches_binding(candidate, binding, tmp_path) + is expected + ) + + +@pytest.mark.parametrize( + "relative_path, expected", + ( + ("selected/a.czi", True), + ("selected/b.czi", True), + ("selected/c.czi", False), + ("other/a.czi", False), + ), +) +def test_binding_directory_regex_and_or_group_use_the_same_owner( + tmp_path, relative_path, expected +): + binding = NamedSourceBinding( + alias="selected", + selector=SourceSelector( + filters=( + SourceFilterClause( + SourceFilterSubject.DIRECTORY, + SourceFilterMatchType.CONTAINS_REGEX, + r"(?:^|/)selected$", + ), + SourceFilterClause( + SourceFilterSubject.FILE, + SourceFilterMatchType.EQUALS, + "a.czi", + any_group=0, + ), + SourceFilterClause( + SourceFilterSubject.FILE, + SourceFilterMatchType.EQUALS, + "b.czi", + any_group=0, + ), + ) + ), + ) + config = SourceBindingsConfig(bindings=(binding,)) + candidate = _candidate(relative_path) + assert config.discovery_path_matches(tmp_path, tmp_path / relative_path) is expected + assert ( + SourceBindingWorkspaceProjector(config).candidate_matches_binding( + candidate, binding, tmp_path + ) + is expected + ) + + +@pytest.mark.parametrize( + "metadata, expected", + ( + ({"well": "A01", "channel": "1"}, True), + ({"well": "B01", "channel": "1"}, False), + ({"well": "A01", "channel": "2"}, False), + ({}, False), + ), +) +def test_physical_admission_does_not_predict_decoded_metadata_or_components( + tmp_path, metadata, expected +): + binding = NamedSourceBinding( + alias="selected", + selector=SourceSelector( + metadata=(MetadataSelector("well", "A01"),), + components=(ComponentSelector(AllComponents.CHANNEL, "1"),), + ), + ) + config = SourceBindingsConfig(bindings=(binding,)) + assert config.discovery_path_matches(tmp_path, tmp_path / "unknown.czi") + assert ( + SourceBindingWorkspaceProjector(config).candidate_matches_binding( + _candidate("unknown.czi", metadata=metadata), binding, tmp_path + ) + is expected + ) + + +def test_decoded_companion_filters_do_not_replace_exact_entrypoint_identity(tmp_path): + entrypoint = tmp_path / "plate.fake" + companion = tmp_path / "well-a01.tif" + entrypoint.touch() + companion.touch() + selector = SourceSelector( + filters=( + SourceFilterClause( + SourceFilterSubject.FILE, + SourceFilterMatchType.EQUALS, + companion.name, + ), + ) + ) + candidate = _candidate( + entrypoint.name, identities=(entrypoint.name, companion.name) + ) + for exact_source, expected in ( + (None, True), + (entrypoint.name, True), + (companion.name, False), + ): + binding = NamedSourceBinding( + alias="selected", + selector=selector, + explicit_source=None + if exact_source is None + else ImagePlaneSource(uri=exact_source), + ) + config = SourceBindingsConfig(bindings=(binding,)) + # Discovery cannot see companions; the adapter must keep compound readers. + assert not config.discovery_path_matches(tmp_path, entrypoint) + assert ( + SourceBindingWorkspaceProjector(config).candidate_matches_binding( + candidate, binding, tmp_path + ) + is expected + ) + + +def test_unrestricted_alias_keeps_union_without_erasing_other_alias_filters(tmp_path): + selected = NamedSourceBinding( + alias="selected", + selector=SourceSelector( + filters=( + SourceFilterClause( + SourceFilterSubject.FILE, + SourceFilterMatchType.EQUALS, + "selected.czi", + ), + ) + ), + ) + unrestricted = NamedSourceBinding(alias="all") + config = SourceBindingsConfig(bindings=(selected, unrestricted)) + other = _candidate("other.czi") + assert config.discovery_path_matches(tmp_path, tmp_path / other.relative_path) + projector = SourceBindingWorkspaceProjector(config) + assert not projector.candidate_matches_binding(other, selected, tmp_path) + assert projector.candidate_matches_binding(other, unrestricted, tmp_path) + + +def test_preparation_consumers_cannot_reauthor_binding_or_java_lifetime(): + """Guard the two reviewed owner bypasses, without importing a Java runtime.""" + root = Path(__file__).resolve().parents[2] + for path, class_name, method_name, owner_call, forbidden in ( + ( + "openhcs/core/source_bindings.py", + "SourceBindingsConfig", + "discovery_path_matches", + "physical_path_matches", + {"explicit_source", "selector"}, + ), + ( + "openhcs/core/source_binding_workspace.py", + "SourceBindingWorkspaceProjector", + "candidate_matches_binding", + "physical_path_matches", + {"explicit_source", "filters"}, + ), + ( + "openhcs/microscopes/bioformats_adapter.py", + "BioFormatsJavaAdapter", + "discover_stores", + "is_single_file", + {"ImageReader", "ensure_initialized", "isSingleFile"}, + ), + ): + tree = ast.parse((root / path).read_text()) + owner = next( + node + for node in tree.body + if isinstance(node, ast.ClassDef) and node.name == class_name + ) + method = next( + node + for node in owner.body + if isinstance(node, ast.FunctionDef) and node.name == method_name + ) + attributes = { + node.attr for node in ast.walk(method) if isinstance(node, ast.Attribute) + } + assert owner_call in attributes, (path, method_name) + assert not attributes & forbidden, (path, method_name, attributes & forbidden) + if class_name == "BioFormatsJavaAdapter": + assert not any( + isinstance(node, ast.FunctionDef) and node.name == "_is_single_file" + for node in owner.body + ) From 6ffea8d61f9ccc0296347d81c7298105f4d1cc5b Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Tue, 29 Sep 2026 16:14:10 -0400 Subject: [PATCH 07/19] Record addressed independent ownership findings and remaining gates --- .../input_preparation_checkpoint_20260929.md | 75 ++++++++++++++++++- 1 file changed, 74 insertions(+), 1 deletion(-) diff --git a/docs/plans/input_preparation_checkpoint_20260929.md b/docs/plans/input_preparation_checkpoint_20260929.md index d6c52b364f..1b464598be 100644 --- a/docs/plans/input_preparation_checkpoint_20260929.md +++ b/docs/plans/input_preparation_checkpoint_20260929.md @@ -5,6 +5,77 @@ Branch: `fix/input-preparation-20260929`. Worktree: `/home/ts/wt/openhcs-input-preparation-20260929`. Issues: #172 and #132. +## Independent review follow-through: physical admission and Java lifecycle + +Review: https://github.com/OpenHCSDev/openhcs/pull/206#issuecomment-5897315214. +The complete comment reviewed parent `006361ff2`, recorded child `f94bbbe`, and +ROI source `8c886e7`. Both returned production findings were valid, despite the +earlier scoped review below. Lovelace picked up both; no competing owner/PR. +Current source commits: OpenHCS `32a2b27e22220b28f4a75db1977973aa61384a10`, +paired PolyStore `0aa057bc3b4241d77f5f3905e10e31efe2ee87e4`. + +1. IMPL-12 / BOUND-2: `NamedSourceBinding.physical_path_matches` at + `source_bindings.py:963` owns explicit-source resolution/equality and delegates + path clauses to `SourceSelector.path_filters_match:706`. That selector calls + the existing matcher/target-resolver families through `source_filters_match`. + Discovery (`SourceBindingsConfig.discovery_path_matches:1868`) and final + projection (`SourceBindingWorkspaceProjector.candidate_matches_binding:965`) + both invoke the same binding operation. Delete their duplicated exact-path + procedures and selector-filter loops, plus the projector's unused matcher + import. Config still owns global filters and alias union; the projector still + owns decoded metadata/components. A binding path-policy change now needs one + owner edit instead of synchronized consumer edits. No filename registry, + companion roster, raw record or metadata mirror. +2. IMPL-13 / BOUND-2: `BioFormatsJavaContext.is_single_file:158` owns the decoder + capability. Both it and existing `declares_path:153` use context-owned + `_probe_reader:168` for initialization, construction and unconditional close. + OpenHCS calls `context.is_single_file` at `bioformats_adapter.py:791`; delete + its entire `_is_single_file` helper and the fixture's imitation reader lifetime. + A compound reader remains discoverable when its entrypoint fails path filters; + decoded companion provenance remains available for final selection. Source + admission policy stays OpenHCS-owned. No setId/OME metadata open in the probe. + The existing valid ROI ownership/optimization is unchanged by this follow-up. + +Light executed evidence: **28 passed in 0.50s**, peak Python RSS **72.8 MiB**. +`tests/unit/test_source_binding_path_admission.py` has 16 behavior cases plus one +AST guard: relative/absolute/file-URI exact identity, directory-regex/OR-group +clauses, unknown metadata/component deferral, decoded companion filters without +replacing exact entrypoint identity, and unrestricted alias union without erasing +another alias's restrictions. `external/PolyStore/tests/test_bioformats_java.py` +has four existing context cases, six new true/false/failure/retry probe cases and +one AST guard. Controlled external Java responses exercise actual context +initialization and close, not a JVM. Guards prevent the reviewed consumer bypasses +and prevent probe operations from copying construction/close instead of using +their shared context lifetime. New tests and touched child source pass Ruff; +both repository diffs pass whitespace checks. No full NRA scan/global proof. + +Driver: recorded interpreter with explicit worktree/eight submodule src paths, +verified actual OpenHCS, SourceBindings/projector, PolyStore Java, ObjectState and +metaclass-registry imports. Import the existing `openhcs` entrypoint first, then +`pytest.main` with `-q --tb=short --noconftest --import-mode=importlib +-p no:cacheprovider -o addopts=` and the two files above. Automatic plugins +disabled; shell bound 45s. No global application fixture/viewer cleanup, MCP, +JVM, GUI, environment creation or shared validation-lock acquisition. Two pytest +warnings report unused asyncio configuration with plugins disabled. Initial +26-case run passed before adding the two ownership guards (0.23s/67.1 MiB). + +Resource guard: 11.4 GiB available / historical swap warning 11.9 GiB; explicit +8 GiB admission checked. Final available RAM 11.2 GiB; not a continuous peak-RAM +claim. Confucius's runtime/lock/frozen installation were not touched. Tiny scratch +owner Lovelace, purpose empty source fixtures, path +`/home/ts/.cache/agent-scratch/openhcs-issue-input-20260929`: 92 KiB removed after +retaining results. No source, biological input/output or saved session removed. + +These 28 contract/guard passes **do not resolve** the original combined-suite +**141 passed / 26 failed** result. Separate-context application preservation, +the retained sixteen-container/AUTO/real-TIFF controls at this new source pair, +the after-change bounded ROI profile, real Java capability/CZI fidelity and +installed user-path acceptance remain open for a coordinator-handed-off finite +slot. The profile did not run. The recorded gitlink remains `f94bbbe`; the new +OpenHCS call requires the paired context commit before integration. Coordinator +owns validated gitlink adoption, normal main integration, merge/install and live +acceptance. No issue closure or installed readiness is claimed. + ## First checkpoint: #172 Zero pixel overlap uses unrestricted cell positions, not empty random ranges. @@ -107,11 +178,13 @@ MaskShape is unsupported by the existing ImageJ ROI codec. Do not invent an equivalence algorithm. Exact masks and label TIFFs are separate evidence. 3D and private diagnostic fidelity remain unverified. No biology/Euler output opened. -## Essential catalog review (changed surface only) +## Earlier essential catalog review (changed surface only) Audited against fetched `openhcsdev/main` at `98d9b9d23` and the working #206 checkpoint, using the owner's current archived catalog. This is source/AST and owner tracing, not a full NRA scan, proof replay or global clean-debt claim. +The later independent review found two bypasses this receipt missed; the +follow-through above supersedes its admission/lifecycle ownership claims. | Pattern | Concrete owner / witness | New-case check and disposition | | --- | --- | --- | From 101a6a4b0ec80a8ce970478399fb9527ef2f5b51 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Tue, 29 Sep 2026 17:15:17 -0400 Subject: [PATCH 08/19] Keep cold plate inspection responsive and inherit declared bundle cache (#224) --- openhcs/agent/capabilities.py | 13 +- openhcs/utils/environment.py | 3 + .../test_plate_inspection_cold_runtime.py | 128 ++++++++++++++++++ 3 files changed, 143 insertions(+), 1 deletion(-) create mode 100644 tests/unit/agent/test_plate_inspection_cold_runtime.py diff --git a/openhcs/agent/capabilities.py b/openhcs/agent/capabilities.py index b484bb97bd..992587a226 100644 --- a/openhcs/agent/capabilities.py +++ b/openhcs/agent/capabilities.py @@ -2034,6 +2034,8 @@ class GenerateSyntheticPlateCapability(PlatePathCapability): class InspectPlatePathCapability(PlatePathCapability): + progress_heartbeat_seconds = 5.0 + progress_worker_thread_safe = True name = "openhcs_inspect_plate_path" cli_command = "inspect-plate" kind = CapabilityKind.TOOL @@ -2045,9 +2047,18 @@ class InspectPlatePathCapability(PlatePathCapability): "workspace-preparation advice, and structured workflow routing. " "It does not configure a running UI or make a handler override the setup " "route; use the PlateManager code document plus selected-plate init when " - "the result must remain visible in the desktop." + "the result must remain visible in the desktop. Optional Bio-Formats " + "cold preparation can download verified Fiji artifacts into its declared " + "bundle cache and start Java; progress keeps this operation observable " + "without blocking unrelated MCP reads. Plate contents remain read-only." ) service = "selected_plate" + mutating = True + side_effects = ( + "may_download_verified_fiji_runtime", + "may_write_runtime_bundle_cache", + "may_start_java_runtime", + ) data_exposure = ( "local_plate_path", "microscope_metadata", diff --git a/openhcs/utils/environment.py b/openhcs/utils/environment.py index 0ddfab6b9e..97035ffd72 100644 --- a/openhcs/utils/environment.py +++ b/openhcs/utils/environment.py @@ -48,11 +48,14 @@ def current_numba_cache_path(cls) -> Path: def child_process_environment_keys(cls) -> tuple[str, ...]: """Return mode selectors required for semantic parity in child processes.""" + from polystore.imagej_distribution import FijiArchiveDistribution + return ( cls.cpu_only_key, cls.headless_key, cls.numba_cache_key, cls.use_threading_key, + FijiArchiveDistribution.cache_root_environment_key, ) @staticmethod diff --git a/tests/unit/agent/test_plate_inspection_cold_runtime.py b/tests/unit/agent/test_plate_inspection_cold_runtime.py new file mode 100644 index 0000000000..1ec8012df8 --- /dev/null +++ b/tests/unit/agent/test_plate_inspection_cold_runtime.py @@ -0,0 +1,128 @@ +"""Inspection uses existing progress/worker and bundle-root projection owners.""" + +import asyncio +import sys +import threading +from types import SimpleNamespace + +import pytest +from polystore.imagej_distribution import FijiArchiveDistribution + +from openhcs.agent.capabilities import InspectPlatePathCapability +from openhcs.agent.dto.common import SCHEMA_VERSION +from openhcs.agent.dto.plate import PlatePathInspectionResult +from openhcs.mcp.dev_client_core import McpDevServerSpec +from openhcs.utils.environment import OpenHCSProcessEnvironment + + +def test_mcp_launch_preserves_bundle_root_separately_from_run_cache( + monkeypatch, tmp_path +): + key = FijiArchiveDistribution.cache_root_environment_key + bundle_root = str(tmp_path / "bundles") + run_cache = str(tmp_path / "run-cache") + monkeypatch.setenv(key, bundle_root) + monkeypatch.setenv("XDG_CACHE_HOME", run_cache) + assert key in OpenHCSProcessEnvironment.child_process_environment_keys() + environment = McpDevServerSpec(sys.executable).environment() + assert environment[key] == bundle_root + assert environment["XDG_CACHE_HOME"] == run_cache + + +def test_inspection_declares_progress_and_optional_runtime_side_effects(): + capability = InspectPlatePathCapability.to_spec() + assert capability.progress_heartbeat_seconds == 5.0 + assert capability.progress_worker_thread_safe + assert capability.mutating + assert not capability.read_only + assert "may_download_verified_fiji_runtime" in capability.side_effects + assert "Plate contents remain read-only" in capability.description + + +def test_real_binding_keeps_discovery_read_responsive_during_controlled_cold_io(): + from openhcs.mcp import server + + async def exercise(): + loop = asyncio.get_running_loop() + entered = asyncio.Event() + release = threading.Event() + caller_thread = threading.get_ident() + observed_threads = [] + + class ControlledColdInspection: + def inspect(self, request): + observed_threads.append(threading.get_ident()) + loop.call_soon_threadsafe(entered.set) + assert release.wait(1), "Cold fixture was not released" + return PlatePathInspectionResult( + schema_version=SCHEMA_VERSION, + plate_path=request.plate_path, + requested_microscope_type=request.microscope_type, + ) + + built = server.build_server( + SimpleNamespace(plate_inspection_service=ControlledColdInspection()) + ) + pending = asyncio.create_task( + built.call_tool( + InspectPlatePathCapability.name, {"plate_path": "/controlled/plate"} + ) + ) + try: + await asyncio.wait_for(entered.wait(), 0.5) + assert not pending.done() + result = await asyncio.wait_for( + built.call_tool("openhcs_list_capabilities", {}), 0.5 + ) + assert result is not None + assert not pending.done(), ( + "Discovery must finish while inspection is still held" + ) + finally: + release.set() + await asyncio.wait_for(pending, 1) + assert observed_threads and observed_threads[0] != caller_thread + + asyncio.run(exercise()) + + +@pytest.mark.parametrize("fails", (False, True)) +def test_existing_progress_reports_held_work_and_propagates_terminal_failure( + monkeypatch, fails +): + from openhcs.mcp import server + + monkeypatch.setattr(InspectPlatePathCapability, "progress_heartbeat_seconds", 0.005) + + async def exercise(): + heartbeat = asyncio.Event() + messages = [] + error = RuntimeError("controlled cold initialization failed") + + class RecordingContext: + request_context = object() + + async def report_progress(self, progress, total=None, message=None): + messages.append(message) + if progress: + heartbeat.set() + + async def operation(): + await heartbeat.wait() + if fails: + raise error + return "ready" + + pending = server._await_with_declared_progress( + InspectPlatePathCapability.to_spec(), RecordingContext(), operation() + ) + if fails: + with pytest.raises(RuntimeError) as caught: + await asyncio.wait_for(pending, 1) + assert caught.value is error + else: + assert await asyncio.wait_for(pending, 1) == "ready" + assert messages[0] == "Inspect plate path: started" + assert "Inspect plate path: still running" in messages + + asyncio.run(exercise()) From 6bc71ee5cdd6ce124b64a7bd67719e3c38e74d9f Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Tue, 29 Sep 2026 17:20:50 -0400 Subject: [PATCH 09/19] Record issue224 source proof and cold installed gates --- .../input_preparation_checkpoint_20260929.md | 92 ++++++++++++++++++- 1 file changed, 91 insertions(+), 1 deletion(-) diff --git a/docs/plans/input_preparation_checkpoint_20260929.md b/docs/plans/input_preparation_checkpoint_20260929.md index 1b464598be..23891008a0 100644 --- a/docs/plans/input_preparation_checkpoint_20260929.md +++ b/docs/plans/input_preparation_checkpoint_20260929.md @@ -3,7 +3,97 @@ Implementation owner: Lovelace. Integration owner: OpenHCS coordinator. Branch: `fix/input-preparation-20260929`. Worktree: `/home/ts/wt/openhcs-input-preparation-20260929`. -Issues: #172 and #132. +Issues: #172, #132 and runtime follow-through #224. + +## Bundle-only cache and responsive inspection checkpoint: #224 + +Issue https://github.com/OpenHCSDev/openhcs/issues/224, implementation owner +Lovelace; existing drafts206/16. Parent owns disk recovery/integration, no +competing code patch. Coordinator reported fresh H001/H002 inspection creating +separate roughly859 MiB Fiji bundles through per-run XDG_CACHE_HOME, blocking +cold MCP and leaving queued10-second reads uncertain before disk-guard shutdown. +These are reported runtime observations, not worker-opened biological receipts. +No replay/restart, author coaching, installation or frozen-source/skill changes. + +No old supported application projection was found: cache_root was constructor- +owned but canonical FIJI_IMAGEJ_DISTRIBUTION/FIJI_IMAGEJ_RUNTIME/BioFormats context +had no process entrypoint. Inspection lacked the existing progress declaration, +so its synchronous operation ran on the MCP event-loop thread. +Source commits: OpenHCS `101a6a4b0ec80a8ce970478399fb9527ef2f5b51`, paired +PolyStore `5e656f8c20b0949139fe1273c31390931ea7ef6d`. + +- FijiArchiveDistribution declares **POLYSTORE_IMAGEJ_CACHE_ROOT** through + cache_root_environment_key at imagej_distribution.py:357. + cache_root_from_environment:368 decodes that owner key once into existing + cache_root when the canonical distribution is constructed at:520. Unset keeps + platformdirs policy; invalid explicit/relative settings fail, not fallback to + another per-run bundle. Release/digest/overlays, checksum, lock/staging and + runtime-policy authority remain unchanged. +- Existing OpenHCSProcessEnvironment.child_process_environment_keys at:48 derives + the dependency owner's key, not a copied spelling/default registry. Existing + McpDevServerSpec.environment at:300 preserves it through platform sanitization, + independently of per-run XDG_CACHE_HOME and log/cache policy. +- Existing InspectPlatePathCapability at:2036 declares five-second progress and + worker-thread execution. Existing MCP binding/heartbeat owns offloading and + observability; no new task/job/progress store or tool-name switch. Optional + downloads/cache writes/Java startup have truthful side effects and mutating + metadata; plate contents stay read-only. Artifact-plan/viewer/config owners + remain untouched. + +Exact new entrypoint: set POLYSTORE_IMAGEJ_CACHE_ROOT to an absolute reviewed +bundle directory **before process import/start**, keep per-run XDG_CACHE_HOME, +then use normal `python -m openhcs.mcp --surface full` or McpDevClient. This +requires the reviewed pair; the unchanged frozen installation does not support +the new projection. No real bundle was relocated. Do not configure retroactively +or restart/replay a live/uncertain process. Coordinator owns future startup. + +Executed **28 passed in5.07s / peak driver RSS267.4 MiB**:17 existing distribution +cases, six new root cases and five application projection/progress cases. +New files: external/PolyStore/tests/test_imagej_cache_environment.py, +external/PolyStore/tests/imagej_cache_process_fixture.py, +tests/unit/agent/test_plate_inspection_cold_runtime.py. +Two fresh Python processes import actual canonical distribution/runtime with +verified assigned paths. Only external host/artifact response is controlled: a +tiny local ZIP with real SHA-256, no overlays/JVM. Production verification, +lock/staging/discovery/reuse run. Independent XDG/log roots stay distinct; +verified download counts[1,0], one bundle directory, no temporary download +residue. Not a real859 MiB/Fiji/JVM performance claim. + +Actual in-process FastMCP binding/service invocation completes canonical +capability discovery while controlled inspection I/O is held on another thread. +Existing progress helper emits started/running and propagates the identical +terminal error. No MCP process/listen startup, JVM, GUI, remote download, +environment creation or paid provider. Not a continuous installed cold journey: +real stdio progress-token/queued reads, Java and live acceptance remain gates. + +Retain first10-pass/1-failure run4.91s/280.1 MiB: registry correctly rejected +effects without mutating metadata; fix declaration, not validator/assertions. +Two unused asyncio-option warnings with plugin autoload disabled. New tests, +helper and touched child source pass Ruff; six touched files parse, both diffs +pass whitespace. Whole environment-file Ruff reports untouched S110/BLE001 in +is_headless_mode; no broad clean claim. Driver uses recorded interpreter/explicit +parent and eight submodule src paths, verified imports, OpenHCS bootstrap before +pytest, disabled plugins/conftests and `-q --tb=short --noconftest +--import-mode=importlib -p no:cacheprovider -o addopts=`; shell45s bound. +Guard10.6 GiB available/historical swap11.9 GiB; explicit8 GiB admission, +final10.2 GiB. RSS is this driver, not continuous fleet peak-RAM evidence. + +Owned316 KiB tiny ZIP/cache/empty fixtures at the recorded scratch path removed +after results retained. A force-style cleanup was denied before execution; normal +non-force recursive removal of the validated exact owned path completed. No +source, saved sessions or blind data/output removed. All finite handles terminal; +no scientific validation-lock takeover. Original141-pass/26-failure receipt and +**not-run** after-change ROI profile remain open. Recorded gitlink staysf94bbbe; +validated paired adoption, normal main integration, preservation/profile and +installed/live acceptance await the next finite coordinator handoff. No closure. + +NRA/catalog receipt: BOUND-2/6 keeps cache_root/canonical distribution as config +authority; process consumers reference its declaration. MEMB-2/3: no second +environment/default registry, companion roster or cache copy/symlink. IMPL-1/4/13: +capability consumes existing generic progress/worker mechanism, not another +dispatch/job store. New root/run-cache values need configuration, not consumer +edits. Focused source/AST/contract checks only, not full NRA/native proof. Existing +ROI ownership and both previous review corrections remain intact. ## Independent review follow-through: physical admission and Java lifecycle From e0dd14ed3681828546f39738ee5c94dace8ad8c1 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Tue, 29 Sep 2026 18:00:13 -0400 Subject: [PATCH 10/19] Record corrected H002 query timing and cold preparation proof limits --- ...02_query_preparation_diagnosis_20260929.md | 112 ++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 docs/plans/h002_query_preparation_diagnosis_20260929.md diff --git a/docs/plans/h002_query_preparation_diagnosis_20260929.md b/docs/plans/h002_query_preparation_diagnosis_20260929.md new file mode 100644 index 0000000000..7ac0caa666 --- /dev/null +++ b/docs/plans/h002_query_preparation_diagnosis_20260929.md @@ -0,0 +1,112 @@ +# H002 attempt4 query preparation: timing correction and bounded diagnosis + +Owner: Lovelace, original206/PolyStore16 source continuation only. +No edits to installed imagej-cache-progress source, H002 driver/receipts, +scientific pipeline or data. No repeated query, raw pixels/notebooks/biological +outputs, new JVM/runtime/download, install, profiling attach or process restart. + +## Evidence and exact timing meaning + +Named receipt: +`/home/ts/wt/openhcs-h002-uncoached-20260929/output/runtime/h002-live-attempt4/receipts/013-openhcs_query_plate_files.json`. +It records query with limit1, include_previews=false, terminal success. +No inspection of the underlying container was performed by this worker. + +Actual driver source `output/mcp_session_attempt4.py:94` sets +`started = time.monotonic()` once before stdio-client/session initialization. +Lines113-115 calculate `elapsed_seconds = time.monotonic() - started` BEFORE +awaiting session.call_tool. Therefore90.16998135202448 is session age at request +admission, NOT query duration. Original90s claim is withdrawn; no new90s query +regression established and no fix justified by that number. + +Receipt admission at1790718869.0149193, UTC2026-09-29T21:54:29.014919. +Result-bearing receipt mtime1790718875.589610, UTC21:54:35.589610. +Difference **6.574690818786621s**: admission-to-terminal-receipt-write interval, +including dispatch/serialization/receipt IO; not an exact native stage timer. +The driver rewrites the same receipt after the successful await. The parent's +next-receipt96.745 estimate is a separate upper bound including author time. +The previous cache acceptance explicitly measured0.211s differently; neither +figure proves identical fixture/warm/cold cost or a general latency reduction. + +Both driver1614516 and MCP1614529 were alive when checked, started17:52:57/58 +local time. The MCP stderr fd points at this attempt's mcp-stderr.log. +Read-only whitelist of actual MCP environ confirms: +`POLYSTORE_IMAGEJ_CACHE_ROOT=/home/ts/.cache/polystore/imagej` and +`POLYSTORE_IMAGEJ_ALLOW_DOWNLOAD=false`. +No credentials/unrelated environment values printed or retained. + +Startup stderr reports BigStitcher override from the existing digest-addressed +shared Fiji jars, then OMETiffReader initialization. These lines contain **no +timestamps**. Several reader-open lines accumulated across this continuing +session; they cannot be attributed/count-budgeted to query013 alone. File mtime +is the most recent write, not each line's event time. Do not assign a precise +JVM/decoder split from their order or repeated filenames. + +## Source-owned preparation boundary + +QueryPlateFilesCapability -> PlateInspectionService.query_files/_query_files +(:997/:1114) -> _create_handler(:1869) -> existing registered detection/handler +owners -> BioFormatsMetadataHandler.source_dataset(:64) -> registered +SourcePlaneStoreAdapter -> BioFormatsJavaAdapter._declares_path and +_discover_container -> BioFormatsJavaContext.ensure_initialized/open_reader +-> FIJI_IMAGEJ_RUNTIME.initialize -> FijiArchiveDistribution.materialize. +Limit1 bounds returned records after inventory preparation, not container/JVM +initialization. include_previews=false prevents requested previews, not metadata +discovery. No biological behavior is assumed. + +Existing ImageJRuntimePolicy.initialize separates materialization from Java: +with a fresh JVM it first materializes the canonical bundle, then configures +bundled Java with fetch=never and invokes imagej.init. If Java is already started, +it reuses Java subject to declared Java/gateway compatibility checks. +BioFormatsJavaContext additionally imports reader/metadata/format Java classes; +open_reader builds OME metadata and invokes setId before source-plane projection. +Its process-owned context/lifecycle lock retains initialized gateway state. +No alternate Java helper/cache/decoder has been added. + +Installed false policy rejects a missing/invalid bundle before cold filesystem +materialization/network. Actual environment + successful query + stderr pointing +to the shared bundle are consistent with successful warm bundle admission, +followed by cold Java/decoder preparation. They do not time either component. +Bundle provisioning and JVM/decoder preparation are distinct;228 fixed sharing +and inspection responsiveness, not all cold latency. + +Query currently does not declare inspection's progress heartbeat/worker offload, +and its description claims no detection although _query_files can create/detect +the handler. This is a source-backed exposure/observability concern, **not a +newly proved90s regression or measured stage bottleneck**. Keep it within the +existing capability/preparation ownership for a separately justified checkpoint; +do not create another progress runner or speculate about bypassing rich metadata. + +## Reproduce the diagnosis without calling MCP + +Read this single receipt and its actual driver; calculate +`receipt.stat().st_mtime - json.loads(receipt.read_text())["at"]`. +Inspect only the actual stderr fd/log and whitelist cache/download keys from the +existing process. Never replay the request to establish timing. Exact JVM, +reader-open and source-projection costs are **unavailable retrospectively**. +Future stage profiling belongs on ImageJRuntimePolicy/BioFormatsJavaContext and +existing preparation owners, using before/after monotonic events on an authorized +small synthetic run after the live science slot; do not attach/restart H002. + +## Source integration and checks (not live fixes) + +Original parent206 was6bc71ee5c, child16 was9e205fee, remote heads matched. +Normally merged fetched OpenHCS main8b090e and PolyStore main94bf443 into ONLY +their original isolated continuation branches. Merge heads parentb2edc4503, +child50c1e49; conflict resolution retains merged17/228 download-policy and launch +projection changes rather than older cache-only declarations. No rebase/reset/ +force-push or installed-tree mutation. Main's recorded c16b8fc gitlink is retained; +the original16 worktree intentionally differs while its broader gates remain. + +Six scoped source/test files parsed; both diff checks and Ruff on dependency +distribution/cache tests and parent cold-runtime tests pass. No pytest/runtime +rerun needed to answer this diagnostic and none performed. NRA/refactor-audit +coverage is focused source/consumer trace, not a global scan or native proof. +BOUND-2/6: canonical distribution/runtime/context own config/lifetime; do not +read/replace them in callers. IMPL-12/13: keep one materializer and Java lifecycle. +The merge restores current-main behavior, not a performance fix/new owner. + +Remaining original206/16 **141 passed/26 failed** and **after-ROI profile not run** +unchanged. Fresh cold stage profile, source preservation controls and broader +Java/CZI fidelity remain pending. No performance/preservation/live claim from +syntax checks or this receipt. Parent's installed228 acceptance remains separate. From 72a9607cb2e562e0ef4c73c2942dcc4fdd24f75f Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Wed, 30 Sep 2026 21:14:18 -0400 Subject: [PATCH 11/19] Integrate paired input preparation on current main with preserved evidence --- .../input_preparation_parent_20261001.rst | 69 +++++++++++++++++++ external/PolyStore | 2 +- 2 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 docs/validation/input_preparation_parent_20261001.rst diff --git a/docs/validation/input_preparation_parent_20261001.rst b/docs/validation/input_preparation_parent_20261001.rst new file mode 100644 index 0000000000..4a8bad1475 --- /dev/null +++ b/docs/validation/input_preparation_parent_20261001.rst @@ -0,0 +1,69 @@ +Paired input-preparation parent checkpoint +========================================= + +Parent is the integration owner of existing OpenHCS206 / PolyStore16. Original +feature worktrees are unchanged. Published parente0dd14ed3 normally integrates +current main74eac059 as e8d6d32f857254cb84087ad712d1226697afab97. Child50c1e49 +normally integrates the application's recorded PolyStoredc341739 as +cb12a2acd91061d60cb6ec65981a9f5c01e18963. Final child4939979978439a6f38145788df9aac3bce65bc70 +adds only its canonical receipt to that tested production source. This checkpoint +adopts that final child gitlink; the other seven dependencies remain unchanged. + +Actual source validation uses Python3.12 and the existing shared interpreter's +third-party dependencies, not its installed OpenHCS/PolyStore sources. Own exact +external worktrees are populated from existing local Git objects; no package, +interpreter, Fiji bundle or JVM download occurs. Source-built native extensions +are produced by the standard setup.py build_ext --inplace in this own tree. +No shared or frozen installation is changed. + +Forty-eight dependency tests pass in0.82s: existing ROI, new RegionProperties +geometry/work-count and actual Java-context lifetime owners with controlled +external responses. The dependency process verifies its source location and +generic polystore_metadata.json configuration before collection. + +One hundred sixty-three application tests pass in19.12s: source-binding +workspace, source-plane stores, BioFormats handler/adapter/storage/SPW/validation, +physical preparation/admission, fragmented materialization and both synthetic +generator families. The fresh process imports the real openhcs entrypoint before +pytest, verifies both source paths and openhcs_metadata.json configuration, +and uses no plugin autoload or global conftests. Two unrelated asyncio config +warnings arise because those optional plugins are disabled. No assertion, +metadata filename or application/dependency API is patched for these checks. + +Historical141-pass/26-failure output remains historical. An intentional fresh +dependency-first control reproduces the current failure: the normalized-workspace +test writes the application metadata but VirtualWorkspaceBackend._load_mapping +looks for the earlier-decoded generic filename. Actual result1 failed in3.34s; +this is not a green test or a repaired embedding contract. Issue327 tracks the +actual reproducer and parent owner. The first control's +missing pytest scratch parent yields1 setup error in3.17s and is retained as a +separate harness mistake, not evidence of the product defect. Parent owns the +namespace-boundary follow-up; no singleton mutation or second filename reader is +introduced here. These separately bootstrapped passes establish current source +preservation, not global elimination of the import-order defect. + +Existing bounded128x128 ROI profile now runs: original parent labels7/42/188, +fragment counts1/1/900,902 native ZIP members, one find_objects scan. Earlier +same-fixture profile recorded two scans. Current cold extraction0.284355s, +warm profiled extraction0.019384s, separate member conversion0.060257s, +archive conversion/write0.133487s. A single-fixture work-count reduction is +observed; no general speedup or biological quality claim is made. Polygon rings +still do not declare hole subtraction; archive reopen is not raster/viewer +equivalence. Exact label TIFF and full-mask controls remain distinct. + +BOUND-2 / IMPL-13: existing RegionProperties and BioFormatsJavaContext own +crop/reader lifetime; existing NamedSourceBinding/SourceSelector own physical +admission. Consumers reuse those nominal operations. No decoder/filename roster, +alternate extractor, mirrored provenance/configuration or type/name switch is +added by this integration. Earlier focused ownership guards remain covered by +the current211 source checks. Full NRA/R1 and the owner's complete ZIP scope are +not certified. Installed stdio/native preparation, actual Java/CZI and new +generation/inspect/sample acceptance remain separate next-slot requirements. + +Raw source receipts, XMLs, profile and original negative-control errors remain +at /home/ts/wt/openhcs-issue-batch-20260929/input-parent-20261001. All source shards +have an unchanged60-second shell bound and one numerical thread. Disposable build, +pytest and profile output is tracked under the parent's named input-parent-20261001 +scratch directory. Active independent H003f's source, skills, runtime handles, +scientific inputs/settings and protected user viewers are untouched. Neither +issue132 nor172 is closed by this source-only checkpoint. diff --git a/external/PolyStore b/external/PolyStore index dc3417392c..4939979978 160000 --- a/external/PolyStore +++ b/external/PolyStore @@ -1 +1 @@ -Subproject commit dc3417392cef19083a907c8c9b88a15466f81df4 +Subproject commit 4939979978439a6f38145788df9aac3bce65bc70 From a3179b214b888308133e55682e964f77c2781879 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Wed, 30 Sep 2026 21:19:31 -0400 Subject: [PATCH 12/19] Keep failed original structural guards explicit in the integration receipt --- .../input_preparation_parent_20261001.rst | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/docs/validation/input_preparation_parent_20261001.rst b/docs/validation/input_preparation_parent_20261001.rst index 4a8bad1475..7266a6373b 100644 --- a/docs/validation/input_preparation_parent_20261001.rst +++ b/docs/validation/input_preparation_parent_20261001.rst @@ -9,6 +9,9 @@ cb12a2acd91061d60cb6ec65981a9f5c01e18963. Final child4939979978439a6f38145788df9 adds only its canonical receipt to that tested production source. This checkpoint adopts that final child gitlink; the other seven dependencies remain unchanged. +Tested paired production candidate is72a9607cb2e562e0ef4c73c2942dcc4fdd24f75f; +subsequent receipt updates do not change its tested production source. + Actual source validation uses Python3.12 and the existing shared interpreter's third-party dependencies, not its installed OpenHCS/PolyStore sources. Own exact external worktrees are populated from existing local Git objects; no package, @@ -67,3 +70,19 @@ pytest and profile output is tracked under the parent's named input-parent-20261 scratch directory. Active independent H003f's source, skills, runtime handles, scientific inputs/settings and protected user viewers are untouched. Neither issue132 nor172 is closed by this source-only checkpoint. + +Unchanged original structural guard +---------------------------------- + +Actual packaged ratchet implementation SHA256e323c94d49c2b72d9524a5169f123e64b4a6e46a41035ca9fb4497e49b6ca562 +matches the original CI-pinned agent-comms tool. Against main74eac059 and the +exact paired candidate72a9607, scripts passes; OpenHCS fails with +GodClassExcess:MicroscopeHandler+10 and benchmark fails with StringSubscript+2. +Full JSONs remain in the parent ledger. These are failures, not waivers or a +global architectural pass. Parent owns their correction through existing PR206: +factor the actual detection capability and use proper parent-label ownership in +the developer profile, without moving code outside the guard to hide it, replacing +string literals with constants merely to evade measurement, or compressing +documentation to game class size. Existing feature remains a visible draft. +Installed acceptance is pending the independent blind trial's terminal release; +the incomplete structural/full-NRA review is separate from that live boundary. From 1998f8ed6b3387ea05629fdc5c7f0712a9d0fa41 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Wed, 30 Sep 2026 21:48:15 -0400 Subject: [PATCH 13/19] Compose metadata detection capability and reuse typed ROI metadata in profile --- benchmark/fragmented_roi_profile.py | 12 ++- .../input_preparation_quality_20261001.rst | 70 +++++++++++++++++ openhcs/microscopes/microscope_base.py | 76 +++++++++++-------- .../unit/test_microscope_handler_identity.py | 49 ++++++++++++ 4 files changed, 172 insertions(+), 35 deletions(-) create mode 100644 docs/validation/input_preparation_quality_20261001.rst diff --git a/benchmark/fragmented_roi_profile.py b/benchmark/fragmented_roi_profile.py index d800195c90..3e50d56194 100644 --- a/benchmark/fragmented_roi_profile.py +++ b/benchmark/fragmented_roi_profile.py @@ -14,7 +14,7 @@ import numpy as np from polystore.disk import DiskStorageBackend from polystore.roi import extract_rois_from_labeled_mask -from polystore.roi_converters import FijiROIConverter +from polystore.roi_converters import FijiROIConverter, NapariShapeMetadata def main(): @@ -52,9 +52,15 @@ def main(): json.dumps( { "shape": list(labels.shape), - "parent_labels": [roi.metadata["label"] for roi in rois], + "parent_labels": [ + NapariShapeMetadata.from_metadata(roi.metadata).label + for roi in rois + ], "fragments_by_parent": dict( - Counter(member.metadata["label"] for member in members) + Counter( + NapariShapeMetadata.from_metadata(member.metadata).label + for member in members + ) ), "zip_roi_members": member_count, "cold_extraction_seconds": cold_extraction_seconds, diff --git a/docs/validation/input_preparation_quality_20261001.rst b/docs/validation/input_preparation_quality_20261001.rst new file mode 100644 index 0000000000..0ce3a14d81 --- /dev/null +++ b/docs/validation/input_preparation_quality_20261001.rst @@ -0,0 +1,70 @@ +Input-preparation ownership correction +===================================== + +Parent integration owner; existing PR206, normally integrated head a3179b214, +paired PolyStore16 head493997997 unchanged. Other open PR326's runtime files +and the installed independent H001c trial are not edited. Issue327 remains +separately assigned to the source-only namespace owner, Averroes; parent will +integrate its dependency/application result and verify the installed path. + +Ownership decisions +------------------- + +MicroscopeHandler mixes viewer/workspace operations with metadata discovery. +Its PR-added detection contract grew the original GodClassExcess by10. +MetadataMicroscopeDetector now owns the existing metadata association and +unchanged shared detection operation. MicroscopeHandler composes that capability +through MI; its secondary registry continues deriving declarations through MRO. +ImageXpress, OperaPhenix and OpenHCS use their original metadata leaves. +BioFormats and declared-source handlers keep their own detection implementations. +No copied method, registry roster, decoder fallback or central dispatch is added. +IMPL-8/AGENT-4 are ownership leads, not claims of a new dispatcher defect. + +This default operation was already shared. A new metadata-backed case needed +one declaration before and still needs one, not a falsely claimed edit reduction. +The new case can now exercise metadata detection without acquiring viewer or +workspace responsibilities. The family behavior and constructor/discovery +contract are the acceptance, not shorter class documentation or a changed guard. + +The developer ROI profile read label keys directly despite the existing +NapariShapeMetadata.from_metadata projection modeling these required label, +area and centroid fields. BOUND-2: the profile now uses that richer existing +owner for actual extracted ROI and converted-member metadata. It does not +substitute expected input labels, reparse archive names, introduce a mirror, +rename the existing public projection or alter arbitrary ROI metadata contracts. +The profile's extracted records supply its required fields. No new raw record +shape is spread into a consumer. No production conversion is added to hide cost. + +Executed source and profile evidence +------------------------------------ + +Source-qualified Python3.12:125 tests pass in20.22s. Both application and paired +dependency imports are asserted to reside in this owned checkout before collection. +Tests cover microscope identity/factory, metadata owner positive/missing cases, +one independent declaration, BioFormats handler/preparation selection, source +binding workspace/admission and plane stores. Two asyncio configuration warnings +come from disabled optional plugins. All assertions and fixture inputs remain. +The first invocation named a nonexistent test file and exited4 without tests; +its original log/XML remain separately, not relabeled as a product failure. + +The existing128x128 developer entrypoint actually runs after the decoder change: +parent labels7/42/188, fragments1/1/900,902 ZIP ROI members, one find_objects scan. +Cold0.296602s, profiled warm0.021064s, member conversion0.062092s, archive +conversion/write0.134282s. This confirms observed identities/work count on one +bounded synthetic fixture, not general throughput or raster/biological parity. + +Original logs/XML/profile are retained under +/home/ts/wt/openhcs-issue-batch-20260929/input-parent-20261001/quality-*. +One numeric thread,60-second shell bounds, no JVM, Fiji/package/environment +download or global install. Owned scratch input-parent-quality-20261001 is +bounded and cleaned after terminal source checks and evidence archive. + +Remaining gates +--------------- + +The unchanged original packaged structural ratchet must be rerun on the committed +candidate before publication acceptance. Full-context NRA/R1/ZIP completion is +not certified by these focused checks or the method's unchanged AST. +Installed native/MCP input preparation, actual Java/CZI and persisted sample +acceptance remain required. The current scientific trial owns the isolated +runtime slot; no foreign or frozen process is changed to obtain that slot. diff --git a/openhcs/microscopes/microscope_base.py b/openhcs/microscopes/microscope_base.py index fc49f528d0..6c09d1181b 100644 --- a/openhcs/microscopes/microscope_base.py +++ b/openhcs/microscopes/microscope_base.py @@ -137,7 +137,50 @@ def source_selection_guidance(cls) -> str: ) -class MicroscopeHandler(ViewerMicroscopeHandlerABC, ABC, metaclass=AutoRegisterMeta): +class MetadataMicroscopeDetector: + """Detect sources through their declared microscope metadata owner. + + Image-container detectors override this operation; ordinary metadata-backed + handlers share it independently of viewer transport and workspace operations. + """ + + _metadata_handler_class: Optional[Type[MetadataHandler]] = None + + @classmethod + def detect( + cls, + plate_folder: Path, + filemanager: FileManager, + source_bindings_config: Optional["SourceBindingsConfig"] = None, + ) -> bool: + """Detect this handler; store-backed declarations can narrow discovery. + + Ordinary metadata handlers detect one plate declaration, without opening + its image containers. Store-backed handlers own selected discovery in + their implementation of this same method. + """ + from polystore.exceptions import MetadataNotFoundError + + metadata_handler_class = cls._metadata_handler_class + if metadata_handler_class is None: + raise RuntimeError( + f"{cls.__name__} missing _metadata_handler_class for detection" + ) + + metadata_handler = metadata_handler_class(filemanager) + try: + metadata_handler.find_metadata_file(plate_folder) + except (MetadataNotFoundError, FileNotFoundError, TypeError): + return False + return True + + +class MicroscopeHandler( + MetadataMicroscopeDetector, + ViewerMicroscopeHandlerABC, + ABC, + metaclass=AutoRegisterMeta, +): """ Composed class for handling microscope-specific functionality. @@ -160,9 +203,6 @@ class MicroscopeHandler(ViewerMicroscopeHandlerABC, ABC, metaclass=AutoRegisterM DEFAULT_MICROSCOPE = "auto" _handlers_cache = None - # Optional class attribute for explicit metadata handler registration - _metadata_handler_class: Optional[Type[MetadataHandler]] = None - def __init__( self, parser: Optional[FilenameParser], metadata_handler: MetadataHandler ): @@ -236,34 +276,6 @@ def source_selection_guidance(cls) -> str: "explicitly and expect metadata-derived fields to remain unavailable." ) - @classmethod - def detect( - cls, - plate_folder: Path, - filemanager: FileManager, - source_bindings_config: Optional["SourceBindingsConfig"] = None, - ) -> bool: - """Detect this handler; store-backed declarations can narrow discovery. - - Ordinary metadata handlers detect one plate declaration, without opening - its image containers. Store-backed handlers own selected discovery in - their implementation of this same method. - """ - from polystore.exceptions import MetadataNotFoundError - - metadata_handler_class = cls._metadata_handler_class - if metadata_handler_class is None: - raise RuntimeError( - f"{cls.__name__} missing _metadata_handler_class for detection" - ) - - metadata_handler = metadata_handler_class(filemanager) - try: - metadata_handler.find_metadata_file(plate_folder) - except (MetadataNotFoundError, FileNotFoundError, TypeError): - return False - return True - @property @abstractmethod def root_dir(self) -> str: diff --git a/tests/unit/test_microscope_handler_identity.py b/tests/unit/test_microscope_handler_identity.py index 5aa119b3d8..8c3203472d 100644 --- a/tests/unit/test_microscope_handler_identity.py +++ b/tests/unit/test_microscope_handler_identity.py @@ -20,9 +20,12 @@ from openhcs.core.steps.function_step import FunctionStep from openhcs.microscopes import create_microscope_handler, get_all_handler_types from openhcs.microscopes.microscope_base import ( + MetadataMicroscopeDetector, MicroscopeHandler, MicroscopeSourceSelectionRole, ) +from openhcs.microscopes.imagexpress import ImageXpressHandler +from openhcs.microscopes.openhcs import OpenHCSMicroscopeHandler from openhcs.microscopes.opera_phenix import OperaPhenixHandler from openhcs.processing.backends.processors.numpy_processor import percentile_normalize from openhcs.demo.synthetic_data import ( @@ -69,6 +72,52 @@ def test_typed_microscope_values_are_exact_registered_handler_keys() -> None: } == configured_types +@pytest.mark.parametrize( + "handler_type", + (ImageXpressHandler, OperaPhenixHandler, OpenHCSMicroscopeHandler), +) +@pytest.mark.parametrize("missing", (False, True)) +def test_metadata_detection_uses_each_declared_owner( + monkeypatch, tmp_path: Path, handler_type, missing: bool, +) -> None: + from polystore.exceptions import MetadataNotFoundError + + observed = [] + filemanager = bioformats_filemanager() + metadata_type = handler_type._metadata_handler_class + + def find_metadata_file(metadata, plate_folder): + observed.append((type(metadata), plate_folder)) + if missing: + raise MetadataNotFoundError("declared metadata unavailable") + return plate_folder / "declared-metadata" + + monkeypatch.setattr(metadata_type, "find_metadata_file", find_metadata_file) + + assert handler_type.detect(tmp_path, filemanager) is not missing + assert observed == [(metadata_type, tmp_path)] + + +def test_metadata_detection_new_case_needs_only_its_declaration( + monkeypatch, tmp_path: Path, +) -> None: + metadata_type = ImageXpressHandler._metadata_handler_class + + class NewMetadataDetector(MetadataMicroscopeDetector): + _metadata_handler_class = metadata_type + + observed = [] + + def find_metadata_file(metadata, plate_folder): + observed.append((type(metadata), plate_folder)) + return plate_folder / "new-case-metadata" + + monkeypatch.setattr(metadata_type, "find_metadata_file", find_metadata_file) + + assert NewMetadataDetector.detect(tmp_path, bioformats_filemanager()) is True + assert observed == [(metadata_type, tmp_path)] + + def test_config_schema_patch_and_source_share_opera_handler_identity() -> None: service = ConfigService() schema = service.describe_schema("pipeline") From 5703899adb036fe4651aef774db4dfc583b72223 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Wed, 30 Sep 2026 21:49:59 -0400 Subject: [PATCH 14/19] Record passing original input-preparation structural guards --- .../input_preparation_quality_20261001.rst | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/docs/validation/input_preparation_quality_20261001.rst b/docs/validation/input_preparation_quality_20261001.rst index 0ce3a14d81..9a7577c747 100644 --- a/docs/validation/input_preparation_quality_20261001.rst +++ b/docs/validation/input_preparation_quality_20261001.rst @@ -59,12 +59,21 @@ One numeric thread,60-second shell bounds, no JVM, Fiji/package/environment download or global install. Owned scratch input-parent-quality-20261001 is bounded and cleaned after terminal source checks and evidence archive. -Remaining gates ---------------- +Original guards and remaining gates +---------------------------------- -The unchanged original packaged structural ratchet must be rerun on the committed -candidate before publication acceptance. Full-context NRA/R1/ZIP completion is -not certified by these focused checks or the method's unchanged AST. +The unchanged original packaged structural ratchet actually passes against +main74eac059 for committed production candidate1998f8ed6: both openhcs and +benchmark have zero positive deltas. MicroscopeHandler excess is now16 below +main, rather than10 above; the profile's added StringSubscript delta is gone. +Other existing improvements retain SourceBindingWorkspaceProjector excess-10, +SyntheticMicroscopyGenerator excess-30 and workspace ForeignAbsenceProbe-1. +No exception, scope move, string-constant substitution or guard change is used. +Original failed and current successful JSONs remain separate. Scripts source +is unchanged from the earlier passing revision. An AST comparison also proves +the complete detect signature/decorator/body is unchanged from a3179b214; +that syntax comparison does not itself prove MRO/metaclass runtime semantics. +Full-context NRA/R1/ZIP completion is not certified by these focused checks. Installed native/MCP input preparation, actual Java/CZI and persisted sample acceptance remain required. The current scientific trial owns the isolated runtime slot; no foreign or frozen process is changed to obtain that slot. From a1a1689f3c1931bd864f609827db694bdfc6dc0e Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Wed, 30 Sep 2026 22:09:10 -0400 Subject: [PATCH 15/19] Declare the application workspace namespace through PolyStore configuration ownership --- docs/validation/metadata_namespace_327.rst | 144 ++++++++++++++++++ .../metadata_namespace_327_resources.rst | 14 ++ ...metadata_namespace_327_source_evidence.tgz | Bin 0 -> 23388 bytes docs/validation/run_metadata_namespace_327.py | 53 +++++++ external/PolyStore | 2 +- openhcs/__init__.py | 3 - openhcs/core/virtual_workspace_metadata.py | 25 +-- tests/unit/metadata_namespace_327_worker.py | 45 ++++++ tests/unit/test_metadata_namespace_327.py | 103 +++++++++++++ 9 files changed, 368 insertions(+), 21 deletions(-) create mode 100644 docs/validation/metadata_namespace_327.rst create mode 100644 docs/validation/metadata_namespace_327_resources.rst create mode 100644 docs/validation/metadata_namespace_327_source_evidence.tgz create mode 100644 docs/validation/run_metadata_namespace_327.py create mode 100644 tests/unit/metadata_namespace_327_worker.py create mode 100644 tests/unit/test_metadata_namespace_327.py diff --git a/docs/validation/metadata_namespace_327.rst b/docs/validation/metadata_namespace_327.rst new file mode 100644 index 0000000000..add4c06193 --- /dev/null +++ b/docs/validation/metadata_namespace_327.rst @@ -0,0 +1,144 @@ +Issue327 source-only namespace checkpoint +======================================== + +Parent is overall integration owner. This source implementation owns only the +metadata namespace/configuration boundary. Audited OpenHCS PR206 is +a3179b214b888308133e55682e964f77c2781879; PolyStore PR16 is +4939979978439a6f38145788df9aac3bce65bc70. Dependency draft PR20 extends PR16; +this application draft stacks on PR206 and pins the reviewed child source. +No parent/foreign worktree or installation was edited. + +Diagnosis, witnesses, destination owners +--------------------------------------- + +Original openhcs/__init__.py:20 projected POLYSTORE_METADATA_FILENAME through +an ambient environment default. Existing OpenHCSMetadataConfig at +core/virtual_workspace_metadata.py:39-59 owned the actual application filename, +metadata path and managed lock paths. Existing PolyStore MetadataConfig at +metadata_writer.py:54 owned a separate frozen generic config. Its workspace +reader at virtual_workspace.py:242 used the earlier-decoded generic singleton, +not the application's already richer owner. The sole application constructor at +microscopes/microscope_base.py:469 did not supply that owner. + +BOUND-2: bind the existing richer application's typed declaration instead of +reading an unrelated ambient dependency namespace. IMPL-13: shared metadata +path/lock implementation now lives once on MetadataConfig; the application +inherits it. TIME-7: delete bootstrap projection of another consumer's defaults. +No new namespace enum, string/type dispatch, mirrored config/store, singleton +mutation, filename copy, alternate reader, compatibility alias or test switch. + +Library workspace construction, persisted reopen and native PicklableBackend +connection parameters carry the exact frozen MetadataConfig value. FileManager +retains its existing declaration-derived backend discovery and worker registry +reconstruction. Native dictionary decoding uses constructor keyword binding; +the get_connection_params declaration is the sole producer of its two fields. +Generic default and helpers share their existing immutable generic owner. +Application config declares only its filename/default factory; common keys, +timeout and path/lock behavior are inherited, not copied. + +Durable metadata paths and structured SourcePixelRef JSON remain unchanged. +Runtime handoff records are produced with the current typed config. No data +conversion, namespace negotiation, environment workaround or legacy reader. + +New-case test and source checks +------------------------------ + +A third namespace now needs one MetadataConfig declaration/value and constructor +binding, not coordination of bootstrap timing and separate consumer defaults. +The library new-case test changes filename and subdirectory-key declaration to +collections, then loads exact pixels without changing any consumer/registry. +Site guards reject a workspace global path lookup, require the retained owner +and native keyword binding, prove inherited method identity, and forbid the +application bootstrap's dependency namespace projection. No exceptions. + +Python3.12 source tests use the existing third-party packages. Exact application +and dependency import locations are asserted; seven other externals are own +persistent worktrees at the application's recorded Git objects. The child used +for final product validation isee1e5438d64cb7d37e2641c2d9b3ee8870663adf; +later child receipt commits do not change tested production source. +Own native extensions were built normally with setup.py build_ext --inplace +(3.80s, peak203.67 MiB), not copied from installed packages. + +* Dependency: eleven checks pass in4.42s, peak294.78 MiB. Includes three + unchanged FileManager/persisted-workspace/pickle controls. +* Dependency-first application declaration journey: five pass in4.56s, + peak260.05 MiB. +* Application-first declaration journey: five pass in4.56s, peak260.05 MiB. +* Independent custom application/dependency environment declarations: five pass + in4.46s, peak260.23 MiB. + +Each application journey writes real persisted metadata through the application +writer, constructs the workspace with its declaration, reopens through the real +FileManager, and launches a fresh native Python recipient of its pickle. Exact +source plane pixels, source axes, registry rebinding and bounded sample pixels / +source-shape provenance are checked. The declaration journey is not a substitute +for the still-unbound microscope registration path below. + +Original and unresolved source failures +-------------------------------------- + +The exact original dependency-first negative control is reproduced unchanged: +one failed in4.35s, peak275.77 MiB, FileNotFoundError for polystore_metadata.json +after the application wrote openhcs_metadata.json. The first invocation's +missing-source-extension collection error is retained separately as a setup +failure. After the source contract change, the unchanged microscope test still +fails (one in3.26s, peak220.86 MiB) because its forbidden parent-owned constructor +does not yet bind the application owner. This is a real remaining source +integration requirement, not merely an installed/live gate. + +Parent-only hunk, requested publicly at +https://github.com/OpenHCSDev/openhcs/issues/327#issuecomment-5923060449:: + + from openhcs.core.virtual_workspace_metadata import METADATA_CONFIG + + backend = VirtualWorkspaceBackend( + plate_root=Path(plate_path), metadata_config=METADATA_CONFIG + ) + +Parent must apply this to its own _register_virtual_workspace_backend on PR206 +and integrate the paired stack normally. I did not edit microscope_base.py, +test_microscope_handler_identity.py, benchmark/fragmented_roi_profile.py, +PR326 runtime/preparation files or any parent external directory. No original +test assertion is modified. The first authored application worker fixture +pre-imported arraybridge's installed source before activating pinned externals; +its two failures are preserved. The worker now faithfully imports dependency +metadata before the real application entrypoint, then other source owners. + +Audit coverage and proof limits +------------------------------- + +Archive entrypoint, pattern README, complete boundaries/implementation/over-time +references and both skills were read. Lightweight full-package AST census at +pinned heads completes without parse omissions: dependency12,209 code lines +in1.06s and application355,153 code lines in22.03s. These are syntax census, +not completed NRA ownership/native-equivalence proofs. + +The unchanged original structural ratchet passes for final child source against +PR16: StringSubscript-2, other deltas zero. The first child draft's+2 failure is +retained, not waived. Actual tool SHA256 is +e323c94d49c2b72d9524a5169f123e64b4a6e46a41035ca9fb4497e49b6ca562. +Pinned tool package cannot import on3.12 (NameError: InputDocument); the existing +system3.14.7, matching original CI's3.14, executes its unchanged pinned local +Git source. This does not alter product3.12 validation or install/download +anything. Tool worktree: /home/ts/wt/metadata-namespace-327-audit-tool. + +Semantic edits were hand-authored; no NRA transaction or completed R1/raw-record +certificate is claimed. Full NRA/R1 remains uncompleted, not waived. All raw +logs/XML/command bounds, including failures, are retained in +metadata_namespace_327_source_evidence.tgz. Initial census JSON filenames were +overwritten by the command-summary harness; the complete census stdout remains +in logs. Harness now uses distinct .command.json outputs. No science data is +part of this archive. + +Acceptance boundary +------------------- + +Parent must finish the forbidden registration hunk and rerun the unchanged +microscope workspace journey in both import orders. Then parent owns paired +merge/install and the actual affected installed native/MCP entrypoint, persisted +workspace reopen and exact pixel/provenance acceptance. No merge, activation, +installed-readiness or issue closure is claimed. No UI/MCP/scientific dataset, +science-runtime lock, Fiji/environment/interpreter download or installed-package +mutation occurred. Resource ownership/bounds are recorded separately in +metadata_namespace_327_resources.rst; terminal disposable scratch is archived +and cleaned by this owner. diff --git a/docs/validation/metadata_namespace_327_resources.rst b/docs/validation/metadata_namespace_327_resources.rst new file mode 100644 index 0000000000..e82aa65fb5 --- /dev/null +++ b/docs/validation/metadata_namespace_327_resources.rst @@ -0,0 +1,14 @@ +Issue327 source resource ledger +============================== + +Owner: SOURCE-ONLY issue327 implementation agent. Parent owns integration. +Source worktrees: /home/ts/wt/openhcs-metadata-namespace-327-20261001 and +/home/ts/wt/polystore-metadata-namespace-327-20261001. +Disposable scratch: /home/ts/.cache/agent-scratch/metadata-namespace-327. +Purpose: tiny synthetic fixtures, pytest XML/logs and bounded source evidence. +Limits: one CPU, 512 MiB combined process RSS, 256 MiB scratch, 60s per shard. +Initial resource guard: warning, 8.5 GiB home free, 16.5 GiB RAM available, +8.9 GiB historical swap used. The owner's 20 GiB disk threshold is warning-only; +the bounded scratch maximum is less than available disk. No science/runtime lock, +installed package, GUI/MCP, provider, interpreter or environment changes. +Archive terminal command output in this worktree before removing owned scratch. diff --git a/docs/validation/metadata_namespace_327_source_evidence.tgz b/docs/validation/metadata_namespace_327_source_evidence.tgz new file mode 100644 index 0000000000000000000000000000000000000000..56db32d8cfd20ebe3f36e14a278d24f039e0de28 GIT binary patch literal 23388 zcmZU(b8uwe7d;wHtch(-Y)x!ilVoC>6Wg5Fwr$(CZQI>1pZR@%_3G8@Kkh!Y@7`;z zb8lClI_orH1T@IM5BPs~{a!fknVDmI^*7LOJ`D<@jKHX$MZBU}s9bW#>I1fP71*|g+e9mp&{6zDVk|b9`ewjX zZ!ZX7``QI@0?<-l{l_EV)62tCFHOzVDAwE72%&-XF)+o&^YQ$2RuWu}f7dx+V;0#$ z_!tv0^N!}v5$8v+_=IIY5}2WsxLxXElM0S_{1(oC%I03TJq3cK?}TwH6MWiXPy5 z+J?{T347KBphlG^Xk$SwY_iCquR^axrf&u=xH1Vr-6Fv{@l~B}uSsAq5)Z7=ejQ$O zoSD~Z*5oJJf+VN2OFAr;2$aA~IHM7S{AC zE^<&69CMeofJ7X8{q%O;L6!jD75PLLZk*C`r^PAn8+;8~w*|Kny6FPG+H?#oA@)H& zrS?dKCtX6P_xs!It*eLkIRO9A?Y)lz%?2_*h_(I7jA~`B0PTpC0$fOxCa2962jG5u zx@6KHyX1(-u_|Yc%SxmTG47S`K0rA1JK92i&R$-`PMSMG?JJkO$I3}B_F#if<2s=T z<_x0pbq(m2UUw-zE6s5uh=BUV{iH<}4%O2(IoI_?JCI1lOPU0bmsa4mH zUJ4>iaYe`-Wo zz~4z={;>#N^RWA#=4D0Jbh?W8SUY|S*{#a(V&))Rk;TG70Z~y-HV2A#tp1^x+DP*( zR$^fN#<`%1oYj@Al+e=KY)xh|d>iO7v{ul>O=g;lc z>*@vITMY-6%esXw;l&@Q?Q(!^`6Ym(8t7aK^ilZazXmRIoF^0D9s?Uhf!(CXK!U@+ z_deSvWQiHT_Ee-lW30LMnwY9m-l;Z%SsBl?)EP`9? zY+oaBl?m{;2D0x*S!~0Hhbm)&ppx-ycpexGaGNK(qqW~O5KKRA4QzU8za z;W7vmcN_1*u{&NWS5)TSi4~~!tf~*aZPAHbef^w;IImp{1*Cy-ANFk0R6AgZ7Fj|^Z2kd-0-*%Ds438Ll5`6u<^3O*{3bX z6QXjeAk8G~n03D1jZRWxw0eYm4xRSyx*K1wTym-JPm(7#dIKisA0_7P07Z@%Hu7yF!!^Ugd(piv=OE<8N!$^EbiA{A8YqP zc=&hvcslp=?+~Q^A~3AUF*_7L%t9X@mNv2~mV~-o+0GQD2faOt?(j_37(IU?Er2)y z*V`TGY7$CzV(T8Cm zkO_&RUCXpbKFlar9GkS9E?Ra_V@Wke59HFjCA%eY%0thN6{V#UU3hj{P3+q9Q~CU+Wq-^;U4?8VU5fm}78_=G zd%)^_;~AXDmmM>B&>_1f%DfYNK(E4^tvPdTA11dUWkKrZfXC~a^3B_^ipkybP(8U3 zx~E;h&)}T}Go-6m))P5`KjsQx&duWBCx0|JO}R(l;a4Q5#Dj)um;ZVz69Bi1`SL>R z)-6KZ3jVcRF+{a5J}Sdfz2x9 z$m%GgTVXMby|zDff}8c`hx?W^Scch{R{7C@cwL;WrM>Kv@4eO({rbtDYTIk^fo8P- zW?PKUb{YKuB-sCZ-o1T7Qm73~Jnv@n1l9`W+kT*l#7Fe;55$fG)HASu z)BrhZft^+V{pNUnes-^|J#KH+a47J&H&-`zZ+~{XH$O*R11~SeKfC!)0NbkDJ_{m% z+IlX)`AH|#w$FpJ&K1xtVZZj3ABBvsH{d*M2T1AYW%o+kr6=f)lgp<(qZM=>6IK7G z8hE{TxA|Ind-QO3a&U4t;(B}bc)Fg?QA~*Ix%z!W1Ea*il~UqV%8r@!RVWu}Mv8kV zi!RmYm$qvRaa?OHC(ZEnR+sA|^QcXDIiIVj&Is>7zd{FkL=qt`jENkPqJJWzC!Bz` zOn|K5-^<%)es-w}BTuWJZp;kq%5;Zh$G^LxjD~f5+i9i; za9+L<{XIR!M|c*QU}r0Bf8I6R-)$Rq&_Rsd8Xi(EIXwan*v=bc(HW$@$dR>%*&pjW zA!h#m`H-iZjNldsMS2>%Hh?Lw`Cz%m9+=gu4$bvzz(f_MZ zkMXwl_H;D|j5HEM-XbinL!l#ivK*m#`Eflc38`A?Pw$a%N4}CSke)2!V6~G>AXR z1D&lfF#N;*L?Y_$^6kJm!Y(a$wn6uT2&Y(o!)$RRmR(o{I!fY zl^5}u8U!lRc06mwkn{xMe(=q?dYt@Voga=8{atAs;~DSaif6f%A)VlvTcA8q)~16U z*oKL9?0H_7?ZIloj^)K}J5ll&*Ll<3Tgk8e5^{H?U=>**>J8aUTawh zOVp>WQDpdLP%heta-TUuN&G`OM&pqPUsxRKx&}f6**Ol1?axsPy;Fw0weR6u{YpTT z0+7v%kV8+<6YZcHmPs4ND^?Lv>5cTq3@@Px#B}yPziVbsJppVhaTu+tr5zS z+PaL{QXiw8*xORDZ=aDoLP{T^Fd(dX68H^OIPgF$dk`KjnlK%5Im{F_Y9Mck$PXmu z-W@bpo*}{C{j_E_727{vlm63R$Q;StO#pi1pdH%3B9nL6oS{pr{;!}1$Pj+z@7-_5!vf*3mUKNUg+@R}= z3{#-P#QXCCB?Fbc15%l>hHW0gnZ2{BAGhhlM91)TT3L?vjr7ekoJHh>xI@^UE2_@^ z&+H^5vd_N^`EGJ^gB=~In&I&rY(S#>dj0&YynG`X7uFtcp3x2Z^XaN&A{QjW*QLO% z14Mx(L)aQrmvaLoE@+`%Q?YEjqu0T?S06ju;*HTZ>HJ9M$&ex|Nrxs`_XwLsk;7eEjM&sxnB9+NlMR z-q`A4xw2q1*xAuF#Jg;_FhiY|^%LP7Kxa*Z&DXKwMeJ0>f z?jx$e&-)P{8STZ+WNWr?>R^03FJ&IVPReJ7Wk9aZJH`r62-;LXk^hvCiQAV_wrn$zP~Zh+W1ZN}ORxJtYUVJx6c zdowU_FOc}gKo(i75ME(ybT?%mUIcD?1_*C0oZlO+FGT5P3OJ8bD#cBbay#Q(fEzQ$ zJZmFKJo{4vLLWh%dfD$;is*E+h-mGc1bMKx3XTdR^|hUp`hT{SH|=@@w>u zM4bVf+Fl-qlGJrd9VB(9U?<+S)ajqDLXwXgVSFlfXU<+v(fgF>J98iNFf5Au^Kw|a6lQtlDucKH|KHot74*3&{4f~}EsHJUD=B@iZ>V3D!=E_2{4 zJMnMa)-?=mR}mRq;@@ILfh%H3xrPf!5xjZc3j^t;;E@bLNxrZzF8rhe*P2z(>2Y5h zAdz#xcQVvmS3?&2T3mmXWEf;1Q3f9#HnKPM|AR7-crVZbRm0C}Ka2MZ9@Xsse$T(tPv1#~G=j1Kf z|AzZ1#x}%8HP$Q|t4Y%|XhGW`lLbbp%cTcwl$Ua&gb`C}7ssHWnx{jhz5N3Q>+Mhj z0nL*PQto?)0#jL{Y6`<}vfwDO=m1-{rWY+wTh&Q&PbN+DY9i6aL5-T7bWTdc#KgYa zQn+qLfYX=VngjJ`TMx4YH@QBj&KF~qCmz#7T9sID@r3RXODX6eL$Q|f(etRSg+ND} zRx}!0AqzXx>4?R{ zm;}vfTL1S$u8dQTuNGQVs47(SPhimu5&Z-7tt>HiZK`G1$$0{Q2`zci@9^$BiNA1G zlVdOd0(Zi8S;>I&zsR4LxF02uzsU443Azq1h_#iwz?63(SHxKu7Kw4%zTfsnLi`?U zr-kSq5>M_rg=~raP`#7FH<;%iy{(yvxcW6o-j#cRk&L9i_?*4xw&`H&HtZ%~-#L>R zh{%ol^M_U<&LnBVf?MlB$HOYT=Y)!OSZb2eqQ*t}p>f|#h8R1WlD>a#=4+VSp~Vqx zmyW1f!~35`oSx$J1VMy5^%H-Uw;xdo0amr3?wUH{Tsw_)@Vcj`8F2>ZRtL^!l$EE( zQY9@nxO}xXfhz`~O`Pilm!k}YTT8yf9;VYW{6yak`_x3yR{b*Jc$;|pysGf|Ui(S4 zQ0%a`Bi5e%IgO#tl!z(6w>~RrvW0P?8)dI2+=6H&lm&+U3LwDFQ*~5$4BulA1skvC zp+oO!+*vBTVbGQ(Tit?c#cc zd>x*kSQ@l4(uYHf?V#7EFA|f$)!=)qp?TQp#q=CYWy6hYt~25yw=F!Vq=hDNqq!$b z>`07jR=QJLg4C$um);ut7>w^obYmp@uOy)(vDKc;)|iiWRErDhtNdT%SN2~=QoZoM zD77{ALm{r&LC5c_6o2Gf?7!ZL*e}p6=`E>IEowI!xF7KUqV&Gx|1$qagSpfHCHfL( zC;0`SNc<1fMlkfCa$>geD}imVZzg=}Z1425nT!jlqqkz9p`i8U5=Px$U8n z%BYs6w*NoHUqDk9UmD2|{*i0Kwfsw?i%M*b?d82le|5!BeU*dg+^Ky@XD0c-3b+LS zjXsX=)ev0_x1`cJH^$=z5(~$u!)VCujRlb^yiLFEF^7#Y>^6^hE#7ms55De@5+8bd*jt5&*G1>C>> zSijjBze?WB^m)l`x(F=IH)qnmhm&-a(V17ocTtUiM~L=rj_7{NDK_?botQMctGu{ zDB2%_MY!k$!fyYHkGvUVo}J5+AoE1;*0Zh-@zQt6GDqLmzpu-{#I?!^RXPg}y@d0F0d z<^mAz3Sb-sn>jIh5R!OcrS-C*96;Z%H0-g^@xA&yRW-EQ_eS~-gp zK>YCTx-VwA@AjZ(#_hQg>dKbr}s@Zb!$ z6Y{GPn0=Ji*?N|#$iHsK_0^b3WR;Q1*xXn9@bGiJtGssD?kK#y(BsAPqVj0s@hZFM z25J;+S}@DTo7~{^pS)<1I+=+?YWeO8V2-xfPcC79GS}{pp*D=nJTndO zM&1ZZe@}kVdITpk9$+5Drf=mAeDav!7j8$v6#u9u#AEVdL#gH*T=WGxYja=?&f0^8 zDi;e}&e~Jka_9#WgLnw8MNstu@NKa(vcaq#a&KIpo_@@H=Qp&iKrT5Y!>Mw08tHm9 zoHnEs8*pprr>YBiARyhB+q7a`O$-$%Y~x%oPu(0+N2c>}3(<5GdwKKkY$`tzs^UY) z*MNsuf`?Um1&XoU0w?95_I(%`CYzps>zn{)VE0@)gHz7VW@JS!eJCnQb_^=hMCz>1 z8An_nfA)4}M&?H5O0ha2{TA@A`;1I9*JJ#}`{4WdJrX5<5vc@ZRQl+v3EziA*F=JJAm!Pz$B<(E<-26ox6EQRu5% zJ909aV_v7@jniG_=G;CmjZ2TpM5f2gu8+3W4BK{@TK;PP(OkD z;l@iekUxPvL1ynQ%+c?Q0b_rzx0vTSAg^%``EXb2V`w&{gqX-0BcqAM7~c>dXN+nX zW=RLb<}O%hJhaVwDZ`DmiWV#H8E)`;JXL3e)Xzf}O+Ksi@f*{{Cxz=5gDLD4Dx0Iv zZuSL{2es>@^M*OGR9obD8T}yBKl-bgH7Rgt9K%|xuvV)twRJ~6Edm?C)rI}DCW9ql z=$RP;EfWL0f&1FUZ(OaC<3Ftk)gsq)t4zAeBa#K@!wH%=3QMFxB9zTm76_3;4@r0k zka@5-jD1I$mqe=DG>4(Ou{&KwSCFgc7;i#$`mCT?OR`t`RuJD@Ot4Iy%L?&HfL!j+ z>VGbUhD89BdgHtJ)nh+7!_X%|wDf`Lk4cF~KEE(&&F>}N~CF@7=X8^ z8-&Haor}LcS?r3*-Ijyq^$KS~)S1kkKQZS@j?vdqn@|86Td|N~E~36oz@SLfM^VHN z*0J#sYGXPTDNf|@a`TIw)sWfe0*h*OSEV4E`n)?Ff~uJr`;Y|d8<@)((%89UPF<1T zJeIe@L9>^re^^n&H;piDc0RiNfZ2Ow_-xZ|QdYlLhe(V`o1WrY*iDyYR65a%<>TIu z%WmFQgd>ObxGeQF_5qdB}- z7hAA$NlA<6W-`{9<3I_4`;K#!e?g@DLGSxmi~JC9GNK2!I_1BGMHRE>~l${AHY=Yinc@$nqy!y&guuy1oz(O zvFbOlK2dlYxO@+M`~U*|)$QOWZcNm`(0p#yJY@yE+!1xhe=az_0M=u|KC0J@yH9J~ z0lGBl!0i)XpsiCoH(+}#*XWXg05=nuEXT@T#s`c1U&_VSLZ1kD1ID5#NVrFtK&r1TRWyY zTzNV(D2$5?uO2W3@2JDt9nCWuNv>ZZ|B7#>Mn3LcaWT(iCT1KjSv5~yJ%}_%uUd_V ztk>UW?K_vew{qQxI1#0^01{zKX`@0|JebK8cp%3po!ycVnv!j??6>#lS5 z_JUb+&CXyC*KM+cogA%er?lwx<3XlPRAX;*a@++#$Mz(-kCew2?{?6^wi`tWi#~+$ zNpVt=b}xSryngeviqc-6QqW2jimW`@>7ZH?y=Qe@Tyl~&u`_jHIx3X#nqk!)^9k=5 z)AJE*evBwc>1F3}LU+C}N~9rmij_Dq8B>RIFewuC zqz$zxWB-!<(T61(8@=F4ND?g@(}J1nAN5yiC^1(QeAjq#vF*9~{kmnjdUu#YGQam! zct2d;BVhuI4w(z;@Wg{`54ift^>I48hDrGhfzWlR)Scw4n$zj=ywbJ!_NzZE*OG+bLtTGZ$n^}(i?G}I)Al} zC8rvvx+}LC!*!u3SNC^>k#+U9jPPljOu?gqt;QSK?7TZ$24h3fAl5U(SW=49nPGl; zY=GVQqg2}!r-hK0tQj{4ra(EPDvW+&ALS-&zOyCw%OHeEXpzv#bYek2zbYpk(`xlA ztE9Gaib~h>)_D&H{)kMu9zH#itO)pYP2$mE&(fjaHM!VVDn#WA(L^W$;epHYYI@e% zncZ8|l-QIuy};ZK>vF}HzR+rbUNF%rjk3orh^Hc{m3irac7#yT;Es9k@{Fpy}OF5Cpye#7-*Kh#a=GPXN%nar3DGMsr zOOUNY?;5y4o5SZecTVGdk-a_xWP_Y|1gHa3*o`>%@`;ql*?ajEG*+sSuP zFB6|`%45l6N2D|Q{cIgTy|5Irw7T6H_K5M6#*&h@N-c`?;)G(|l4IvHcSn)}%+4N( zZ={n42mcnr0xv{j$P1PD=~eGG?oX(G6w@zMTg6HrXSogx;s(uGHM2E=mmQop71|B* z=zuvs<7EhD!#;4^4k) zzy*PY6PK&!JZ~6+u^yu);qLM;lbrAcqq@ymUWS$l1jD-xT@n^r!o{lT#|r0Z*Zna2 z*=O!BfWH50HmAVz#6gcwOzB%j84T(6d*hXth5RsT40n*>+@W0^_S~V_vOc3fO+rO= zrkQYD>GI)OCWZQv?>MADxL=U|D##BU?7^D7Qz~_gix`860+wP8^87*37HyqbnML-R zsIU>wm7|*=)AdN-iC(KS$QIHK__mv+bkOXFC|Ip=6W`j@5^;=6L)0I_{R&7CbV9rq zIFltHhSfsS0`?^6JVWGLNMu;dV5%4q^>Bae08sXAj7Dvm&6(EzbZWXc9CKFfHnH58 zUJ(fX36c`?C+1ssv^{LA-P~I(14-BA1vLX$mPB>8zruL*pL=YJKj@DK2Z@17D(*Zl zLTr>K;8rpt&JurXe6L+U`6&8P-lwWNVwoNdN+HbctyDBAoGBGqI_~waJPaGB8%*VU zsADO9&_i7?|1<<(IMN7_4f7oadR~9)?6Wl-59=Z5sx2B6wVF{lyfz9ju(SAyyKXyv zU}8JVD{q0=IKV6Gs@I^0LQSN3&1wFvNij+iA^d$kXQTsjU5`zwlVaL;UKMf0d$V3? zRx&(x=}Hm_F2OvBdTARkLjp~To+|}BFiP6D((ewOS#Tz6jHjcp<@-n5GuO@`C8z)V zTKs7Igr8A7@#;pw#!G`Xu4ZVMA!@MJ8@8-a|Bb1^OhCPD4NS=puUqb{GqPf8D~o6? zhYC8_(m^+~e6EU&4cL#Pf~`m|!fo;Kh(ZZEq#+j5_^SRM{AUT*K@I)oz>XY-fy@Cf zhAN=QxJD;AEM-q2cnjEylweT)P>UV{1G{*SEMxSjyisNk4E74O&4^;ZY*EjHA5B9T z5}i;5HQ5N!3~GM0ql>;Z^=LHQ=#LmQO#=%x!JF-z$8XaH3)(0Kf-WLTd z>imrCS|-%n0p&NOR{5=jrN{`Ap@BPMdEdJ;Gj^mqoG`h)15rn}i}@9a*)wCf9OvtI z)Ao40^A50e3F*tObqAdn9kMn1YsJmSQ`a*B5!&QVgxRSRkH1T;bqG0lLkBPoq@=c> z{AYYpHCwo4F*+4*6g!|&q)-JgI#*9jXj2w1#}1kT>Dj?uofDZ9h+vdy~o_x z3%5}jLThR%N1?ZX;r=5M;h^xfmJ!DU(S0UOoHQOs*dnI>R3CjHz zV0K>zdH5B(A@#tjKi>3fwZOMdU#s`GqBmogQjh$!T*(ssA^J8*4K#?2eJe8xt+hbl zh;Q5rgETdHi6T1q!Mli$*naGY!bja1q)?_M4~RG}}p zkS|j#ok|TN_aifL*i?}X;s(9Dp0tAUk6I2&cgC<)dPba%$#hM>l75D6aBDRM#zp;7 zf-n(b67@nwlRu&F*Te)Dix9N`!vyt>M(qIRIF;He#gX>Sc!%GUPynA3aN3h9A#yCg z{ax~{OEpNH@kOV{_9Vc^0cpRLV5*~wl6g)YQVNkrv&vT=Z-YPz-T==puiPDT6jdr;W-+hj-q>DqaTra6p4r|QLt;xEGKAY~L*k%4tFhVDHjc!=^=^Cq-HCu34v)L#9xz&g~-I9aY3p=Y?_G0 zYOC*)8l~7Y$vb94W3OU@ENWFq71%V(!t;@dhhMABY-D0utrCkGhNbtvXfZT~o+Zzs zW-%mH%JX%zPKed8(4=C+7kN@cGsoO~Xe^FG{R>1ZDy=pS+;V8lfGVx_PL)ON$ezv6 zm_qHp&au{#FRb7G|CD@IhmUup2S;N;#vj+2Fepw9=n#k+7IxqMX9af&g*bnc7%J*^KF+T{t0gexja7>zK%di@|B{~UM zFZDDstJ95}5b4m{VvjSt^?Z4mWFf1NIqE)%>RV~;aFK}D$mpZ;MH1U!*aiH4h#cr! z$#n;h`HF{LfPDP@lp*%U2Oq*>NkA6iyP-y955*P@AeyqwFEE&s%2nLC{ejI{A%uq{ zIg^pH;uVV%I6UGnH^?p1fXe8IMAlRaJa9#W6`X|6oP|F3=g2}MR?agzZwxRk*)S6j zM?Et_0By|mHWH>uq5p=Cwl1f}vbr9hVW1XIttKt22ylG3s%9?(3J(VYLxy%xn{fBzdP5Jyc;P#mrusd^~J}@gJ{wZ>k_(c_57wolm*74`G?-s!| zz93$tEo0=^x;NH`peHHbd;apDL@#jl6|AM+-7Y~d@YdW5{herDJ2A6gCEaBOi#z1A zv|(}dIFJld(o2xN$U|p}mt1lIKnn||vPrS#v&i056+^6t1;(S0-O89GTh5FWrzB`= zmRQ@?mOr#_P=~?6Fk7V1vn*Inq1ll-qEZDtTy!{ai_ng&>0uOZXR@MFhl2+Xbr@&l z25{l#sx~FX4IZ6+@!<>UiAm~rc0qB`JOz;#=xYVyB#TH+T?rljOMAt*u{U8*;HYi? zuQh`xajm!to*kF5+*~WM9oiX1S~a=+i+pV>e4bJg{l;_I+nxA*ihlbqB~Gbjhw`t( zcU7+^IgJ_S!qqEmIF&GuUHH=HHe1e5J}<7SP&AjR6Kkn?lMr+6QjGa!axYT7dldW_ z(a4jW9m;l)rD35N$~@A5&K*uTcVzkZ+bl&d(~ST~arnHQ+yA^71@a~AB#b*^O zkNP;`%dH`Ig`-{?Lz&J6t$zxqD4cVVdgd(_(4+*oo?NORB*i-P%;ToS4ML14ss)%# z`!R7L(U&R4Ak1PykVVjO@2UM+8-gYw*}KAxs^Af#onx*Hka&NrK2I3|?l?eUM%L|t z=mi07H817PfIOA#<3LttfGJJLXiP+7zOMlwdk_SZ@|wY?m8)Cl@I4jSe9NeE5Q6~O z7>`(|qIY#=Oj^ZBHoytVfSOL4an?>#RGf}PcC_EU`OT5*=}8G$rbE>CZWbu-p}V%h z9!Iv8{Yrzp68fD>*1yGV<*sTyt_Zz){j!m_wqD=uI}wbOSu6xD2%S0i$F{BsehaY@ znkc@`VE~ze_)=nGA|;;E94j>W-M1N*C6B`?`t%h!>APY(()%zHc$LW1HL`J@lAK4j zQY0qcjg{M@BYK;()^q)$pX}u_>Jdzp5ZC^6RE)84nBn|>uW?3w71A;WGmYSWag6Yw zQeZxFQft*tBDIn6PApK!e3QH-)u}!ForQ3=hG##!7mzT2|IIq^h+WXe;YoQgL4TpE z5T!X9YEp-511Xd8QtVdZ?8;?9Qwos@{R@F7Wmy2y8xf)BpZ`cHSMT45SUC6AW1Ji5 zKZ?-go82gV0FgF4=y4#cwH`u zM#;Ik+Bz#hb%cfb;t!u=`EAh#RM#O*dsazX13PZN+I~x0(Dd%ucxSwKLP0qk!@~&8 zKnO^`rP#WVaxb*vvE1M4yENC6=q;)Oz`5~@n1i8Y=ap<@>{_68tFWG36Pkb*3z@4$yl0%xv#;$C*(8*~Yjk0JvokLEtqpKE4m9 zw~x~YU=t`sSxm_9m1L2ShBHja4|uYNF8-E7*Yh!#!t)$90uz@Q&j^q!7W?gw^V?2y zresL$PvebYcn&j@=yBL;YJZ1V0oITBsLuSeoDwbYm@w2Uc$|F&R-ANgIYjcg*{Cau z3x~ZK5>F9@K+ol@*>V_dIk_<3s0<#v@H~j{@D0~Olte2@sCTSu@WR{r?$D6Ajn0Ri z6F-(u$SWpIfjoNCMM=yn4tbI`(UF99-**g9VRNz{ko`lMrrH7fElRATZxuPYjVOV3 zV5s&k$Vc9Geuq##r@1Sa%@ggArot#%msX{C>9elzAP$e9$Zul(jEG0lKi%=K*Kn)r za3HQ%(hULrVuIts3iNU&0^1d*-=baxCu33*A^D`lh}`-kt-_X^! zsMs#3Vt_{t1dc)9oGS0_#-)HC;7(!o4NXK9u~Ku~4|Dw%>spn6;dZ80n0LuVWN!bI z%}Mc~zw$mHMwE%QOpuqVk(xo(}*)7eO@Ma?Qr;GYtL2UT+PM!25 zA+n))IJYai`ewz4>2nf4Yvnv}MuoQ)IO_qq0c^;c0dC5>amCFiCN)M(7P%vljU{43bZZkP9` zZDY2)z0Wf;01h93%j)40BqxexZQSXDhVugvFY=%&B&v7RgQuh6W$z6mZBEuIYj3W2 zTh%LtBo0#ttzYpp#b84Be*k*fnWDZ{LbRjV3Lrcqc0UWY_1{*;$v?Frg!|UCn7d~# ztOfWtN69q6V0vf^tV%7OT+ZUVa(MeYR=(+Aa+7#5CRgF&?5 zqZzKzai)Nbk6#rgEO&aRt;up&#NhT2(~p9>wbpkO{?b3P7KBGt+>iEOv6b4ebm4=+ zo_@@EW-j_$r;bh``hPv>8RbvgNg+VC^_o9mTiTuVqZ@IYa^$aSE4jl)vdd__aAxDW z_b_JzLFWRAvNelQS-Dhqs0@PZ!(C?6B_p*?%fQqJlfEq9U)Y_?m+cyOwq4)~tr#g%1KXsUQvlo*nZYSq>0@Z1JzFV@l%H`$n7HuU> zG#MFRq$f!o^zpK2(&}&~mCVO{yL}V%DflLYRVhq;P1Qmz|8Wwx(q|OG-smzUbU_E- z#ebypNhn0yYwUOxbex4+DTc~V=HALNN_!-$d6H9jfFYFbyJ>NJ2hgmOj78@(1}3mT zARGGcDGz-o(?hBak;*^^)>cd|Jzu|?F!RrQGkcT4D+R->YC{oV_vAW35KgpIX`Y~t zs_p3rrQ+;&)Da_7@abJ10H$RM5l_WS6+pwU(RqW}0^gy&F(x$cwCJB~ac&WUs1d-k zbHcXGPFJ>Gpf!*WnebMe*hVmnGOR7bYOG`h&Q32)W&KFN&Rk>7{h5fVXy?IkSRT6} zB!}RPWVRLSb_0nIDl<`;vlxy=$`%4#M6O zvXX4_i(-c?jm?!kZVc}g{a zLm4Ai+`ttm-Sx_$L-h04Y$!$e=dl|-DrZ(RRPSEbTiuqV9iHgrXUKJBg_B393;I1r zEJrj1cRSJenxR~$<=8+$*ZG`El1qn~oSU8J$1Lb+4AtMQO{oz^k6=Fuw4ZrTFMM~_ z6i?)*I_tqjxx0R&Olza0xk1kFs$tMmNyA}YRiu~V6<@$Zvtr5NF~eAgqQ1c}t!&3P zrfsJg!QrV{`}U*$!6-b>J|@;m?R%USBGje&0!~N z8}KvU>#?SIcMix(p(|-A9UjBQpp&Tiup%wMD@#lWYuhB?$3D(aRZtJy7-#4y&Hz*7U68X+wa0fl7=*T@VZl;g)UT(v>2 zYda0k-II@8q2Wvc|2pyJ?lHh^&hieBm^PoJgn`p$XegNz5WbLUS-C zx@;LS)!oTxCVi7o)1?O;FEta#m5P^lT=ln=ivRej`7~o-G$VE?j(fJ?r<>@G#84a} zqi~mxbP;i;w$E5_cGg;It$4p4pZsC$4m94-9-a+kgbOnc9{%7J`2r!7A1fS&07=W7 z=`YA98sx6ix!bB?0!16Sqx8sMwX@R$DyzefQwh17nssnwsTW*C1$uY^G^~AXFlsaI zN*OY5&;Q|N@iQ>C4Sbk0%5n`wkEr5jZM5Izb#gZ6-v@CqoIk-`2s$|oL zQ}ar;2#bpZxZUA7%=CltCbJ@q!19}#%0OAuUxMgvgd)7IsUZGp z<)#MF)!5gKeFjV6B|MsF0WrxE^X z;YIYnE^7d2N$Y7>x4*Spy|qF4dQs6M+(Rd`=!fdjxjX^ce^_8Q?EGuv`t2Gc*H>2) zO3)mHjtb$2(t?o6*ZLnFd+3$iLhX5|qXQC(_66AMcJ!ZaMKnbZhsN-w7??UtS|Tw( zZ@I$8uh0%)Qyn$qRelTBvTKI9%!#~OI9VDrxDv47&&71;;>uvVC}AfNZv~E6pYABS zLR+rLw^BUOC$Eso?-O+z-$i`yWay1z`7Ih(WM`-_5dP zL4Q!FqdKruEcg!1Smu}DkP3L)UuGEU`2Std-5!U~@zHl(^sO)RJF-1Gr{r2jBEc>%l-$Zz+1j2?tZhCR0fXCt zm(z;PoVs+ewv=6XYSo4ig)2&gN;-~dBpnPoir2@iehr?H>+p)~`T9=SvLmD0jQ=KU zpSVL>g96>%viY0TNIXC6l7Yzb6g9%?lQBB)Ep4V$zJ@l=G{bV&wTNLOS68P~pdoj^ zyw%I;3sHDv3gz25kjhOXos{b{XsG-BDrazp!}s*!YDyOFnvV^PijiXVWs$bu+IMu& z)6X%5M~Q}sOuT#g63R`dnjdpNq~*P$2{FPcQDxVgp;T=Mmt=~P`n!2%R)f`W$wmsf=_l*1)(lg z#zTP817w~*o@U3jZ6IBZ0>3*I#M+Ev7G86tVfb}GzJhmSx0=SXE9bRkmvSoa##*1B zzz-tQTj=ZOj}r%Wy${u0;a^Uz+nDp@V6Up*M39!(Z+|>`Fm@jx1v2V)UuNbFr0N=Y z;HYX?jzP5MUnRj%8`PjM zuBl`jyaNA&dn*Q^tY~Ju^GJ@1HU*XJN(qSbdgqRXu#BaLIrS*(`f9ulylM&ND0C!~V@4dpwgshijqPLBW4k`;((}gAT%s1epbaN8v=@$Ef=~ zW%Yld`V;jd0{LRLa&VHUHVZ=q65@fync@+efeqIypgt5}S>?Qkw7gKihb*bC+#xj` zawGdqWTsh@*-|CVzYII2YxQhPIxs~m&6PXeWXSD}mAVOhsx3{`C^~uKo$U}dxAdru zcn7 zsi!o3`JZRHdp#*)JD9MZw`-NdvSyFxZ#)*SmNgs{CnaqEExvagmsLMERH@T{GtVjC zR4`b{JX6M|2xx*r?g2{)gzN3;98eOA1UZ|MUjt?{3kLu@PNBr7I+t)9bydo1+-yRn z;4^uPf#XMD>6}pem_PiAyNgF*DF~pJxW5ala+FYis^113@pJ8Rw5JIa;zCp(MB2+zu_}BgUWtT@ zyw#As)Ir6%KXr6zYu4YD^4YC=O>8(;Vbi_LQ3!Xy7|f{+0TECo-hIL zyF^$-KcfRpot|_#u8_c58C^JLeye-0xR*S&U(!aZ%N)$joSQ#V=qpf)t#}Q9wx{~c zW zHScgxn3(1Tsl=OutJlRfc?Ra8<#ZddiVkEAHF8eh%E?!&fTe~SvrWnR3<>W*iEdYe zW2lgJ&4v@nOl-Vjzelr0YNvDjH>a0(2gF;)NZTKDvZjV%v0D1CvXij)uXdwu_pq)i zI}6T4@E?BQTu~b_Z}sr_tj&odlcers-N~KdHlH;nPnOK z2)vuYodVd|Ra0_1GUwV;Ei@j`s@&v~`)S7sWbR}R#NeAvu3*J1bPYdE@j&8kPb$>>X zZEaFfpmC^3Nas{xQt1trGyAeJS2AViXMs_9`^H`aeVtkRQBR(ICYF#LxZxb@!+;fq z`%TSBJ*eonk$D05 z9?E?bxiYa6#;rEPa@w7U)>n!WO|~ft{*bi!52+jDChnhtWUiA8*o50D;Cn}FFk9?fs)V2C`8*pS_Xe*MHWD)U zLt!uDB4^lw=`gIeo#LYR6{UH<`B3SlA$g!w=b-@~8(p^D5cG=!sCRgnTc~gn>BsKV za9H8t^QvEn<&4_{On9Lj(h5z;jsbxsiH>MXDz*6jjJr}DdIZ7AAS&kncPRV#53Eqd zCkCcyh}PU}EQ_c#keS`)$#R(nLiQS=!VAm54gW1j5BSmS!~iU-1|SNfNwZJ-WDRxo zMBR6TSnIa!%Xt8no91Df<*%DL1QIr*!(Hz{V_MHVUnxQ4vXCA}>Lb27w_2_3n{Rce zVPXi>D_(QDG1kz-im=4u;Dwgrc~I4-K?;LROD{doXpLL$&#Yx!nq7zd_?iDoqV#rz zeGQ!xUKiw+;lYMvA2_;j(>H`NudK+<*s(0mi&26a66ruF%N*GJaGt&(C*!YPC#yrefJr5UvAhNU+nT>fsz7?15T7;qK6no^?G*fDyqlo@cNky zGuQe^13#&YrHNW9VfBW96YQJ>i>q4x3+u}6hy(TH=+VQ!7N?o>G_>X@I7wsI;|Qrg z*IZ2!UUqNtMxK*R31XIFzt$!*5A0E&8j}kwB*7in6kEhG9$@q01t??dXD)2qaw$Y0iv55^} zm68Sb^dh3ldQzV}Bo5+;xNs@)pB2jL@$W_`g6-EDO+zXyCVt+ihNH5++?cxIe~rqP z%~da~^OzQ6o6Y{tdDN8{3;oWiH|O~Pc@*7|>r1Pmee9iWISM~QDM~qNMRflc{^7WQ-D;<{7{G9JYZcA4o@F zuffpzyHUJ76RVJO%01mE?qFfj*GRQF{l@o0{Gq88Qz+@^$kGh8k89Wa_||!)P|=VJ z`@hnkCeV>bT&8~GKNd+JL*37mH2-xgIdCV?ss%LRz{wWAka}9Je#01{OoV-6=4*Apmef>hQo)M^>GhLgvqo3@e$tNaH>Z;u@smuN(kO=8u zagVkuVa5B2VVoh%_xVcPZXG2L70UlJ`(l{A04UAqn-eN^11P8DCef!AL z&<^G`4G?)U`zWe0=P{M~_=pYI1i0U7+ISdBs?4wVwEE+}XP7Ll-+cI0^Wi#wptkN5 zQ9d8N!EOwFVYQJZg?e)k_V;ZVV~DthVPFw)7TbzefT|eYnKP+q*#@~$1gLq9vZtY~ zty71{SlW}t)$O@lz+^CQw9g+dZUHZtC!QnYOeEVMpe&L{`~anH4_NUT1B9H|fhKgo z_6ep81M5qDCmM;dzJpk`YZ0rt%T=+KnRo$u;=|a#FCoL6#IWFp-pHH=;BcAh50LvA z=*+r)^AAV|9NEiiH->sW@-i|O@G-mL4@VI4Zy7DcH8(8pY~+9qIj?lqF< zBjaT+7nt-O0;)W0SE{-a?dA}dwq#|+!zdh|>hY58f1xGWLMiCzjM-99PZ{*PasvtR ztrw7X5l9+f`z75=V*0^HArTBW^J{Ed_pE^TZ z179wcop2XR|FCv;YyJ(ewh!0_ddn^0=0BRz0bd?A6Ru<^V+uG#rv&pMG6}!E-2ct` z@ZpN5d#&418vm`0fM4ROxL)4XRK)Bw#tJghR1E+1tI$`$Qj-w4_OTkdbd<-q7^;a# zdDE84FP~eWcZ2Vah{7@!hPJOlh9@3a>m|^9?Qkeinw_B!7WjNHHAun{f+z1BR8)H zH?O82(=`IqAW~SK5_%PqJD}R43+=?gv1~8_cr;Pz(T@!NiNf3ME>*p@LMZJn7BC1;l=W zggpD&0TVk9)K8X*XVAy=?E>181K2=<78tLD50~sUBG?z{qb6iMKp zw^d`2gbDd}LSG6tIEY6hPsj;7cM@L_c=w`8#&^lIhOSt!o0#lW)(X$e9*))hmT5BV z{@|Qwd@S408q1mOCEw!boVD@7CyPpaugNgBQpkpaa6CUH=)laR7SEwc%?x@sJQhJL zLb@zCC`=t^k@DLftSIy*7BgYaFPGv%-$o!IavbsE4dYmIt({&I6^iiwZU7!>%|QDN zbqV3lT;QTr-5bKUIVzJ5^mV!6&2=WU&B8PyCYic6H|A;TP1GNAM1PX@nCIwHvr06f zp2TBw)9fm~Unt{%75(9Q?^*7%mwN0*=0=V5fFawt)51MB!vH8a@ItZj!#IK!TvPtO z#3AYcb;jF!$}R{$^CE-S^Oe09qiO%`cPTOg50`I$+J#rZ$tK3ml@3kjj?R_!oeh!4 z`SCe%P;!pnfA!#|9ZU^w_9QkAzg$IwwAVi7{2EOhuzffS(~K3zD9mWX@M&(xy)bkc zH9v{j#9Bprn`a&UOkRLsfVzm1J$L4cm9x_-Pb>!ZwPr5tu)jHx*;0RPO zz?nsJsBD#K$eA;(USp@Ls*>x|@cP$=bw{-GtshYdIBa`^obC4p=v8`OVVtx4dX;Y2goO{NK{RwyLE zWEVO_{XRC&-R-BPD4j~?U`=>WxvA!sqiV-kc2h1S4s0k1@!ah zv*RV+Ez+1_?Dk(w#7eHzVu%dvtgRRn>IAa{1Z@-| z5(ez_Lz5}3yLcFA$|Q>1s$JtyhI)PwS6qU!Ll2XkjA78~MY7{2*Hg^W&H^c_<6Mn? zTcd-V{vY31MTo;%datbK`fMy{%*y{Xj?*vT5#Nap!9x@9ye-sUOupYNRbtshb&98t zV}#H^P(RdhO-K8H;Et}h)6igbKBeC|^il>MX-W>dGchWBVUkQPa~>U}Uw9~r1CwwbCadUoi+gp~i0%Y$ZdA1>c+0Kjs@yZyCEE^27 zON&`HN@%hJdR%y&U9v;zsus~_YsHm!Y}7RhYO-6TcSEXnkXrAZC zTj8R@H3E#fi(oU><0&@}-K~kWNg{c!)+j2=8KW9!Q=O%%FGfsRFT)NjF)kO?+wy=U zLV#ovG6x7x{KtG#3qlZe?wyFbT8@B8s>5S}R)DtJbMo%+w)X@Lf&I9d9~?y$Y?AS<%G z6{)b>6EB%6%+qIm>0pDFzCrDzMNUMi-!DjU5pY_N-u5mm;=346*0eM9MEAI&FUNpt z9Zn>Ez|k8R!8@#-wW8>WwMXP`0n)>ZFE&)RZH9$1V1`)R&GqEI{C!Ptbtf2A8zn~_ zo*ch^9E&GDT@mr9XUV$8d&`%u5+TEk7d1F$ALf8k^`d=Wy1ffK#I9P)uq8o`&)~!n z!{W=5v4Ux03~g)Px1srcT#a!JZp#2~_wGj&2IIQlq-wfud-cO$gdkqAf2=(tPsk1YUl#K zsJIw1wjK#o|A{&smh#nDE%VhG*$+fCg92lQv0?k&ayA$_hBgMRjt|^A{0u?g;~1A{ zT%u|9vN_)$+>K(I|S&CX4-D{r`sbs+!W`=jgH1_{{y6cgmM4? literal 0 HcmV?d00001 diff --git a/docs/validation/run_metadata_namespace_327.py b/docs/validation/run_metadata_namespace_327.py new file mode 100644 index 0000000000..447f656fe9 --- /dev/null +++ b/docs/validation/run_metadata_namespace_327.py @@ -0,0 +1,53 @@ +"""Bound a source command's whole process group and retain original output.""" + +import json +import os +import signal +import subprocess +import sys +import time +from pathlib import Path + +SCRATCH = Path("/home/ts/.cache/agent-scratch/metadata-namespace-327") +SCRATCH.mkdir(parents=True, exist_ok=True) +name, *command = sys.argv[1:] +start = time.monotonic() +peak = 0 +reason = None +with (SCRATCH / f"{name}.log").open("w") as output: + process = subprocess.Popen( + command, stdout=output, stderr=subprocess.STDOUT, start_new_session=True + ) + while process.poll() is None: + rss = 0 + for stat_path in Path("/proc").glob("[0-9]*/stat"): + try: + stat = stat_path.read_text().rpartition(") ")[2].split() + if int(stat[2]) == process.pid: + rss += int(stat[21]) * os.sysconf("SC_PAGE_SIZE") + except (OSError, ValueError, IndexError): + continue + peak = max(peak, rss) + scratch_bytes = sum(p.stat().st_size for p in SCRATCH.rglob("*") if p.is_file()) + if rss > 512 * 1024**2: + reason = "512 MiB combined RSS limit" + elif scratch_bytes > 256 * 1024**2: + reason = "256 MiB scratch limit" + elif time.monotonic() - start > 60: + reason = "60s shard limit" + if reason: + os.killpg(process.pid, signal.SIGKILL) + break + time.sleep(0.05) + result = process.wait() +summary = { + "command": command, + "exit": result, + "limit": reason, + "peak_rss_mib": round(peak / 1024**2, 2), + "seconds": round(time.monotonic() - start, 2), +} +(SCRATCH / f"{name}.command.json").write_text(json.dumps(summary, indent=2) + "\n") +print(json.dumps(summary)) +print((SCRATCH / f"{name}.log").read_text()) +sys.exit(result if result >= 0 else 1) diff --git a/external/PolyStore b/external/PolyStore index 4939979978..91fd7e854c 160000 --- a/external/PolyStore +++ b/external/PolyStore @@ -1 +1 @@ -Subproject commit 4939979978439a6f38145788df9aac3bce65bc70 +Subproject commit 91fd7e854ca760a16e7472d8d558b4012e89b6ba diff --git a/openhcs/__init__.py b/openhcs/__init__.py index 0fc21d6fa3..bcf7df4e9a 100644 --- a/openhcs/__init__.py +++ b/openhcs/__init__.py @@ -7,7 +7,6 @@ registrations or other side-effects. """ -import os import platform import sys @@ -16,8 +15,6 @@ __version__ = "0.8.7" -# Configure polystore defaults for OpenHCS integration -os.environ.setdefault("POLYSTORE_METADATA_FILENAME", "openhcs_metadata.json") OpenHCSProcessEnvironment.project_dependency_gpu_import_policy() OpenHCSProcessEnvironment.project_numba_worker_profiling_policy() diff --git a/openhcs/core/virtual_workspace_metadata.py b/openhcs/core/virtual_workspace_metadata.py index c9aabd2da3..6e8cc71b6c 100644 --- a/openhcs/core/virtual_workspace_metadata.py +++ b/openhcs/core/virtual_workspace_metadata.py @@ -5,12 +5,13 @@ import logging import os from collections.abc import Mapping, Sequence -from dataclasses import dataclass +from dataclasses import dataclass, field from pathlib import Path from types import MappingProxyType from typing import Any, Callable, TypeAlias -from polystore.atomic import LOCK_CONFIG, FileLockError, atomic_update_json +from polystore.atomic import FileLockError, atomic_update_json +from polystore.metadata_writer import MetadataConfig from polystore.virtual_workspace import SourcePixelRef from openhcs.constants.constants import AllComponents @@ -36,24 +37,14 @@ @dataclass(frozen=True) -class OpenHCSMetadataConfig: +class OpenHCSMetadataConfig(MetadataConfig): """Configuration owned by the OpenHCS metadata file contract.""" - METADATA_FILENAME: str = os.getenv( - "OPENHCS_METADATA_FILENAME", - "openhcs_metadata.json", + METADATA_FILENAME: str = field( + default_factory=lambda: os.getenv( + "OPENHCS_METADATA_FILENAME", "openhcs_metadata.json" + ) ) - SUBDIRECTORIES_KEY: str = "subdirectories" - AVAILABLE_BACKENDS_KEY: str = "available_backends" - DEFAULT_TIMEOUT: float = LOCK_CONFIG.DEFAULT_TIMEOUT - - def metadata_path(self, plate_root: str | Path) -> Path: - return Path(plate_root) / self.METADATA_FILENAME - - def managed_paths(self, plate_root: str | Path) -> tuple[Path, Path]: - """Files this metadata transaction owns, not scientific source artifacts.""" - path = self.metadata_path(plate_root) - return path, LOCK_CONFIG.lock_path(path) METADATA_CONFIG = OpenHCSMetadataConfig() diff --git a/tests/unit/metadata_namespace_327_worker.py b/tests/unit/metadata_namespace_327_worker.py new file mode 100644 index 0000000000..4f2fe46759 --- /dev/null +++ b/tests/unit/metadata_namespace_327_worker.py @@ -0,0 +1,45 @@ +"""Native process recipient; import dependency before application on purpose.""" + +import pickle +import sys +from pathlib import Path + +import numpy as np +import polystore.metadata_writer + +import openhcs + + +def main(): + from polystore.base import ImageSamplingRequest + + assert ( + Path(openhcs.__file__).resolve().parents[1] + == Path(__file__).resolve().parents[2] + ) + assert ( + Path(polystore.metadata_writer.__file__).resolve() + == Path(sys.argv[2]).resolve() + ) + with Path(sys.argv[1]).open("rb") as stream: + manager, root, config, expected = pickle.load(stream) + workspace = manager.registry["virtual_workspace"] + assert workspace.metadata_config == config + assert workspace.metadata_config.__class__ is config.__class__ + assert workspace._registry is manager.registry + np.testing.assert_array_equal( + manager.load(root / "virtual.npy", backend="virtual_workspace"), expected + ) + sample = manager.sample( + root / "virtual.npy", + backend="virtual_workspace", + request=ImageSamplingRequest(origin_yx=(1, 1), shape_yx=(2, 3)), + ) + np.testing.assert_array_equal(sample.data, expected[1:3, 1:4]) + assert sample.source_shape == expected.shape + assert workspace._resolve_ref(root / "virtual.npy").source_axis_indices == (1,) + print("native handoff: exact application namespace, pixels and provenance") + + +if __name__ == "__main__": + main() diff --git a/tests/unit/test_metadata_namespace_327.py b/tests/unit/test_metadata_namespace_327.py new file mode 100644 index 0000000000..5df136b5fd --- /dev/null +++ b/tests/unit/test_metadata_namespace_327.py @@ -0,0 +1,103 @@ +"""The application metadata declaration participates in the generic contract.""" + +import ast +import json +import pickle +import subprocess +import sys +from pathlib import Path + +import numpy as np +import polystore.metadata_writer +import pytest +from polystore.disk import DiskBackend +from polystore.filemanager import FileManager +from polystore.metadata_writer import METADATA_CONFIG as DEPENDENCY_CONFIG +from polystore.metadata_writer import MetadataConfig +from polystore.virtual_workspace import SourcePixelRef, VirtualWorkspaceBackend + +from openhcs.core.virtual_workspace_metadata import ( + METADATA_CONFIG, + AtomicMetadataWriter, + OpenHCSMetadataConfig, +) + + +@pytest.mark.parametrize( + "config", + [ + METADATA_CONFIG, + OpenHCSMetadataConfig(METADATA_FILENAME="application-custom.json"), + ], + ids=["declared-application", "explicit-custom"], +) +def test_application_namespace_persist_reopen_and_native_handoff( + tmp_path: Path, config +): + source = np.arange(2 * 4 * 5, dtype=np.uint16).reshape(2, 4, 5) + np.save(tmp_path / "source.npy", source) + ref = SourcePixelRef("disk", "source.npy", (1,)) + AtomicMetadataWriter().merge_subdirectory_metadata( + config.metadata_path(tmp_path), + { + ".": {"workspace_mapping": {"virtual.npy": ref.to_workspace_mapping()}}, + }, + ) + manager = FileManager( + { + "disk": DiskBackend(), + "virtual_workspace": VirtualWorkspaceBackend( + tmp_path, metadata_config=config + ), + } + ) + np.testing.assert_array_equal( + manager.load(tmp_path / "virtual.npy", backend="virtual_workspace"), source[1] + ) + handoff = tmp_path / "handoff.pickle" + with handoff.open("wb") as stream: + pickle.dump((manager, tmp_path, config, source[1]), stream) + result = subprocess.run( + [ + sys.executable, + str(Path(__file__).with_name("metadata_namespace_327_worker.py")), + str(handoff), + polystore.metadata_writer.__file__, + ], + text=True, + capture_output=True, + timeout=30, + check=False, + ) + assert result.returncode == 0, result.stdout + result.stderr + assert "exact application namespace" in result.stdout + assert not (tmp_path / DEPENDENCY_CONFIG.METADATA_FILENAME).exists() + assert ( + json.loads(config.metadata_path(tmp_path).read_text())["subdirectories"]["."][ + "workspace_mapping" + ]["virtual.npy"] + == ref.to_workspace_mapping() + ) + + +def test_application_declaration_inherits_the_existing_metadata_contract(): + assert isinstance(METADATA_CONFIG, MetadataConfig) + assert OpenHCSMetadataConfig.metadata_path is MetadataConfig.metadata_path + assert OpenHCSMetadataConfig.managed_paths is MetadataConfig.managed_paths + + +def test_application_custom_environment_is_independent_of_dependency(monkeypatch): + monkeypatch.setenv("OPENHCS_METADATA_FILENAME", "explicit-openhcs.json") + monkeypatch.setenv("POLYSTORE_METADATA_FILENAME", "explicit-polystore.json") + assert OpenHCSMetadataConfig().METADATA_FILENAME == "explicit-openhcs.json" + assert MetadataConfig().METADATA_FILENAME == "explicit-polystore.json" + + +def test_application_bootstrap_does_not_set_the_dependency_namespace(): + import openhcs + + tree = ast.parse(Path(openhcs.__file__).read_text()) + assert not any( + isinstance(node, ast.Constant) and node.value == "POLYSTORE_METADATA_FILENAME" + for node in ast.walk(tree) + ) From 5660082fd9895fa0a8ddf0c6b7dc8392d9f6a7e0 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Wed, 30 Sep 2026 22:12:27 -0400 Subject: [PATCH 16/19] Archive paired source guards and preserve the parent registration boundary --- docs/validation/metadata_namespace_327.rst | 12 ++++++++++ .../metadata_namespace_327_resources.rst | 22 ++++++++++++++++++ ...metadata_namespace_327_source_evidence.tgz | Bin 23388 -> 179161 bytes docs/validation/run_metadata_namespace_327.py | 6 ++++- 4 files changed, 39 insertions(+), 1 deletion(-) diff --git a/docs/validation/metadata_namespace_327.rst b/docs/validation/metadata_namespace_327.rst index add4c06193..dba3083cdf 100644 --- a/docs/validation/metadata_namespace_327.rst +++ b/docs/validation/metadata_namespace_327.rst @@ -122,6 +122,13 @@ system3.14.7, matching original CI's3.14, executes its unchanged pinned local Git source. This does not alter product3.12 validation or install/download anything. Tool worktree: /home/ts/wt/metadata-namespace-327-audit-tool. +The application guard also passes unchanged at committeda1a1689f3 against PR206: +all deltas zero for openhcs (14.01s, peak74.06 MiB), scripts (2.02s) and +benchmark (2.59s). No parent-owned files are added to the write set. +Changed-file census completes on both stacks with no parse omissions: dependency +StringSubscript-2 / literal_key_get-1, application all measures unchanged. This +does not certify parent changes after the pinned baseline or a future integration. + Semantic edits were hand-authored; no NRA transaction or completed R1/raw-record certificate is claimed. Full NRA/R1 remains uncompleted, not waived. All raw logs/XML/command bounds, including failures, are retained in @@ -130,6 +137,11 @@ overwritten by the command-summary harness; the complete census stdout remains in logs. Harness now uses distinct .command.json outputs. No science data is part of this archive. +Visible paired drafts: https://github.com/OpenHCSDev/PolyStore/pull/20 and +https://github.com/OpenHCSDev/openhcs/pull/328. Final paired source uses child +91fd7e854ca760a16e7472d8d558b4012e89b6ba, whose production tree equals the tested +ee1e5438d64cb7d37e2641c2d9b3ee8870663adf tree (the later change is receipt-only). + Acceptance boundary ------------------- diff --git a/docs/validation/metadata_namespace_327_resources.rst b/docs/validation/metadata_namespace_327_resources.rst index e82aa65fb5..7eabc5070d 100644 --- a/docs/validation/metadata_namespace_327_resources.rst +++ b/docs/validation/metadata_namespace_327_resources.rst @@ -12,3 +12,25 @@ Initial resource guard: warning, 8.5 GiB home free, 16.5 GiB RAM available, the bounded scratch maximum is less than available disk. No science/runtime lock, installed package, GUI/MCP, provider, interpreter or environment changes. Archive terminal command output in this worktree before removing owned scratch. + +Own read-only guard-tool worktree: +/home/ts/wt/metadata-namespace-327-audit-tool, pinned3b03785f45df2ef5dc62ba6aed99294192ecbb01, +3.1 MiB sparse checkout. Existing system3.14.7 executes the unchanged original +CI tool; product/native test processes use3.12. Source native build outputs are +openhcs/core/_tabular_native.abi3.so and +openhcs/processing/backends/cellprofiler/_granularity_native.abi3.so in this own +application worktree (376,344 bytes total); never installed/copied externally. +Final scratch is approximately4 MiB, well below256 MiB. All shards are terminal; +peak observed process-group RSS355.73 MiB, including a retained initial guard +failure, and longest shard22.03s. Archive the raw errors and results, then remove +only this owned scratch, source-native build outputs and clean guard-tool WT. +Keep both implementation WTs and their seven source dependency WTs persistent. + +Cleanup completed after terminal-process check. Fresh extraction and byte-for-byte +comparison verified original negative log/XML, current unresolved-source XML and +both complete structural-guard logs before deletion. Archive SHA256: +05b24739c81fbdc86c3dbc9e96f45e1764a5f50ffe946953271c386df68c8dfa. +Owned scratch, two reproducible native build outputs and clean tool worktree are +removed; all implementation source, parent original errors and saved history +remain untouched. Rebuild the two source extensions with the recorded standard +setup.py command before rerunning application source tests in this own WT. diff --git a/docs/validation/metadata_namespace_327_source_evidence.tgz b/docs/validation/metadata_namespace_327_source_evidence.tgz index 56db32d8cfd20ebe3f36e14a278d24f039e0de28..6c2dd1e466edab0ba8c4c08f71726c0fe6c62a2f 100644 GIT binary patch literal 179161 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 literal 23388 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 diff --git a/docs/validation/run_metadata_namespace_327.py b/docs/validation/run_metadata_namespace_327.py index 447f656fe9..c5811c1f78 100644 --- a/docs/validation/run_metadata_namespace_327.py +++ b/docs/validation/run_metadata_namespace_327.py @@ -49,5 +49,9 @@ } (SCRATCH / f"{name}.command.json").write_text(json.dumps(summary, indent=2) + "\n") print(json.dumps(summary)) -print((SCRATCH / f"{name}.log").read_text()) +log_text = (SCRATCH / f"{name}.log").read_text() +if len(log_text) <= 8000: + print(log_text) +else: + print(f"Full output retained in {SCRATCH / f'{name}.log'} ({len(log_text)} characters)") sys.exit(result if result >= 0 else 1) From 5ce9e3087e603aaf5177ea88363ef48a804c1163 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Wed, 30 Sep 2026 22:40:28 -0400 Subject: [PATCH 17/19] Bind microscope workspaces to the application metadata declaration --- openhcs/microscopes/microscope_base.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openhcs/microscopes/microscope_base.py b/openhcs/microscopes/microscope_base.py index 6c09d1181b..24275e5779 100644 --- a/openhcs/microscopes/microscope_base.py +++ b/openhcs/microscopes/microscope_base.py @@ -476,9 +476,12 @@ def _register_virtual_workspace_backend( """ from polystore.virtual_workspace import VirtualWorkspaceBackend from openhcs.constants.constants import Backend + from openhcs.core.virtual_workspace_metadata import METADATA_CONFIG # Always create a new backend for this plate (VirtualWorkspace is plate-specific) - backend = VirtualWorkspaceBackend(plate_root=Path(plate_path)) + backend = VirtualWorkspaceBackend( + plate_root=Path(plate_path), metadata_config=METADATA_CONFIG + ) filemanager.register_backend(Backend.VIRTUAL_WORKSPACE.value, backend) logger.info(f"Registered virtual workspace backend for {plate_path}") From 98e3b0568455d834ad8170ef7a163cc55232f0ea Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Wed, 30 Sep 2026 23:15:53 -0400 Subject: [PATCH 18/19] Record actual paired MCP initialization and retained Opera failure --- .../input_preparation_installed_20261001.rst | 92 +++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 docs/validation/input_preparation_installed_20261001.rst diff --git a/docs/validation/input_preparation_installed_20261001.rst b/docs/validation/input_preparation_installed_20261001.rst new file mode 100644 index 0000000000..fd668663d0 --- /dev/null +++ b/docs/validation/input_preparation_installed_20261001.rst @@ -0,0 +1,92 @@ +Paired namespace integration and installed MCP checkpoint +========================================================= + +Parent integration owner; OpenHCS PR206 production 5ce9e3087 and paired +PolyStore PR16 production 91fd7e854. This receipt supersedes the pending +namespace/installed statements in the earlier quality receipt, not its history. +Normally merged namespace worker PR328/20 into these existing feature bases. + +Ownership and controls +---------------------- + +The existing frozen PolyStore MetadataConfig owns path/managed-path behavior. +OpenHCSMetadataConfig inherits that contract and declares the application +filename. VirtualWorkspaceBackend carries its exact config through the existing +FileManager native serialization. Microscope registration explicitly supplies +the application declaration. No dependency singleton/environment mutation, +companion metadata copy, alternate reader or duplicate registry is introduced. +BOUND-2/IMPL-12: reuse the existing owner and remove copied shared path methods. +The earlier MI metadata-detection capability and declared registry remain intact. + +Twenty-nine current application checks pass in both import orders (12.03s and +11.28s pytest), not fifty-eight distinct tests. Five namespace checks additionally +pass with independent custom environment filenames (4.04s). The unchanged +normalized BioFormats workspace test that failed without the registration binding +now passes. Actual FileManager pickle handoff is separately tested by the worker; +this does not establish a ZMQ or Java handoff. Original failures remain retained. +The first combined parent collection was killed at 520.27 MiB because optional +pytest plugins were not disabled; corrected source-qualified bounded shards use +the original provider-free plugin policy, with no input/assertion weakening. + +The original packaged structural guard at agent-comms 3b03785f passes on 5ce9e3087 +against main74eac059 for openhcs, benchmark and scripts: zero positive deltas. +MicroscopeHandler excess -13, source projector -10, generator -30; no guard +exceptions, scope moves, documentation compression or constant substitutions. +These checks do not certify full-context NRA/R1 or all ZIP refactors. + +Installed entrypoint evidence +----------------------------- + +Built OpenHCS0.8.7 and PolyStore0.3.0 paired wheels without isolation/downloads, +then installed them in an owned42 MiB candidate environment using explicit local +wheels, no dependencies and no index. Existing third-party dependencies are +read-only backing. Exact imported package files reside in the candidate. +OpenHCS wheel SHA25696a5ec632c679921e67825b88b477e8692d233c0e80c89633511a20ef76f5040; +PolyStore SHA25601b18c001f2b04f5b18f3e08a5f9c0a21a03059b52253712634e43f160024a00. +This is an installed pair, not a fresh public-registry resolver claim. + +The real installed persistent MCP dev-client process370288, startup +1790822619.1630557, reports healthy packaged resources56, unchanged source and +no restart requirement. Its environment deliberately sets the generic dependency +filename to generic-private.json while leaving the application default independent. +Read-only reopening of the preserved ImageXpress and Opera generated workspaces +finds their OpenHCS metadata and loads exact matching bounded4x4 uint16 samples. + +The same MCP process generated a new raw ImageXpress1x1 64x64 plate, four cells, +seed7, zero overlap and stage error. Source artifact-plan inspection of a complete +PipelineDocument with zero processing steps initialized the actual virtual +workspace, registered its backend, and returned one source mapping without errors. +This is an initialization/compile probe: zero execution axes/steps is deliberate, +not a processing or biological pass. Subsequent MCP sample through the persisted +virtual path loads the same exact4x4 pixels; reopening detects OpenHCS data, +one A01 well/image, grid1x1, and no preparation requirement/errors/warnings. +No generic-private.json companion was created. No arrays were analyzed outside MCP. + +The installed raw Opera1x1 generator itself succeeds, but returns unavailable-grid +warning. Actual initialization/compile then fails OperaPhenixXmlContentError: +Could not determine grid size from XML data. It is retained as a new explicit +issue172 follow-up, assigned to Averroes, not disguised by generated OpenHCS +metadata or altered inputs. Previously persisted Opera also exposes A01 metadata +versus R01C01 filename well identity; do not claim complete Opera normalization. + +Final same-process health succeeds. Normal quit is terminal with aggregate CLI +exit2 from retained invalid-command and structured Opera errors; it is not a +server crash. Independently verified MCP/client/recorder PIDs are gone. The native +recorder preserves every original request/result and timing. Owned scratch has +no active scientific runtime/viewer. No original image, held-out data, frozen +analysis, desktop or shared installed environment was changed. + +Remaining work and evidence +--------------------------- + +Ship the locally/source/installed validated namespace and ImageXpress checkpoint +without hosted-CI waiting. Keep issue172 open for Opera and real Java/CZI/native +execution acceptance. Full R1/ZIP refactors and scientific accuracy remain open. +No publication claim: the paired PolyStore public resolver route is separate. + +Durable parent ledger: +/home/ts/wt/openhcs-issue-batch-20260929/namespace-installed-20261001/ +contains local wheels/build logs, original guard JSON, complete MCP stdin/stdout/ +timing, fixtures and initialization_probe.py. Earlier source XML/logs and failed +predecessors remain in their original ledger directories. Current installed +candidate: /home/ts/wt/openhcs-namespace-installed-20261001/.venv. From ad63a817358516923b5f6cd7745e90c802d5aab2 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Wed, 30 Sep 2026 23:24:47 -0400 Subject: [PATCH 19/19] Pin normally merged PolyStore input and metadata owners --- external/PolyStore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/PolyStore b/external/PolyStore index 91fd7e854c..84f322e468 160000 --- a/external/PolyStore +++ b/external/PolyStore @@ -1 +1 @@ -Subproject commit 91fd7e854ca760a16e7472d8d558b4012e89b6ba +Subproject commit 84f322e46871de5ed47e7fd20976ad03e533c1c4