From 2d1b224126c94f8d2aad7fb54ed1463b0ce73cd3 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 5 Oct 2026 11:10:30 -0400 Subject: [PATCH 1/3] docs: checkpoint typed centroid and diagnostic-image authoring scope --- .../typed-centroid-authoring-20261005.rst | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 docs/validation/typed-centroid-authoring-20261005.rst diff --git a/docs/validation/typed-centroid-authoring-20261005.rst b/docs/validation/typed-centroid-authoring-20261005.rst new file mode 100644 index 0000000000..6c0624d9f4 --- /dev/null +++ b/docs/validation/typed-centroid-authoring-20261005.rst @@ -0,0 +1,49 @@ +Typed centroid and multi-image authoring checkpoint +================================================== + +Owner and scope +--------------- + +Singer owns this documentation-only change. Planck explicitly released the +callable-artifact authoring and custom-function reference documentation; Planck +retains #748 geometry and its frozen engineering fixtures. No production, +installed package, live author, or scientific input is changed here. + +The canonical owner is ``docs/source/development/callable_artifact_authoring.rst``. +The custom-function skill reference routes to that manifest-declared document; +``build_mcp_knowledge_assets.py`` copies both declared sources into package output. +It does not convert this RST into a second Markdown artifact manual. + +Determining source contracts +---------------------------- + +``PointROIOptions`` in ``processing/materialization/options.py`` selects nominal +runtime measurement features. The registered ``_write_point_roi_zip`` writer in +``processing/materialization/core.py`` requires a contextualized measurement +table, object subject, owned coordinate fields, and exact source provenance. +It retains fractional Z through ``ROIFractionalZ`` and binds the archive through +``ROIArchiveSourceMetadata``. There is no new Points artifact declaration. + +The read-only engineering494 ``point_volume_fixture_494.py`` is pinned at SHA256 +``c883251abc009e9d2c7b82a3dde3b4fe4a66529b6c8117b6bf1bfd887fdc2935``. +Its typed feature owner, subject relation, and CSV plus PointROIOptions supply +the existing declaration example, not an assay algorithm or recommended settings. + +``CallableContract.__post_init__`` separates consecutive main-flow Image specs +into canonical return specs; ``resolve_returned_output`` expects those images +in one named ``AlignedImageStack`` return slot. Each remaining typed artifact +has one trailing slot. A flat tuple of diagnostic Images is not this ABI. +``pack_aligned_image_outputs`` and ``AlignedImageSliceContext`` own its packing. + +Review and readiness +-------------------- + +Read current skill-creator, NRA, authoritative refactor-audit archive, pattern +README and boundary patterns, Diataxis and use-openhcs. Relevant BOUND-2/4/8 +risks are hand-decoding rows, flattening nominal output families, and inventing +an image/CSV substitute for the materialization contract. Existing owners remain +unchanged; this is not a structural refactor or global NRA proof. + +Source reflection, executable declaration/ABI checks and real knowledge/package +projection follow the coherent documentation edit. Native #748 geometry/reopen +qualification remains with Planck/Dewey and is not claimed by these source checks. From 22cc05cb54689d2ee33173721933d82c4559f0b8 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 5 Oct 2026 11:13:58 -0400 Subject: [PATCH 2/3] docs: declare centroid Points and pack diagnostic image returns on existing owners --- .../callable_artifact_authoring.rst | 154 +++++++++++++++++- .../references/custom-function-authoring.md | 6 + .../agent/test_callable_artifact_reference.py | 98 +++++++++++ 3 files changed, 254 insertions(+), 4 deletions(-) diff --git a/docs/source/development/callable_artifact_authoring.rst b/docs/source/development/callable_artifact_authoring.rst index 431e72d622..46d45ede42 100644 --- a/docs/source/development/callable_artifact_authoring.rst +++ b/docs/source/development/callable_artifact_authoring.rst @@ -79,8 +79,9 @@ runtime loader or default value for it. Image outputs that retain the current stack's axes should use ``MainFlowStackOutputSpec.output(name, ImageArtifactType, ...)``. The compiler -binds its lineage to the current image input. Declare the image before any -trailing table outputs so the returned image participates in main flow. +binds their lineage to the current image input. Declare main-flow images before +trailing typed artifacts; multiple images share one aligned canonical return +slot, as described under **Return diagnostic images without flattening the ABI**. For a cropped subset, return ``SelectedPlaneImageOutput(array, source_indices)`` in that image slot; the source indices must match the array's leading axis. It is an array-compatible runtime payload: ``numpy.asarray(result)`` exposes @@ -235,14 +236,13 @@ normal plate compilation/execution supplies image-source and runtime context. :name: callable-artifact-reference-check from openhcs.core.callable_contract import CallableContract - from openhcs.core.runtime_output_matching import RuntimeReturnedOutputMatcher from openhcs.core.steps import FunctionStep fixture = np.zeros((8, 8), dtype=np.uint16) fixture[2:6, 3:7] = 1 returned = inspect_label_fixture(fixture) contract = CallableContract.from_callable(inspect_label_fixture) - matched = RuntimeReturnedOutputMatcher(contract, returned).resolve() + matched = contract.resolve_returned_output(returned) assert np.array_equal(matched[FIXTURE_IMAGE.ref()], fixture) assert matched[FIXTURE_LABELS.ref()].dtype == np.int32 assert matched[FIXTURE_ROWS.ref()].row_mappings() == ( @@ -267,6 +267,152 @@ import, so its dataclass schema is valid in the custom execution namespace as well as in an ordinary module. Persist the source when it must survive a new process; session-only registration does not make a spawned worker import it. +Materialize typed 3D centres as feature-bearing Points +---------------------------------------------------- + +Points are a materialization of ``MeasurementsArtifactType``, not a missing +``PointsArtifactType`` or a centre-voxel image. Attach ``PointROIOptions`` to the +same object-measurement output as ``CsvOptions``. The existing writer projects +the contextualized table into source-bearing point ROIs; reopening that archive +uses the native Points route and retains row features, including object identity. +A CSV alone is not that native geometry declaration. + +This declaration block is for an operation that already produces labels and +3D centre rows. It specifies their ABI, not how to detect or choose a centre: + +.. code-block:: python + :name: callable-artifact-points-reference + + from dataclasses import dataclass + + from openhcs.core.artifacts import ( + ImageArtifactType, MainFlowStackOutputSpec, MeasurementsArtifactType, + ObjectLabelsArtifactType, ObjectMeasurementSubjectRelation, + ) + from openhcs.core.measurement_row_materialization import ( + DataclassMeasurementColumnarRows, + ) + from openhcs.core.runtime_measurements import ( + ObjectCoreMeasurementFeature, RuntimeMeasurementFeatureOwner, + ) + from openhcs.processing.materialization import ( + CsvOptions, MaterializationSpec, PointROIOptions, + ) + + class CentreFeatureOwner(RuntimeMeasurementFeatureOwner): + @classmethod + def owns_measurement_feature_name(cls, feature_name: str) -> bool: + return any(feature.feature_name == feature_name + for feature in ObjectCoreMeasurementFeature) + + @classmethod + def owns_primary_measurement_feature_name(cls, feature_name: str) -> bool: + return cls.owns_measurement_feature_name(feature_name) + + @dataclass(frozen=True) + class CentreRow: + object_label: int + center_z: float + center_y: float + center_x: float + + CENTRE_IMAGE = MainFlowStackOutputSpec.output("centre_image", ImageArtifactType) + CENTRE_LABELS = MainFlowStackOutputSpec.output( + "centre_labels", ObjectLabelsArtifactType, + ) + CENTRE_ROWS = MainFlowStackOutputSpec.output( + "centre_rows", MeasurementsArtifactType, + measurement_feature_owner=CentreFeatureOwner, + relations=(ObjectMeasurementSubjectRelation( + source=CENTRE_LABELS.ref(), id_field="object_label", + ),), + materialization=MaterializationSpec( + CsvOptions(), + PointROIOptions( + z_feature=ObjectCoreMeasurementFeature.CENTER_Z, + y_feature=ObjectCoreMeasurementFeature.CENTER_Y, + x_feature=ObjectCoreMeasurementFeature.CENTER_X, + ), + ), + ) + +On the existing centre-producing callable, declare +``@artifact_outputs(CENTRE_IMAGE, CENTRE_LABELS, CENTRE_ROWS)`` and return +``image, labels, DataclassMeasurementColumnarRows(rows, row_type=CentreRow)``. +Use the existing memory/processing decorators, as in the executable reference; +``PURE_3D`` is appropriate only for an actual volumetric calculation. Retain +the complete source bindings and compiled ZYX grouping: the decorator alone +does not establish axis order. The row's representative rule (peak, body centre +or another task-defined location) remains the analysis contract, not the writer. + +Coordinates are floating source-grid Z/Y/X locations, not calibrated world +coordinates and not a manually rescaled display array. Keep the full declared +source Z domain, including planes without objects, and its source paths, +spatial frame and voxel spacing through the compiled source/subject relations. +The writer rejects missing source provenance, missing/foreign coordinate fields, +duplicate object IDs and non-finite coordinates. It currently rejects an empty +Points archive; empty measurement rows retain their table schema but do not +establish successful point materialization. + +Fractional Z is geometry. An integer navigation slice, a rounded centre-voxel +marker image and a fractional point table describe different things. Do not +round the table to fit a viewer slice or infer its Z domain from occupied +labels/rounded markers. Compare the persisted table/archive and native Points +at matched raw coordinates, full Z extent and orthogonal views; inspect object +IDs/features and source calibration, not just point count. Verify that the +installed version exposes the required point producer/domain and viewer geometry +contracts before relying on automatic streaming or archive reopening. Declaration +and direct-call checks do not prove that live path. + +Return diagnostic images without flattening the ABI +-------------------------------------------------- + +``MainFlowStackOutputSpec`` declares source lineage; it does not give every +Image its own outer tuple slot. ``CallableContract`` groups the consecutive +leading main-flow Image declarations into one canonical return slot. Labels, +measurements and other remaining artifact declarations each have one trailing +slot in their exact order. Inspect ``canonical_return_output_specs`` and +``trailing_return_output_specs`` before extending a callable's returns. + +For example, to extend the executable 2D reference with an aligned diagnostic, +keep its labels/rows declarations and replace its image declarations/decorator: + +.. code-block:: python + :name: callable-artifact-diagnostic-reference + + from openhcs.core.aligned_image_payload import ( + AlignedImageSliceContext, pack_aligned_image_outputs, + ) + + FIXTURE_DIAGNOSTIC = MainFlowStackOutputSpec.output( + "fixture_diagnostic", ImageArtifactType, + ) + + @numpy(contract=ProcessingContract.PURE_2D) + @artifact_outputs( + FIXTURE_IMAGE, FIXTURE_DIAGNOSTIC, FIXTURE_LABELS, FIXTURE_ROWS, + ) + def inspect_label_fixture_with_diagnostic(image: np.ndarray): + image, labels, rows = inspect_label_fixture(image) + image_specs = (FIXTURE_IMAGE, FIXTURE_DIAGNOSTIC) + main_images = pack_aligned_image_outputs( + (image, (labels > 0).astype(np.uint8)), + slice_contexts=AlignedImageSliceContext.main_flow_for_artifact_specs( + image_specs, + ), + ) + return main_images, labels, rows + +The diagnostic here is only the synthetic fixture's foreground indicator. +The helper retains exact named slice contexts; it is not ``np.stack`` of +scientific Z planes and introduces no new return codec. With five leading +main-flow Images followed by three typed artifacts, the outer return has four +positions (one aligned image bundle plus three artifacts), not eight. A +``trailing return count`` error is repaired against this declared partition, +not by dropping useful diagnostics or weakening the matcher. Validate/compile +the corrected complete document and inspect its materialization/streaming plan; +this ABI repair does not itself settle a viewer or validate the analysis. + Consume a nominal artifact input ------------------------------- diff --git a/packaging/codex/openhcs/skills/use-openhcs/references/custom-function-authoring.md b/packaging/codex/openhcs/skills/use-openhcs/references/custom-function-authoring.md index 597b4555f5..6e7a7fd8ec 100644 --- a/packaging/codex/openhcs/skills/use-openhcs/references/custom-function-authoring.md +++ b/packaging/codex/openhcs/skills/use-openhcs/references/custom-function-authoring.md @@ -20,6 +20,12 @@ Record the missing operation and expected input axes, dtype, units, memory backend, outputs and empty-input behaviour before writing source. For centre detection, specify what defines a centre, coordinate order/origin, label identity and whether a count covers one plane or the whole volume. +For native feature-bearing 3-D Points, retrieve **Materialize typed 3D centres +as feature-bearing Points** in `openhcs_callable_artifact_authoring`: existing +`MeasurementsArtifactType` plus `PointROIOptions`, not a new Points artifact or +rounded centre-voxel image. When adding diagnostic Images, use that document's +**Return diagnostic images without flattening the ABI** section for the aligned +canonical image slot and trailing typed outputs. Before writing source, verify the intended imports, decorators, helper types and enum members against the actual public declarations for the installed diff --git a/tests/unit/agent/test_callable_artifact_reference.py b/tests/unit/agent/test_callable_artifact_reference.py index 965d416f87..b702f7ff79 100644 --- a/tests/unit/agent/test_callable_artifact_reference.py +++ b/tests/unit/agent/test_callable_artifact_reference.py @@ -59,6 +59,8 @@ DOCUMENT_ID = "openhcs_callable_artifact_authoring" REFERENCE_BLOCKS = ( "callable-artifact-reference", + "callable-artifact-points-reference", + "callable-artifact-diagnostic-reference", "callable-artifact-input-reference", "callable-artifact-plate-reference", ) @@ -173,6 +175,87 @@ def test_reference_executes_and_compiles_actual_function_step(reference_namespac assert not namespace["FixtureFeatureOwner"].owns_measurement_feature_name("unknown") +def test_diagnostic_reference_uses_one_canonical_slot_and_exact_trailing_artifacts( + reference_namespace, +): + namespace = reference_namespace + function = namespace["inspect_label_fixture_with_diagnostic"] + contract = CallableContract.from_callable(function) + pixels = np.zeros((8, 8), dtype=np.uint16) + pixels[2:6, 3:7] = 7 + returned = function(pixels) + assert len(returned) == 3 + assert contract.canonical_return_output_specs.names() == ( + "fixture_image", "fixture_diagnostic", + ) + assert contract.trailing_return_output_specs.names() == ( + "fixture_labels", "fixture_object_rows", + ) + matched = contract.resolve_returned_output(returned) + np.testing.assert_array_equal(matched[namespace["FIXTURE_IMAGE"].ref()], pixels) + np.testing.assert_array_equal( + matched[namespace["FIXTURE_DIAGNOSTIC"].ref()], pixels > 0, + ) + assert matched[namespace["FIXTURE_ROWS"].ref()].row_mappings() == ( + {"slice_index": 0, "object_label": 7, "pixel_count": 16}, + ) + with pytest.raises(ValueError, match="trailing return count"): + contract.resolve_returned_output((pixels, pixels > 0, returned[1], returned[2])) + + +def test_points_declaration_materializes_fractional_geometry_and_full_source_domain( + reference_namespace, tmp_path, +): + from openhcs.core.roi_point_metadata import ROIFractionalZ + from openhcs.core.roi_source_metadata import ROIArchiveSourceMetadata + from openhcs.core.runtime_measurements import MeasurementTable + from openhcs.core.source_image_provenance import SourceImageProvenancePlanes + from polystore.roi import PointShape + + namespace = reference_namespace + spec = namespace["CENTRE_ROWS"] + labels = namespace["CENTRE_LABELS"] + plan = ArtifactOutputPlan( + name=spec.name, path="/synthetic/centre_rows", artifact_type=spec.artifact_type, + relations=spec.relations, + ) + assert plan.object_subject_binding().source == labels.ref() + row = namespace["CentreRow"](7, 1.5, 1.25, 2.5) + carrier = namespace["DataclassMeasurementColumnarRows"]( + (row,), row_type=namespace["CentreRow"], + ) + # Direct materialization supplies the same nominal context normally owned + # by runtime contextualization, without inventing an archive/CSV writer. + table = MeasurementTable( + name=spec.name, rows=carrier, subject=plan.measurement_subject(), + measurement_feature_owner=namespace["CentreFeatureOwner"], + source_path="/synthetic/volume.tif", + source_image_provenance_planes=SourceImageProvenancePlanes.from_components( + paths=("/synthetic/volume.tif",) * 4, + component_metadata=tuple( + {"well": "A01", "site": 1, "channel": 1, "z_index": z, "timepoint": 1} + for z in range(4) + ), + ), + ) + materialize( + spec.materialization, data=table, path=str(tmp_path / "centres"), + filemanager=FileManager({"disk": DiskStorageBackend()}), + backends=["disk"], backend_kwargs={}, + ) + assert (tmp_path / "centres.csv").read_text().splitlines() == [ + "object_label,center_z,center_y,center_x", "7,1.5,1.25,2.5", + ] + rois = load_rois_from_zip(tmp_path / "centres_points.roi.zip") + assert rois[0].shapes == [PointShape(y=1.25, x=2.5)] + assert rois[0].metadata["object_label"] == 7 + assert ROIFractionalZ.decode(rois[0].metadata) == ROIFractionalZ(1.5) + metadata = ROIArchiveSourceMetadata.decode(rois) + domain = ROIFractionalZ.source_component_domain(rois, metadata) + assert tuple(plane["z_index"] for plane in domain) == (0, 1, 2, 3) + assert metadata.source_path == "/synthetic/volume.tif" + + def test_input_reference_compiles_nominal_binding_and_repairs_wrong_annotation( reference_namespace, ): @@ -502,3 +585,18 @@ def test_packaged_knowledge_projection_retains_executable_reference(tmp_path): source_path = "packaging/codex/openhcs/skills/use-openhcs/references/custom-function-authoring.md" assert (destination / source_path).read_bytes() == (ROOT / source_path).read_bytes() assert "Summarize declared measurements once per" in route.content + for section_id in ( + "materialize-typed-3d-centres-as-feature-bearing-points", + "return-diagnostic-images-without-flattening-the-abi", + ): + selected = service.get_document(KnowledgeBaseDocumentRequest.from_fields( + document_id=DOCUMENT_ID, section_id=section_id, max_chars=10_000, + )) + assert not selected.errors and not selected.truncated + assert selected.selected_section_id == section_id + original = KnowledgeBaseService(repo_root=ROOT).get_document( + KnowledgeBaseDocumentRequest.from_fields( + document_id=DOCUMENT_ID, section_id=section_id, max_chars=10_000, + ) + ) + assert selected.content == original.content From 735887b23a3bca2c0e338b391a968ddcd7ec4ed6 Mon Sep 17 00:00:00 2001 From: Tristan Simas Date: Mon, 5 Oct 2026 11:22:56 -0400 Subject: [PATCH 3/3] docs: qualify canonical Points and image-bundle examples through existing package owners --- .../callable_artifact_authoring.rst | 8 +-- .../mcp_knowledge_base_manifest.json | 4 +- .../typed-centroid-authoring-20261005.rst | 68 ++++++++++++++++++ .../typed-centroid-authoring-20261005.tar.gz | Bin 0 -> 34442 bytes .../agent/test_callable_artifact_reference.py | 12 ++-- 5 files changed, 83 insertions(+), 9 deletions(-) create mode 100644 docs/validation/typed-centroid-authoring-20261005.tar.gz diff --git a/docs/source/development/callable_artifact_authoring.rst b/docs/source/development/callable_artifact_authoring.rst index 46d45ede42..b928a6dc6e 100644 --- a/docs/source/development/callable_artifact_authoring.rst +++ b/docs/source/development/callable_artifact_authoring.rst @@ -268,7 +268,7 @@ well as in an ordinary module. Persist the source when it must survive a new process; session-only registration does not make a spawned worker import it. Materialize typed 3D centres as feature-bearing Points ----------------------------------------------------- +------------------------------------------------------------ Points are a materialization of ``MeasurementsArtifactType``, not a missing ``PointsArtifactType`` or a centre-voxel image. Attach ``PointROIOptions`` to the @@ -365,7 +365,7 @@ contracts before relying on automatic streaming or archive reopening. Declaratio and direct-call checks do not prove that live path. Return diagnostic images without flattening the ABI --------------------------------------------------- +------------------------------------------------------------ ``MainFlowStackOutputSpec`` declares source lineage; it does not give every Image its own outer tuple slot. ``CallableContract`` groups the consecutive @@ -414,7 +414,7 @@ the corrected complete document and inspect its materialization/streaming plan; this ABI repair does not itself settle a viewer or validate the analysis. Consume a nominal artifact input -------------------------------- +-------------------------------- An artifact's input ABI is not necessarily its raw output representation. ``ObjectLabelsArtifactType`` accepts integer labels from a producer, but supplies @@ -480,7 +480,7 @@ the corrected complete document through the ordinary route. This establishes technical input compatibility, not object identity or biological accuracy. Summarize declared measurements once per plate ---------------------------------------------- +---------------------------------------------- A terminal ``PLATE`` callable receives ``RuntimeArtifactBatch``, not an image or a directory of CSVs. The parent executes it once after the compiled axes diff --git a/docs/source/development/mcp_knowledge_base_manifest.json b/docs/source/development/mcp_knowledge_base_manifest.json index 249ca9afd6..03674c18d3 100644 --- a/docs/source/development/mcp_knowledge_base_manifest.json +++ b/docs/source/development/mcp_knowledge_base_manifest.json @@ -709,7 +709,7 @@ "document_id": "openhcs_callable_artifact_authoring", "section_count": 0, "source_path": "docs/source/development/callable_artifact_authoring.rst", - "summary": "Canonical callable and artifact authoring reference with executable nominal input annotations and artifact bindings, earliest compile-error repair, typed NumPy main flow, object labels, dataclass ColumnarRows, RuntimeMeasurementFeatureOwner, exact subject/source relations, CSV and ROI materialization, custom registration namespace boundaries, and a terminal PLATE summary using RuntimeArtifactBatch.", + "summary": "Canonical callable and artifact authoring reference with executable nominal input annotations and artifact bindings, earliest compile-error repair, aligned diagnostic-image returns, typed NumPy main flow, object labels, dataclass ColumnarRows, RuntimeMeasurementFeatureOwner, exact subject/source relations, CSV and fractional 3D Points materialization, custom registration namespace boundaries, and a terminal PLATE summary using RuntimeArtifactBatch.", "tags": [ "custom function artifacts", "callable artifact authoring", @@ -726,6 +726,8 @@ "ObjectMeasurementSubjectRelation", "materialization", "ROIOptions", + "PointROIOptions fractional centres", + "aligned diagnostic image returns", "CsvOptions", "exact imports decorators", "custom registration namespace" diff --git a/docs/validation/typed-centroid-authoring-20261005.rst b/docs/validation/typed-centroid-authoring-20261005.rst index 6c0624d9f4..8cfce6d6c5 100644 --- a/docs/validation/typed-centroid-authoring-20261005.rst +++ b/docs/validation/typed-centroid-authoring-20261005.rst @@ -47,3 +47,71 @@ unchanged; this is not a structural refactor or global NRA proof. Source reflection, executable declaration/ABI checks and real knowledge/package projection follow the coherent documentation edit. Native #748 geometry/reopen qualification remains with Planck/Dewey and is not claimed by these source checks. + +Implemented owner/consumer change +-------------------------------- + +The RST adds one declaration-only centre row/feature owner and measurement output +with CSV plus PointROIOptions; detection is deliberately left to the existing +analysis callable. The original engineering fixture is not edited or copied as +a new detector. The named subject binds the exact labels declaration. The generic +writer accepts this independently named CentreRow/CentreFeatureOwner declaration +without consumer edits, retaining fractional geometry, row features and the full +four-plane source domain. Missing source/field, duplicate-ID and empty-archive +guards remain unchanged. No Points artifact, schema mirror or custom codec is added. + +The existing executable image/labels/rows reference gains a diagnostic variant +using the original aligned image packer and named slice-context owner. Its two +Images share one canonical slot and labels/rows retain two trailing slots; the +original matcher rejects the flattened return. Five canonical Images plus three +trailing typed artifacts likewise means four outer return positions, not eight. +This identifies the reported return-count error as an ABI mismatch, not evidence +that diagnostics are unavailable or that a viewer geometry guard must change. + +One obsolete direct-check consumer of the deleted RuntimeReturnedOutputMatcher +is migrated to CallableContract.resolve_returned_output. One stale fixture cache +clearing call is deleted: resolved_callable_type_hints now owns fresh resolution, +not that removed cache. No compatibility facade or alternative matcher is added. +Two pre-existing short RST heading underlines are corrected so the existing +knowledge section parser ends each section at its actual boundary. The packaged +custom-function reference links these two sections once, with no duplicated ABI +example or new SKILL entrypoint. Manifest discovery metadata names this same +canonical document; the original package builder copies its exact bytes. + +Actual qualification and preserved failures +------------------------------------------ + +All controls use the existing run_installed_tests.py entrypoint, paired interpreter +and read-only qualified receiving14 runtime/dependencies. Six declaration/source +owners (artifacts, callable contract, runtime measurements, aligned image payload, +options and tabular CPP) byte-match current main. The backing writer includes +unmerged #748 PointROIOutput; native streaming/reopen is explicitly NOT inferred +from this disk/source contract proof. No install/build/catalogue/native/viewer or +provider request occurs. Current live authors and their instructions are untouched. + +Original controls01:13PASS/3FAIL,7.02s,377724KiB max RSS, swaps0. Failures were +the new fixture assuming .csv instead of CsvOptions' declared _details.csv, +the pre-existing removed cache API call, and an undersized new RST heading. +Original controls02:15PASS/1FAIL,7.11s,372296KiB max RSS, swaps0. All executable +ABI/materialization tests passed; section retrieval still included later content +because the pre-existing input/plate section underlines were too short. +Final affected controls03:3PASS,6.42s,364996KiB max RSS, swaps0. No larger bound, +parser patch or assertion weakening: correct canonical section boundaries now +retrieve both sections untruncated at10000chars; whole-document retrieval remains +untruncated at30000chars. All16 distinct original controls are qualified by02/03; +this is not reported as a single clean16-test final run. + +Real KnowledgeBaseService retrieval/search, exact RST/skill package projection, +isolated managed-skill sync and unchanged second sync pass. The original +skill-creator quick validator reports ``Skill is valid!``. No production changes, +scientific inputs/reference answers, settings recommendations, source calibration +invention or current-author coaching. git diff --check passes on this change; +eight unrelated foreign gitlinks and prior untracked evidence remain untouched. + +Raw numbered stdout/stderr and original generated centroid CSV/ROI archive are +preserved in the byte-exact qualification archive adjacent to this receipt. +Its15members compare byte-exact to source; archive SHA256 is +``9cc10ec8c226b8c4d79c599aa6d2f670de7de8ca7432d828c501914cc451c6c9``. +CI is deferred. Documentation/contract exposure is ready for review and future +ordinary package delivery; #748's native geometry/selected-source acceptance +remains the explicit independent scope boundary, not a documentation delivery hold. diff --git a/docs/validation/typed-centroid-authoring-20261005.tar.gz b/docs/validation/typed-centroid-authoring-20261005.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..f1b2a5ace410c6d13414ad887b048615e1ad6b4f GIT binary patch literal 34442 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 literal 0 HcmV?d00001 diff --git a/tests/unit/agent/test_callable_artifact_reference.py b/tests/unit/agent/test_callable_artifact_reference.py index b702f7ff79..ab292178bc 100644 --- a/tests/unit/agent/test_callable_artifact_reference.py +++ b/tests/unit/agent/test_callable_artifact_reference.py @@ -243,7 +243,11 @@ def test_points_declaration_materializes_fractional_geometry_and_full_source_dom filemanager=FileManager({"disk": DiskStorageBackend()}), backends=["disk"], backend_kwargs={}, ) - assert (tmp_path / "centres.csv").read_text().splitlines() == [ + csv_options = next( + option for option in spec.materialization.outputs + if isinstance(option, namespace["CsvOptions"]) + ) + assert (tmp_path / ("centres" + csv_options.filename_suffix)).read_text().splitlines() == [ "object_label,center_z,center_y,center_x", "7,1.5,1.25,2.5", ] rois = load_rois_from_zip(tmp_path / "centres_points.roi.zip") @@ -285,13 +289,10 @@ def test_input_reference_compiles_nominal_binding_and_repairs_wrong_annotation( original_annotation = raw.__annotations__["objects"] try: raw.__annotations__["objects"] = np.ndarray - from openhcs.core.pipeline.function_contracts import resolved_callable_type_hints - resolved_callable_type_hints.cache_clear() with pytest.raises(TypeError, match="does not accept object_labels artifact payloads"): compile_function_pattern(function, {plan.ref(): plan}, {}) finally: raw.__annotations__["objects"] = original_annotation - resolved_callable_type_hints.cache_clear() compile_function_pattern(function, {plan.ref(): plan}, {}) @@ -573,6 +574,9 @@ def test_packaged_knowledge_projection_retains_executable_reference(tmp_path): ) assert "def inspect_label_fixture(" in document.content assert "from openhcs.core.memory import numpy" in document.content + for query in ("PointROIOptions fractional centres", "aligned diagnostic image returns"): + hits = service.search(KnowledgeBaseSearchRequest(query=query, limit=10)) + assert DOCUMENT_ID in {hit.document.document_id for hit in hits.hits} for block in REFERENCE_BLOCKS: for line in _reference_block(block).splitlines(): if line.strip():