diff --git a/docs/validation/custom-registration-native-20261004.tar.gz b/docs/validation/custom-registration-native-20261004.tar.gz new file mode 100644 index 0000000000..05d99f0bf4 Binary files /dev/null and b/docs/validation/custom-registration-native-20261004.tar.gz differ diff --git a/docs/validation/custom-registration-observation-20261003.rst b/docs/validation/custom-registration-observation-20261003.rst new file mode 100644 index 0000000000..d1f3bf2bc7 --- /dev/null +++ b/docs/validation/custom-registration-observation-20261003.rst @@ -0,0 +1,281 @@ +Custom registration observation checkpoint +========================================== + +Singer receiving owner; initial production qualified at 5c4b5adf0 (base mainb551a6ff3). +The narrow typed-renderer correction below is qualified at a799352b9; current +whole candidate normally includes main4f9e10820 through merge45bb6fc90. +Root394 head7d7e1249 was checked for crossing before edits. It owns the shared +execution server/compiler/producer family; no change to those files is proposed. +561 owns resource admission. Original562 source/archive remains published and +its finished checkout is reused on a new branch; foreign gitlinks/journals stay. + +Original negative +----------------- + +Retained original public reply: +/home/ts/wt/openhcs-issue-batch-20260929/next-h002-fourth88-after552-20261003/ +H002_FOURTH88/author-workspace/output/evidence/openhcs_register_custom_function-01.json. +Its native6020 log under output/runtime/scratch/data/openhcs/logs reports no +registration traceback/cause. Nothing here determines whether source/registry +mutation occurred. No original source request, status mutation, runtime launch +or UNKNOWN replay was sent. Scientific source/settings are not copied publicly. + +Required owner relationship +--------------------------- + +Original CustomFunctionManager owns source preparation, persistence and registry +publication under CustomFunctionRuntimeRegistry.lifecycle. Its declaration/source +identity and persisted snapshots already exist. FunctionCatalogService projects +the native registration result; endpoint service validates its destination and +may project ephemeral source locally. ZMQ control/client and generated MCP +capability carry those results. The response currently loses native causes and +the generic exception envelope omits a typed observation handle. + +Extend these original owners, not a second operation store. Read-only status +must compare the same source revision and process incarnation, derive current +publication/persistence from canonical owners, and never infer failure or safe +retry from absent evidence. A native reply followed by local projection failure +must preserve that confirmed native result rather than relabel it unknown. + +Working source checkpoint +------------------------- + +Registration errors now retain the original exception type/message and a typed +CustomFunctionRegistrationHandle. Native success followed by an ephemeral local +projection failure retains the confirmed native count/owner/receipt instead of +reclassifying it as an uncertain native mutation. The old uncertainty exception +and its cause-erasing projection are deleted, including their test consumers. + +FunctionCatalogOperationHandle owns the existing shared connection/incarnation +check; preparation and registration handles inherit it. The registration member +owns named-source/store validation. CustomFunctionSource now owns the one byte +digest algorithm used by manager preparation/persistence and observation. +CustomFunctionRuntimeRegistry observes its original declaration/export owners; +CustomFunctionManager.require_source verifies existing saved bytes. Neither +observer loads/evaluates source, prepares a catalog, or adds an operation ledger. + +The read-only openhcs_get_custom_function_registration_status capability is a +normal declaration registered through the existing capability/control families. +Its arguments are the returned handle's declared fields, not a new nested +``handle`` parameter. Missing evidence means not_observed, never proof that the +original request did not mutate anything or permission to replay it. Process +incarnation, content digest and persistence store are checked on the original +owners. No transport deadline, retry policy, startup or execution server changed. + +Complete production changes: agent/capabilities.py, agent/dto/functions.py, +agent/services/{endpoint_function_catalog_service,function_catalog_service}.py, +mcp/dev_client_renderers/knowledge.py, processing/custom_functions/{manager, +runtime_registry,source_namespace}.py, runtime/{zmq_control,zmq_execution_client}.py. +Root394 public coordination: issuecomment-5974588121; current eac856066 remains +separate execution/compiler/materialization ownership. No Root worktree edit. + +Source verification and original failures +---------------------------------------- + +Original controls01: 61 passed, 3 failed, 68 deselected; 10.392 seconds whole +scope, 221.4 MiB, zero swap. All three generated-MCP failures stopped before the +operation: preserved foreign ZMQRuntime lacked EndpointStartupStatus.callback_scope. +No progress bypass or dependency edit was made. Controls02 preserves a separate +pre-bootstrap harness error (systemd working directory was omitted). + +The parent identified the existing read-only paired backing: +/home/ts/wt/openhcs-paired-raw-installed-parent-20261001/.venv/lib/python3.12/site-packages. +Actual imported zmqruntime/startup.py SHA256: +55118bc44633926434f0e0b057c167dc2948f2a632a902b5bdb60dcb92731d69. +Required class/API: zmqruntime.startup.EndpointStartupStatus.callback_scope. +Existing zmqruntime 0.3.1 direct_url records wheel SHA256 +b431eac28214dad42df61b44be25b51d30b8ff11181383e614607214adcd735f. +This is observed backing provenance, not proof of the unavailable main gitlink +f7b80c871eb4773757368bb21f108920bf461881. The original source bootstrap imports +OpenHCS from this checkout, then selects that original package before consumers; +it restores other source priorities and logs exact imported identities. + +Controls03--05 preserve the real follow-up source defect: a string store was +passed to the manager's Path contract. The handle now supplies its validated Path +to both endpoint and native consumers. No request/expected result was weakened. + +Controls06: 6 passed, 78 deselected, two existing pytest config warnings; 8.646 +seconds whole scope, 206.4 MiB, zero swap/OOM. The real build_server/call_tool +boundary preserves a controlled native ValueError, returns its handle, invokes +the declared status tool and observes no current evidence with exactly ONE +registration dispatch. The original native control router/service are exercised +through an in-process fixture client, not an actual socket/native process. +Persisted/ephemeral controls exercise real manager publication, exact source +proof, stale-incarnation rejection, changed digest and persisted-only state. +Fail guards prevent source evaluation/lazy loading during observation. +No repeated unchanged 61-control batch was required. + +All source scopes use the original common slice, CPU1, 512 MiB, Swap0 and 60s. +No viewer/native/provider launch, install/download or frozen science change. + +AST and ratchet boundaries +-------------------------- + +Latest NRA and authoritative refactor-audit archive/catalog were read. Applicable +patterns: BOUND-4 (flattened cause), IDEN-1 (receipt failure conflated with native +outcome), IDEN-5 (parallel status store), IDEN-8 (incarnation identity), IMPL-12 +(copied mechanisms). Existing ABC/service, registry, source and capability owners +carry the implementation; there is no consumer kind/type roster or new ledger. + +Original Package parser before01 hit the fixed 512 MiB limit and is retained. +Serial before02/source-finish01/02 initially omitted root-level files because the +original Repository.python_files accepts directory scopes. Those are explicitly +NOT complete dependency censuses. The corrected thin Repository subclass enables +single-file scopes through the same Package parser, without a copied scanner. +family-complete01 at 58e327ab3: OpenHCS 704, ZMQRuntime 32, python-introspect 12, +metaclass-registry 6 modules; zero parse omissions; 8.361s/192.2 MiB/Swap0. +Full selected module ASTs retain imports, bases, declarations, writes, decisions +and consumers. Dependency census describes the preserved foreign checkout +revisions, NOT the paired installed backing selected for the successful controls. +The paired backing's actual startup source/API/import identity is separately +verified above. Dynamic declaration registration is semantically traced through +AutoRegisterMeta; AST is not a behavioral or global NRA R1 proof. + +Original unmodified R0 at 5c4b5adf0 over ALL ten changed production files has zero +type/string switches/arms, chain growth, foreign absence probes and codec classes. +It is NOT an all-zero ratchet: literal_key_get +2, none_identity +5, +isinstance_call +1, broad_except +3, classes +7, enum +1, code lines +202. +The initial foreign absence +3 was corrected at the actual handle owner. The +remaining cause/error boundary and renderer metrics remain visible for review; +no detector copy, omitted file, bound increase or positive-delta waiver is claimed. +Raw R0 JSON and every original failure are archived byte-exactly. + +Archive: docs/validation/custom-registration-observation-20261003.tar.gz, +3,570,775 bytes, SHA256 +442b8996dc579ba7bda521059ea33acac84acfc8351e16d6b51e5b6fda0f1ee0. +Includes exact qualified product/test sources, original bootstrap, original +censuses/R0 JSON and controls01--06 stdout/stderr. Loose originals remain retained; +pytest whitespace is evidence-only, not rewritten to manufacture diff cleanliness. + +Focused native acceptance received +--------------------------------- + +The separate installed TCP receiving case now passes: one registration, exact +returned observation handle, original native read-only publication/persistence +proof and successful typed close. Full evidence is appended below. Original +recorded shell terminal1 remains separate, not hidden or relabelled0. No +scientific gain, original H002 UNKNOWN recovery, all-zero R0 or global R1 closure +is claimed. No specific public operation remains failing in this focused case. + +Parent BOUND-2 correction: one typed registration renderer +-------------------------------------------------------- + +Candidate2b39117cd was superseded BEFORE build. Dewey explicitly confirmed no +ADMIN567, materialization, build, client or native launch had started; its +whole-main10 target is separate and contains no567. No source borrower remained. +Foreign gitlinks, original uncertain input, failed controls and first archive +are unchanged. Root compiler/materialization ownership is not crossed. + +CustomFunctionRegistrationRenderer now inherits existing McpDevTypedOutputRenderer. +The ancestor owns McpDevToolBatchResponse.for_rendering, declared DTO decoding, +renderer discovery and McpDiagnosticRenderer.typed_error_lines. The member's +render_payload hook formats CustomFunctionRegistrationResult and its declared +FunctionCatalogEntry/handle fields. All ten raw get reads in this ONE renderer, +its raw payload/error projection and sibling raw function formatting calls are +deleted. No sibling knowledge renderer, codec, response or registry is changed. +The original explicit persisted-is-False lifetime guard is retained on the DTO. + +RegisterCustomFunctionCommandSpec is an original SingleToolCommandSpec, so the +ancestor's selected decoded payload is the registration contract. The existing +payload_for API takes AgentCapabilitySpec, not a DTO class; focused controls +independently verify payload_for(agent_capabilities.register_custom_function) +retains the complete original CustomFunctionRegistrationResult. Production +does not copy the ancestor's framing/diagnostic procedure into a leaf. + +Final renderer02: 3 PASS, 27 deselected, two original pytest configuration +warnings. Covers uncertain count0/cause/handle, confirmed-native count1 with +local projection failure, and success/function/path/lifetime/next commands. +Typed errors render once; complete serialized observation handle is retained; +zero/absence remains explicitly NOT proof of no mutation. The original renderer01 +3-PASS batch and first audit remain historical evidence, not overwritten. +Final combined controls/AST/R0 scope: terminal0, 6.719s, 160.4 MiB, Swap0/OOM0, +original common slice, CPU1/512MiB/60s. No application/native process launched. + +Original Package parser reread the changed MCP consumer family: 33 modules, +zero parse omissions; retained full ASTs of relevant rendering/batch/command +modules at a799352b9f229182de888d3d996ba2e6083f4155. The original full704/32/12/6 +census above remains the broader family evidence; no repeated global R1 claim. +Original unmodified debt_census.py SHA256: +fbe4651372d4d79963075d7fb6ba6dedf90d5e88e14eee07f845c5d836974e35. +Correction R0 versus2b391: literal_key_get -10, none_identity -1, all other +counts zero, including foreign absence probes. Initial6716 audit's +1 probe +is retained; restoring the original explicit non-persistence guard removed it. +Whole567 R0 versus merged main4f9 over ALL ten production files now has gets -8, +None checks +4, isinstance +1, broad except +3, classes +7, enum +1, lines +202; +switches/arms/chains/absence probes/codecs zero. This is NOT all-zero R0. + +Narrow byte-exact source/raw-log/AST/R0 archive: +docs/validation/custom-registration-renderer-20261003.tar.gz, 890,567 bytes; +SHA256 35f16c072659d300fcace7fa89c694ae07558f8ea908714de1a21f77b6b8b503. +It includes final a799 renderer and original unchanged owner sources, both +control/audit passes and their original raw outputs. Original larger archive +is still historical5c4 evidence. Production/tests diff-check passes; authentic +raw pytest whitespace is retained in archives, not rewritten. + +Those historical receiving prerequisites were satisfied: Dewey qualified the +whole8513 candidate for BOTH client/native and delivered the original one-case +journey after prior exact94 closure. No old package, selective overlay or source +fixture substitutes for the native evidence below. #541 remains separate typed +pixel/unit ownership with Root shared-frame dependency. + +Actual whole-package TCP receiving, 2026-10-04 +-------------------------------------------- + +Persistent root: /home/ts/wt/openhcs-issue-batch-20260929/engineering567/ +public94-attempt01. Original LIVE-RECEIVING01.rst, raw stdin/stdout/timing and +native log/startup journal are retained there. Qualified wheel/source8513 is +unchanged by the receiving-only0aaf checkpoint or this documentation update. +Wheel SHA2569fde2794d19f5d57837a67c4672e1f1053eed3c95c71ebae7f37c160655756de; +target is headless01-admission/ADMIN_PACKAGE567/author-workspace/output/target01. +Original whole verification815 tracked payloads/90 resources/13 skill files/ +984 RECORD entries and nine installed controls remains the package proof. + +ONE original normal retained CLI PTY15414 was physically owned by Dewey; +Singer's cross-thread read-only PTY lookup failed before sending any input. +No second client was started. Its MCP177709 health reports exact target01, +errors=[], stale_source_paths=[],86 packaged resources ready. Both original +MCP/native process command lines activate that same whole target through the +original paired interpreter. Native188784/create1791080888.84 owns6012TCP/7012. +Original bootstrap observation returns ready=true; explicit catalogue prepare +and its same-handle status return ready before source submission. + +The154-byte engineering declaration was submitted exactly ONCE, persist=true, +using its actual native launch-plan store. Original registration returns +errors=[], registered_count=1, persisted=true, one original source-file path, +canonical function_id openhcs:registration_observation_probe_567 and the typed +observation_handle. The direct-six-field read-only status was submitted once; +errors=[], outcome=registered, returned handle exactly equals registration's +handle, and published_sources contains precisely the persisted_source identity. +Exact name/digest/connection/process/store/persist comparisons pass, not just +counts. SHA2564398ac619555f8a1c0459dbbcb1a6440862fab50602cea1485c756c6ebd1e828 +matches frozen source bytes; cmp against the native saved source is terminal0. +PUBLIC-RELATIONS01.stdout and PUBLIC-CLOSURE01.stdout retain true assertions. + +Original exact close returns request_attempted/acknowledged/process_exited/ +endpoint_terminated/succeeded all true, errors=[], original process_identity. +At2026-10-04T02:36:04Z independent ps finds native188784 and MCP177709 absent, +ss finds no6012/7012 listeners. Original script ends at22:35:02-04:00 with +COMMAND_EXIT_CODE=1. Preserve actual terminal1; numerical native exit code and +cause of the nonzero recorded-shell disposition are not inferred. Every one +of the eight public operation batches has errors=[], mcp_error=false and +payload errors=[]; the terminal1 is not falsely reported as a failed public +registration/status/close. No operation here is UNKNOWN. + +Original uncapped scope787d442349b8407891ced5b3a15b8e84 belongs to the common +openhcs-blindsol3phase03.slice, CPU1, MemoryMax=infinity/MemorySwapMax=infinity. +This uses merged577's original operations, not a second launcher or cap override. +The receiving runtime, including original warmup cache, occupies21MiB; raw +input/output/timing/native log total44,881 bytes. No viewer, scientific image, +pipeline execution, provider, download, new environment/build/install/worktree, +scientific hot update, original uncertain request replay or fault injection. +Focused native acceptance is complete, not biological or global-audit closure. + +Byte-exact native receiving archive: +docs/validation/custom-registration-native-20261004.tar.gz,31,047 bytes, +SHA2568d67365976e12d3c896f8431f591e97dfc468d2d7d26052d51fadc6a38db6b77. +Contains original input/output/timing, native log/startup journal, frozen and +persisted declaration, original uncapped resource readback and receiving +projections/command evidence. Archived stdout SHA256 equals original +ecef55d8af81771c92ffb92b2fcc247f2a6f11e7a6b0d3d09220e400c2593f42. +Original61PASS/3FAIL, earlier process/lookup negatives and old uncertain sources +remain separately retained; no original failure was rewritten or replayed. diff --git a/docs/validation/custom-registration-observation-20261003.tar.gz b/docs/validation/custom-registration-observation-20261003.tar.gz new file mode 100644 index 0000000000..553d6e7749 Binary files /dev/null and b/docs/validation/custom-registration-observation-20261003.tar.gz differ diff --git a/docs/validation/custom-registration-renderer-20261003.tar.gz b/docs/validation/custom-registration-renderer-20261003.tar.gz new file mode 100644 index 0000000000..ead3b16119 Binary files /dev/null and b/docs/validation/custom-registration-renderer-20261003.tar.gz differ diff --git a/openhcs/agent/capabilities.py b/openhcs/agent/capabilities.py index 237a014b8b..d018f9dee2 100644 --- a/openhcs/agent/capabilities.py +++ b/openhcs/agent/capabilities.py @@ -78,6 +78,8 @@ from openhcs.agent.dto.functions import ( CustomFunctionRegistrationRequest, CustomFunctionRegistrationResult, + CustomFunctionRegistrationHandle, + CustomFunctionRegistrationObservation, FunctionCatalogPage, FunctionCatalogPreparationHandle, FunctionCatalogPreparationState, @@ -2035,6 +2037,24 @@ class RegisterCustomFunctionCapability(FunctionCatalogCapability): ) +class ObserveCustomFunctionRegistrationCapability(FunctionCatalogCapability): + name = "openhcs_get_custom_function_registration_status" + cli_command = "get-custom-function-registration-status" + kind = CapabilityKind.TOOL + title = "Observe custom registration source" + description = "Read exact publication/persistence proofs through the original native owners using observation_handle. Does not evaluate/load source or prepare a catalog. Missing evidence stays not_observed; it never authorizes registration replay or proves original mutation did not occur." + service = "function_catalog" + exposition = FunctionCatalogCapability.exposition.refine( + visibility=CapabilityVisibility.STANDARD, + ) + input_contract = CustomFunctionRegistrationHandle + output_contract = CustomFunctionRegistrationObservation + request_invocation = AgentDataclassRequestServiceInvocation( + service=lambda context: context.function_catalog, + method=lambda service, request: service.observe_custom_function_registration(request), + ) + + class GetAuthoringContextCapability(KnowledgeCapability): name = "openhcs_get_authoring_context" cli_command = "authoring-context" diff --git a/openhcs/agent/dto/functions.py b/openhcs/agent/dto/functions.py index a45354e755..33d3f167fc 100644 --- a/openhcs/agent/dto/functions.py +++ b/openhcs/agent/dto/functions.py @@ -17,6 +17,7 @@ from openhcs.agent.dto.common import ( SCHEMA_VERSION, + AgentError, AgentDataclassCliRequest, AgentResultEnvelope, ) @@ -25,6 +26,7 @@ from openhcs.agent.path_policy import AgentPathPolicy from openhcs.core.artifacts import ArtifactViewerStreaming from openhcs.core.function_reference import FunctionReference +from openhcs.processing.custom_functions.source_namespace import CustomFunctionSource DEFAULT_FUNCTION_DETAIL_DOC_CHARS = 6_000 @@ -37,6 +39,7 @@ class FunctionCatalogControlMessageType(str, Enum): READ_DETAIL = "openhcs_function_detail_read" READ_REFERENCE = "openhcs_function_reference_read" REGISTER_CUSTOM = "openhcs_custom_function_register" + OBSERVE_CUSTOM_REGISTRATION = "openhcs_custom_function_registration_observe" CUSTOM_REGISTRATION_DESTINATION = "openhcs_custom_function_registration_destination" START_PREPARATION = "openhcs_function_catalog_prepare" READ_PREPARATION = "openhcs_function_catalog_preparation_status" @@ -108,8 +111,8 @@ class FunctionSearchRequest(FunctionCatalogControlRequestABC): @dataclass(frozen=True, slots=True) -class FunctionCatalogPreparationHandle(AgentDataclassCliRequest): - """The one preparation owner in this exact execution-server incarnation.""" +class FunctionCatalogOperationHandle(AgentDataclassCliRequest): + """Incarnation authority shared by catalog lifecycle operations.""" connection: ExecutionConnectionSpec server_identity: ProcessIdentity @@ -117,10 +120,74 @@ class FunctionCatalogPreparationHandle(AgentDataclassCliRequest): def require_current_owner(self) -> None: if self.server_identity != ProcessIdentity.current(): raise RuntimeError( - "Function catalog preparation owner changed; handle is stale." + "Function catalog operation owner changed; handle is stale." ) +@dataclass(frozen=True, slots=True) +class FunctionCatalogPreparationHandle(FunctionCatalogOperationHandle): + """The one preparation owner in this exact execution-server incarnation.""" + + +@dataclass(frozen=True, slots=True) +class CustomFunctionRegistrationHandle(FunctionCatalogOperationHandle): + """Content/owner proof for observing publication, not replay permission.""" + + content_sha256: str + function_name: str | None + persist: bool + storage_dir: str | None + + @classmethod + def from_request(cls, request: CustomFunctionRegistrationRequest) -> Self: + return cls( + connection=request.connection, server_identity=request.require_server_identity(), + content_sha256=CustomFunctionSource.content_digest(request.source_code.encode("utf-8")), + function_name=request.function_name, persist=request.persist, + storage_dir=request.storage_dir, + ) + + def require_named_source(self) -> CustomFunctionSource: + if self.function_name is None: + raise ValueError("Persisted registration observation requires its admitted name.") + return CustomFunctionSource(self.function_name, self.content_sha256) + + def require_storage_dir(self) -> Path: + """Decode this handle's admitted source store once for native/path owners.""" + if self.storage_dir is None: + raise ValueError("Persisted registration observation requires its admitted store.") + return Path(self.storage_dir) + + +class CustomFunctionRegistrationObservationOutcome(str, Enum): + """Current canonical proofs; absence never establishes a failed mutation.""" + + NOT_OBSERVED = "not_observed" + PUBLISHED = "published" + PERSISTED_ONLY = "persisted_only" + REGISTERED = "registered" + + @classmethod + def from_proofs(cls, *, published: bool, persisted: bool, persist: bool) -> Self: + if published: + return cls.REGISTERED if persisted or not persist else cls.PUBLISHED + return cls.PERSISTED_ONLY if persisted else cls.NOT_OBSERVED + + +@dataclass(frozen=True, kw_only=True) +class CustomFunctionRegistrationObservation(AgentResultEnvelope): + handle: CustomFunctionRegistrationHandle + published_sources: tuple[CustomFunctionSource, ...] = () + persisted_source: CustomFunctionSource | None = None + outcome: CustomFunctionRegistrationObservationOutcome = field(init=False) + + def __post_init__(self) -> None: + object.__setattr__(self, "outcome", CustomFunctionRegistrationObservationOutcome.from_proofs( + published=bool(self.published_sources), persisted=self.persisted_source is not None, + persist=self.handle.persist, + )) + + class FunctionCatalogPreparationOutcome(str, Enum): NOT_STARTED = "not_started" PENDING = "pending" @@ -263,6 +330,12 @@ class CustomFunctionRegistrationDestinationRequest(FunctionCatalogControlRequest message_type = FunctionCatalogControlMessageType.CUSTOM_REGISTRATION_DESTINATION +@dataclass(frozen=True, slots=True) +class CustomFunctionRegistrationObservationRequest(FunctionCatalogControlRequestABC): + handle: CustomFunctionRegistrationHandle + message_type = FunctionCatalogControlMessageType.OBSERVE_CUSTOM_REGISTRATION + + @dataclass(frozen=True, slots=True) class CustomFunctionRegistrationDestination: storage_dir: str @@ -660,6 +733,25 @@ class CustomFunctionRegistrationResult(AgentResultEnvelope): next_steps: tuple[str, ...] = () connection: ExecutionConnectionSpec = field(default_factory=ExecutionConnectionSpec) server_identity: ProcessIdentity | None = None + observation_handle: CustomFunctionRegistrationHandle | None = None + + @classmethod + def uncertain( + cls, request: CustomFunctionRegistrationRequest, error: Exception, + ) -> Self: + """Preserve the actual cause and same-owner read-only recovery identity.""" + handle = CustomFunctionRegistrationHandle.from_request(request) + cause = error.to_agent_error() if isinstance(error, AgentFacingErrorMixin) else AgentError.from_exception( + "custom_function_registration_uncertain", error, + hint="Preserve the original request. Observe this handle; do not replay registration. Absence is not proof of no mutation.", + ) + return cls( + schema_version=SCHEMA_VERSION, persisted=request.persist, + storage_dir=request.storage_dir, connection=request.connection, + server_identity=request.server_identity, observation_handle=handle, + errors=(cause,), + next_steps=("Observe this observation_handle with openhcs_get_custom_function_registration_status; do not replay source.",), + ) class CustomFunctionRegistrationControlResponse( @@ -686,6 +778,13 @@ def destination(self) -> CustomFunctionRegistrationDestination: return self.value +class CustomFunctionRegistrationObservationControlResponse( + FunctionCatalogControlResponseBase[CustomFunctionRegistrationObservation] +): + field = FunctionCatalogControlField.RESULT + value_type = CustomFunctionRegistrationObservation + + def catalog_page( *, items: tuple[FunctionCatalogEntry, ...], diff --git a/openhcs/agent/services/endpoint_function_catalog_service.py b/openhcs/agent/services/endpoint_function_catalog_service.py index 4055c4bc4c..befa6b250f 100644 --- a/openhcs/agent/services/endpoint_function_catalog_service.py +++ b/openhcs/agent/services/endpoint_function_catalog_service.py @@ -19,6 +19,8 @@ CustomFunctionRegistrationDestinationRequest, CustomFunctionRegistrationRequest, CustomFunctionRegistrationResult, + CustomFunctionRegistrationHandle, + CustomFunctionRegistrationObservation, FunctionCatalogControlRequest, FunctionCatalogEntry, FunctionCatalogPage, @@ -32,7 +34,6 @@ FunctionReferenceControlRequest, FunctionSearchRequest, ) -from openhcs.agent.exceptions import AgentFacingErrorMixin from openhcs.agent.path_policy import AgentPathPolicy from openhcs.agent.services.function_catalog_service import FunctionCatalogServiceABC from openhcs.runtime.zmq_config import OpenHCSZMQConfig @@ -47,19 +48,6 @@ logger = logging.getLogger(__name__) -class CustomFunctionRegistrationUncertainError(AgentFacingErrorMixin, RuntimeError): - agent_error_code = "custom_function_registration_uncertain" - agent_error_hint = "Preserve this request and endpoint receipt. Do not replay or fall back; persistence may have completed." - - def __init__(self, request: CustomFunctionRegistrationRequest): - super().__init__( - f"Registration observation failed after invoking {request.connection.transport_endpoint()}; " - f"destination={request.storage_dir}, function={request.function_name!r}. " - f"Selected server={request.server_identity!r}. " - "Outcome is uncertain; source or registry mutation may have completed." - ) - - FunctionCatalogClientFactory = Callable[ [OpenHCSZMQConfig], "FunctionCatalogExecutionClient", @@ -471,8 +459,18 @@ def register_custom_function( result = client.register_custom_function( request, operation_deadline=deadline ) + if result.errors: + if result.observation_handle != CustomFunctionRegistrationHandle.from_request(request): + raise ValueError("Registration error returned a different observation owner.") + return result destination.require_result(request, result) - if not request.persist: + except FunctionCatalogEndpointUnavailableError: + # Native admission failed before the source-bearing exchange. + raise + except Exception as error: + return CustomFunctionRegistrationResult.uncertain(request, error) + if not request.persist: + try: from openhcs.processing.custom_functions.manager import ( CustomFunctionManager, ) @@ -483,13 +481,29 @@ def register_custom_function( clear_caches=False, emit_signal=False, ) - self.invalidate() - except FunctionCatalogEndpointUnavailableError: - # Native admission failed before the source-bearing exchange, so this - # is recoverable rejection, not an uncertain mutation receipt. - raise - except Exception as error: - raise CustomFunctionRegistrationUncertainError(request) from error + except Exception as error: + from openhcs.agent.dto.common import AgentError + # Native receipt is already confirmed; do not erase it on local failure. + return replace(result, errors=(AgentError.from_exception( + "custom_function_local_projection_failed", error, + hint="Native registration is confirmed. Preserve its receipt; do not register again to repair local projection.", + ),)) + self.invalidate() + return result + + def observe_custom_function_registration( + self, handle: CustomFunctionRegistrationHandle, + ) -> CustomFunctionRegistrationObservation: + if handle.persist: + from openhcs.processing.custom_functions.manager import CustomFunctionManager + source = handle.require_named_source() + self._path_policy.assert_readable_location(CustomFunctionManager.source_path_for_name( + handle.require_storage_dir(), source.function_name, + )) + endpoint = self._endpoint_for_connection(handle.connection) + result = self._client_for(endpoint).observe_custom_function_registration(handle) + if result.handle != handle: + raise ValueError("Registration observation returned a different owner/source handle.") return result def _endpoint_for_connection( diff --git a/openhcs/agent/services/function_catalog_service.py b/openhcs/agent/services/function_catalog_service.py index f71566984b..ad47d46e16 100644 --- a/openhcs/agent/services/function_catalog_service.py +++ b/openhcs/agent/services/function_catalog_service.py @@ -37,6 +37,8 @@ CustomFunctionRegistrationDestinationRequest, CustomFunctionRegistrationRequest, CustomFunctionRegistrationResult, + CustomFunctionRegistrationHandle, + CustomFunctionRegistrationObservation, FunctionArtifactSpec, FunctionCatalogEntry, FunctionCatalogPage, @@ -599,6 +601,12 @@ def register_custom_function( ) -> CustomFunctionRegistrationResult: """Register one custom declaration through this catalog authority.""" + @abstractmethod + def observe_custom_function_registration( + self, handle: CustomFunctionRegistrationHandle, + ) -> CustomFunctionRegistrationObservation: + """Observe exact existing publication/persistence without evaluating source.""" + @abstractmethod def search( self, @@ -755,6 +763,37 @@ def admit_write(path: Path) -> Path: ), connection=request.connection, server_identity=server_identity, + observation_handle=CustomFunctionRegistrationHandle.from_request(request), + ) + + def observe_custom_function_registration( + self, handle: CustomFunctionRegistrationHandle, + ) -> CustomFunctionRegistrationObservation: + from openhcs.agent.dto.common import AgentWarning + from openhcs.processing.custom_functions.runtime_registry import CustomFunctionRuntimeRegistry + + handle.require_current_owner() + published = CustomFunctionRuntimeRegistry.published_sources_for_content( + handle.content_sha256, function_name=handle.function_name, + ) + persisted = None + warnings = () + if handle.persist: + manager = custom_function_manager.CustomFunctionManager(create_storage=False) + if handle.require_storage_dir().resolve(strict=False) != manager.storage_dir.resolve(strict=False): + raise ValueError("Registration observation reached a different native source store.") + source = handle.require_named_source() + self._path_policy.assert_readable_location(manager.source_path_for_name(manager.storage_dir, source.function_name)) + try: + manager.require_source(source) + except (OSError, RuntimeError) as error: + warnings = (AgentWarning("registration_source_not_observed", str(error), + "Missing/changed current source is not proof of no original mutation; do not replay."),) + else: + persisted = source + return CustomFunctionRegistrationObservation( + schema_version=SCHEMA_VERSION, handle=handle, published_sources=published, + persisted_source=persisted, warnings=warnings, ) def custom_function_registration_destination( diff --git a/openhcs/mcp/dev_client_renderers/knowledge.py b/openhcs/mcp/dev_client_renderers/knowledge.py index aa47ecbefe..172353c1d3 100644 --- a/openhcs/mcp/dev_client_renderers/knowledge.py +++ b/openhcs/mcp/dev_client_renderers/knowledge.py @@ -28,8 +28,11 @@ CatalogRenderOptions, CodeDocumentRenderOptions, McpDevOutputRenderer, + McpDevTypedOutputRenderer, + McpDevOutputRenderOptions, McpDevPayloadProjection, ) +from openhcs.serialization.json import to_jsonable from openhcs.mcp.dev_client_renderers.object_state import ObjectStateScopeRenderer from openhcs.mcp.dev_client_renderers.viewer import ( RuntimeServerRenderer, @@ -461,54 +464,54 @@ def _item_lines(items: tuple[Mapping[str, JsonValue], ...]) -> list[str]: return lines -class CustomFunctionRegistrationRenderer(McpDevOutputRenderer): +class CustomFunctionRegistrationRenderer(McpDevTypedOutputRenderer): """Compact renderer for custom-function registration results.""" output_contract = CustomFunctionRegistrationResult + unavailable_summary = "Custom function registration: " @classmethod - def render(cls, response: JsonObject) -> str: - payload = McpDevPayloadProjection.first_tool_payload(response) - if payload is None: - return json.dumps(response, indent=2, sort_keys=True) - errors = McpDevPayloadProjection.sequence_of_mappings(payload.get("errors")) - if errors: + def render_payload( + cls, payload: CustomFunctionRegistrationResult, options: McpDevOutputRenderOptions, + ) -> str: + del options + # The existing typed ancestor owns batch decode and diagnostic rendering. + if payload.errors: return "\n".join( ( - "Custom function registration: failed", - *ViewerValidationRenderer._error_lines(errors), + "Custom function registration: incomplete observation or projection", + f"Retained native receipt registered_count: {payload.registered_count} (zero/absent does not prove no mutation)", + "Read-only observation handle: " + json.dumps(to_jsonable(payload.observation_handle), sort_keys=True), ) ) - functions = McpDevPayloadProjection.sequence_of_mappings( - payload.get("functions") - ) lines = [ ( "Custom function registration: " - f"registered={McpDevPayloadProjection.text(payload.get('registered_count'))} " - f"persisted={McpDevPayloadProjection.text(payload.get('persisted'))} " - f"storage={McpDevPayloadProjection.text(payload.get('storage_dir'))}" + f"registered={payload.registered_count} " + f"persisted={payload.persisted} " + f"storage={McpDevPayloadProjection.text(payload.storage_dir)}" ) ] - source_paths = payload.get("source_file_paths") - if source_paths: + if payload.source_file_paths: lines.append( - f"Files: {ViewerValidationRenderer._sequence_text(source_paths)}" + f"Files: {','.join(payload.source_file_paths)}" ) - if functions: + if payload.functions: lines.append("Functions:") - lines.extend(FunctionSearchRenderer._item_lines(functions)) - if payload.get("persisted") is False: + for function in payload.functions: + lines.append(f"- {function.function_id}: {function.signature} tags={','.join(function.backend_tags)}") + if function.summary: + lines.append(f" {function.summary}") + if payload.persisted is False: lines.append( "Lifetime: process-local only; follow-up dev_client commands " "start a fresh MCP process. Omit --no-persist or reuse the " "same MCP session before using these function ids." ) lines.append("Next:") - for function in functions: - function_id = McpDevPayloadProjection.text(function.get("function_id")) - lines.append(f"- function {function_id}") - lines.append(f"- draft-pipeline-step {function_id} --name ") + for function in payload.functions: + lines.append(f"- function {function.function_id}") + lines.append(f"- draft-pipeline-step {function.function_id} --name ") return "\n".join(lines) diff --git a/openhcs/processing/custom_functions/manager.py b/openhcs/processing/custom_functions/manager.py index e4cbd28f04..e4934e2a11 100644 --- a/openhcs/processing/custom_functions/manager.py +++ b/openhcs/processing/custom_functions/manager.py @@ -15,7 +15,6 @@ from __future__ import annotations -import hashlib import logging import os import tempfile @@ -208,7 +207,7 @@ def source_revision(self) -> CustomFunctionSourceRevision: sources = tuple( CustomFunctionSource( function_name=source_path.stem, - content_sha256=hashlib.sha256(source_path.read_bytes()).hexdigest(), + content_sha256=CustomFunctionSource.content_digest(source_path.read_bytes()), ) for source_path in sorted(self.storage_dir.glob("*.py")) ) @@ -531,7 +530,7 @@ def _prepare_source( ) source = CustomFunctionSource( function_name=declaration.__name__, - content_sha256=hashlib.sha256(code.encode("utf-8")).hexdigest(), + content_sha256=CustomFunctionSource.content_digest(code.encode("utf-8")), ) CustomFunctionSourceNamespace(source, namespace).bind(declaration) try: @@ -597,7 +596,7 @@ def _snapshot_source( return CustomFunctionSourceSnapshot( source=CustomFunctionSource( function_name=source_path.stem, - content_sha256=hashlib.sha256(source_bytes).hexdigest(), + content_sha256=CustomFunctionSource.content_digest(source_bytes), ), code=code, ) diff --git a/openhcs/processing/custom_functions/runtime_registry.py b/openhcs/processing/custom_functions/runtime_registry.py index 97d0dcbaf6..77cba42248 100644 --- a/openhcs/processing/custom_functions/runtime_registry.py +++ b/openhcs/processing/custom_functions/runtime_registry.py @@ -139,6 +139,20 @@ def declaration_for_source( return None return declaration + @classmethod + def published_sources_for_content( + cls, content_sha256: str, *, function_name: str | None = None, + ) -> tuple[CustomFunctionSource, ...]: + """Observe existing exact declarations without loading or evaluating source.""" + with cls._lock: + sources = tuple( + CustomFunctionSource(name, content_sha256) + for name, declaration in cls._declarations_by_name.items() + if (function_name is None or name == function_name) + and cls._declaration_revision(declaration.metadata) == content_sha256 + ) + return tuple(source for source in sources if cls.declaration_for_source(source) is not None) + @classmethod @contextmanager def lifecycle(cls): diff --git a/openhcs/processing/custom_functions/source_namespace.py b/openhcs/processing/custom_functions/source_namespace.py index 43c271b9d9..56bb1c0516 100644 --- a/openhcs/processing/custom_functions/source_namespace.py +++ b/openhcs/processing/custom_functions/source_namespace.py @@ -8,6 +8,7 @@ from __future__ import annotations +import hashlib from collections.abc import Callable, Iterator from dataclasses import dataclass, field from types import FunctionType @@ -23,6 +24,11 @@ class CustomFunctionSource: function_name: str content_sha256: str + @staticmethod + def content_digest(source_bytes: bytes) -> str: + """The exact byte identity used by preparation, persistence and observers.""" + return hashlib.sha256(source_bytes).hexdigest() + @dataclass(frozen=True, slots=True) class CustomFunctionSourceRevision: diff --git a/openhcs/runtime/zmq_control.py b/openhcs/runtime/zmq_control.py index c2c0996546..0f7e06c842 100644 --- a/openhcs/runtime/zmq_control.py +++ b/openhcs/runtime/zmq_control.py @@ -439,16 +439,42 @@ class CustomFunctionRegistrationMessageStrategy(ZMQControlMessageStrategy): def handle(self, message: dict, context: ZMQControlRequestContext) -> dict: from openhcs.agent.dto.functions import ( CustomFunctionRegistrationControlResponse, + CustomFunctionRegistrationResult, FunctionCatalogPreparationHandle, ) try: request = self.request_type.from_control_payload(message) + except Exception as error: + return self.error_response(error) + try: context.require_function_catalog_preparation().observe( FunctionCatalogPreparationHandle(request.connection, request.require_server_identity()) ).require_ready() return CustomFunctionRegistrationControlResponse( value=context.require_function_catalog().register_custom_function(request), ).to_control_response() + except Exception as error: + return CustomFunctionRegistrationControlResponse( + value=CustomFunctionRegistrationResult.uncertain(request, error), + ).to_control_response() + + +class CustomFunctionRegistrationObservationMessageStrategy(ZMQControlMessageStrategy): + """Read canonical source proofs without catalog preparation or evaluation.""" + + from openhcs.agent.dto.functions import CustomFunctionRegistrationObservationRequest + + request_type = CustomFunctionRegistrationObservationRequest + registry_key = request_type.message_type.value + + def handle(self, message: dict, context: ZMQControlRequestContext) -> dict: + from openhcs.agent.dto.functions import CustomFunctionRegistrationObservationControlResponse + + try: + request = self.request_type.from_control_payload(message) + return CustomFunctionRegistrationObservationControlResponse( + value=context.require_function_catalog().observe_custom_function_registration(request.handle), + ).to_control_response() except Exception as error: return self.error_response(error) diff --git a/openhcs/runtime/zmq_execution_client.py b/openhcs/runtime/zmq_execution_client.py index 69438a12e3..9965ec8672 100644 --- a/openhcs/runtime/zmq_execution_client.py +++ b/openhcs/runtime/zmq_execution_client.py @@ -79,6 +79,8 @@ CustomFunctionRegistrationDestinationRequest, CustomFunctionRegistrationRequest, CustomFunctionRegistrationResult, + CustomFunctionRegistrationHandle, + CustomFunctionRegistrationObservation, FunctionCatalogControlRequest, FunctionCatalogControlRequestABC, FunctionCatalogPage, @@ -789,7 +791,6 @@ def custom_function_registration_destination( from openhcs.agent.dto.functions import ( CustomFunctionRegistrationDestinationControlResponse, ) - response = self._send_function_catalog_exchange( request, operation_deadline=operation_deadline, @@ -800,6 +801,19 @@ def custom_function_registration_destination( ).destination ) + def observe_custom_function_registration( + self, handle: CustomFunctionRegistrationHandle, + ) -> CustomFunctionRegistrationObservation: + """One read-only exchange; never register/load the requested source.""" + from openhcs.agent.dto.functions import ( + CustomFunctionRegistrationObservationRequest, + CustomFunctionRegistrationObservationControlResponse, + ) + response = self._send_function_catalog_exchange( + CustomFunctionRegistrationObservationRequest(handle), + ) + return CustomFunctionRegistrationObservationControlResponse.from_control_response(response).value + def function_catalog_preparation( self, request: FunctionCatalogControlRequestABC, diff --git a/tests/unit/agent/test_custom_registration_admission.py b/tests/unit/agent/test_custom_registration_admission.py index 79f695463b..e200393523 100644 --- a/tests/unit/agent/test_custom_registration_admission.py +++ b/tests/unit/agent/test_custom_registration_admission.py @@ -24,7 +24,6 @@ ) from openhcs.agent.path_policy import AgentPathPolicy, AgentPathPolicyError from openhcs.agent.services.endpoint_function_catalog_service import ( - CustomFunctionRegistrationUncertainError, ZMQFunctionCatalogService, ) from openhcs.agent.services.function_catalog_service import FunctionCatalogService @@ -169,8 +168,10 @@ def factory(endpoint): catalog.register_custom_function(request(root)) assert not mutations elif timeout: - with pytest.raises(CustomFunctionRegistrationUncertainError, match="uncertain"): - catalog.register_custom_function(request(root)) + result = catalog.register_custom_function(request(root)) + assert result.errors[0].code == "custom_function_registration_uncertain" + assert result.errors[0].exception_type == "TimeoutError" + assert result.observation_handle.server_identity == ProcessIdentity.current() assert len(mutations) == 1 assert catalog._config_provider().default_port == 15993 else: @@ -332,10 +333,10 @@ def disconnect(self): path_policy=policy(tmp_path), ) try: - with pytest.raises( - CustomFunctionRegistrationUncertainError, match="Selected server" - ): - catalog.register_custom_function(request(tmp_path)) + result = catalog.register_custom_function(request(tmp_path)) + assert result.errors[0].code == "custom_function_registration_uncertain" + assert result.errors[0].message == "Registration returned a different execution owner." + assert result.observation_handle.server_identity == ProcessIdentity.current() assert len(mutations) == 1 assert catalog._config_provider().default_port == 15993 finally: @@ -382,7 +383,7 @@ def disconnect(self): try: with pytest.raises(FunctionCatalogNotReadyError) as failure: catalog.register_custom_function(request(tmp_path)) - assert not isinstance(failure.value, CustomFunctionRegistrationUncertainError) + assert failure.value.preparation.outcome is not FunctionCatalogPreparationOutcome.READY assert observed == ["destination", "read-only-preparation"] assert catalog._config_provider().default_port == 15993 finally: @@ -543,6 +544,117 @@ async def invoke(): catalog.close() +def test_generated_mcp_retains_native_cause_and_observes_without_replay(tmp_path, monkeypatch): + from openhcs.mcp.server import build_server + from openhcs.agent.dto.functions import ( + CustomFunctionRegistrationControlResponse, + CustomFunctionRegistrationObservationControlResponse, + CustomFunctionRegistrationObservationRequest, + FunctionCatalogControlPayload, + ) + from openhcs.runtime.zmq_control import ZMQControlMessageRouter, ZMQControlRequestContext + + native = FunctionCatalogService(path_policy=policy(tmp_path)) + dispatched = [] + + def fail_after_dispatch(admitted): + dispatched.append(admitted) + raise ValueError("original-native-validation-cause") + + monkeypatch.setattr(native, "register_custom_function", fail_after_dispatch) + from openhcs.processing.custom_functions import manager as manager_module + monkeypatch.setattr(manager_module, "get_data_file_path", lambda _name, *, create: tmp_path) + context = ZMQControlRequestContext( + compiled_artifacts={}, function_catalog=native, + function_catalog_preparation=SimpleNamespace(observe=lambda handle: preparation_state(handle)), + ) + + class Client: + def custom_function_registration_destination(self, _probe, **_kwargs): + return CustomFunctionRegistrationDestination(str(tmp_path), str(tmp_path / "boundary_probe.py")) + + def function_catalog_preparation(self, read_request, **_kwargs): + return preparation_state(read_request.handle) + + def register_custom_function(self, admitted, **_kwargs): + response = ZMQControlMessageRouter.handle(FunctionCatalogControlPayload.from_request(admitted).to_dict(), context) + return CustomFunctionRegistrationControlResponse.from_control_response(response).result + + def observe_custom_function_registration(self, handle): + response = ZMQControlMessageRouter.handle(FunctionCatalogControlPayload.from_request(CustomFunctionRegistrationObservationRequest(handle)).to_dict(), context) + return CustomFunctionRegistrationObservationControlResponse.from_control_response(response).value + + def disconnect(self): + pass + + catalog = ZMQFunctionCatalogService(lambda: OPENHCS_ZMQ_CONFIG, + client_factory=lambda _endpoint: Client(), path_policy=policy(tmp_path)) + + async def invoke(): + server = build_server(SimpleNamespace(function_catalog=catalog)) + first = await server.call_tool("openhcs_register_custom_function", { + "source_code": request(tmp_path).source_code, + "function_name": "boundary_probe", "storage_dir": str(tmp_path), + "port": 15993, "transport_mode": "tcp", + }) + content = first[0] if isinstance(first, tuple) else first.content + result = json.loads(content[0].text) + assert result["errors"][0]["exception_type"] == "ValueError" + assert result["errors"][0]["message"] == "original-native-validation-cause" + handle = result["observation_handle"] + second = await server.call_tool("openhcs_get_custom_function_registration_status", handle) + content = second[0] if isinstance(second, tuple) else second.content + observed = json.loads(content[0].text) + print(json.dumps({"registration_observation_public_reply": observed}, sort_keys=True)) + assert observed.get("handle") == handle, observed + assert observed["outcome"] == "not_observed" + assert observed["published_sources"] == [] + assert len(dispatched) == 1 + + try: + asyncio.run(invoke()) + finally: + catalog.close() + + +def test_confirmed_native_receipt_survives_local_projection_failure(tmp_path, monkeypatch): + mutations = [] + + class Client: + def custom_function_registration_destination(self, _probe, **_kwargs): + return CustomFunctionRegistrationDestination(str(tmp_path), None) + + def function_catalog_preparation(self, read_request, **_kwargs): + return preparation_state(read_request.handle) + + def register_custom_function(self, admitted, **_kwargs): + mutations.append(admitted) + return CustomFunctionRegistrationResult( + schema_version=SCHEMA_VERSION, registered_count=1, persisted=False, + connection=admitted.connection, server_identity=admitted.server_identity, + ) + + def disconnect(self): + pass + + def local_failure(*_args, **_kwargs): + raise LookupError("local-projection-witness") + + monkeypatch.setattr(CustomFunctionManager, "register_from_code", local_failure) + catalog = ZMQFunctionCatalogService(lambda: OPENHCS_ZMQ_CONFIG, + client_factory=lambda _endpoint: Client(), path_policy=policy(tmp_path)) + try: + result = catalog.register_custom_function(request(tmp_path, persist=False)) + assert result.registered_count == 1 + assert result.server_identity == ProcessIdentity.current() + assert result.errors[0].code == "custom_function_local_projection_failed" + assert result.errors[0].exception_type == "LookupError" + assert result.errors[0].message == "local-projection-witness" + assert len(mutations) == 1 + finally: + catalog.close() + + def test_generated_mcp_preparation_tools_use_reflected_connection_and_handle(): from openhcs.agent.dto.execution_connection import ExecutionConnectionSpec from openhcs.mcp.server import build_server diff --git a/tests/unit/agent/test_mcp_dev_client_pipeline_results.py b/tests/unit/agent/test_mcp_dev_client_pipeline_results.py index 4770bc1dfa..19d34b9c38 100644 --- a/tests/unit/agent/test_mcp_dev_client_pipeline_results.py +++ b/tests/unit/agent/test_mcp_dev_client_pipeline_results.py @@ -86,6 +86,61 @@ def batch(capability, value): ) +@pytest.mark.parametrize("registered_count, code", ( + (0, "custom_function_registration_uncertain"), + (1, "custom_function_local_projection_failed"), +)) +def test_registration_renderer_retains_typed_error_receipt_and_handle(registered_count, code): + from openhcs.agent.dto.execution_connection import ExecutionConnectionSpec + from openhcs.agent.dto.functions import CustomFunctionRegistrationHandle, CustomFunctionRegistrationResult + from openhcs.mcp.dev_client_renderers.knowledge import CustomFunctionRegistrationRenderer + from zmqruntime.messages import ProcessIdentity + + handle = CustomFunctionRegistrationHandle( + ExecutionConnectionSpec(port=15993), ProcessIdentity.current(), + "a" * 64, "engineering_registration", False, None, + ) + original = CustomFunctionRegistrationResult( + schema_version=SCHEMA_VERSION, registered_count=registered_count, + connection=handle.connection, server_identity=handle.server_identity, + observation_handle=handle, + errors=(AgentError(code=code, message="original cause", hint="Do not replay.", exception_type="ValueError"),), + ) + response = batch(agent_capabilities.register_custom_function, original) + decoded = McpDevToolBatchResponse.for_rendering(to_jsonable(response)) + assert decoded.payload_for(agent_capabilities.register_custom_function) == original + text = CustomFunctionRegistrationRenderer.render(to_jsonable(response)) + assert "incomplete observation or projection" in text + assert f"registered_count: {registered_count}" in text + assert "zero/absent does not prove no mutation" in text + assert text.count(f"{code}: original cause") == 1 + assert "Do not replay." in text + marker = "Read-only observation handle: " + rendered_handle = next(line.removeprefix(marker) for line in text.splitlines() if line.startswith(marker)) + assert json.loads(rendered_handle) == to_jsonable(handle) + + +def test_registration_renderer_uses_declared_success_entries_without_mapping_reads(): + from openhcs.agent.dto.functions import CustomFunctionRegistrationResult, FunctionCatalogEntry + from openhcs.mcp.dev_client_renderers.knowledge import CustomFunctionRegistrationRenderer + + entry = FunctionCatalogEntry( + function_id="openhcs:engineering_registration", import_path="openhcs.processing.custom_functions.engineering_registration", + name="engineering_registration", module="openhcs.processing.custom_functions", library="openhcs", + signature="engineering_registration(image)", summary="Original declared summary", backend_tags=("numpy",), + ) + original = CustomFunctionRegistrationResult( + schema_version=SCHEMA_VERSION, registered_count=1, persisted=False, + storage_dir="/engineering", source_file_paths=("/engineering/original.py",), functions=(entry,), + ) + text = CustomFunctionRegistrationRenderer.render(to_jsonable(batch(agent_capabilities.register_custom_function, original))) + assert "registered=1 persisted=False storage=/engineering" in text + assert "Files: /engineering/original.py" in text + assert f"{entry.function_id}: {entry.signature} tags=numpy" in text + assert entry.summary in text and "Lifetime: process-local only" in text + assert f"draft-pipeline-step {entry.function_id}" in text + + def args_for(*argv): return dev_client._build_parser().parse_args(argv) diff --git a/tests/unit/test_custom_function_lifecycle.py b/tests/unit/test_custom_function_lifecycle.py index e101b413ed..4769f31b51 100644 --- a/tests/unit/test_custom_function_lifecycle.py +++ b/tests/unit/test_custom_function_lifecycle.py @@ -410,6 +410,48 @@ def isolated_custom_runtime(monkeypatch, tmp_path): CustomFunctionRuntimeRegistry.clear() +@pytest.mark.parametrize("persist", (False, True)) +def test_registration_observation_uses_current_owners_without_loading( + isolated_custom_runtime, monkeypatch, persist, +): + from dataclasses import replace + from openhcs.agent.dto.functions import ( + CustomFunctionRegistrationHandle, CustomFunctionRegistrationRequest, + CustomFunctionRegistrationObservationOutcome, + ) + from openhcs.agent.path_policy import AgentPathPolicy + from openhcs.agent.services.function_catalog_service import FunctionCatalogService + from zmqruntime.messages import ProcessIdentity + + root = isolated_custom_runtime + policy = AgentPathPolicy.with_roots(readable_roots=(root,), writable_roots=(root,)) + manager = CustomFunctionManager(create_storage=False) + request = CustomFunctionRegistrationRequest.from_fields( + source_code=_source("observation_probe"), function_name="observation_probe", + persist=persist, storage_dir=str(root), port=22319, + ) + handle = CustomFunctionRegistrationHandle.from_request(replace(request, server_identity=ProcessIdentity.current())) + service = FunctionCatalogService(path_policy=policy) + before = service.observe_custom_function_registration(handle) + assert before.outcome is CustomFunctionRegistrationObservationOutcome.NOT_OBSERVED + [function] = manager.register_from_code(request.source_code, persist=persist, clear_caches=False, emit_signal=False) + monkeypatch.setattr(CustomFunctionManager, "_prepare_source", lambda *_: pytest.fail("Read-only observation cannot evaluate source")) + monkeypatch.setattr(CustomFunctionManager, "load_custom_function", lambda *_a, **_k: pytest.fail("Read-only observation cannot lazy load")) + observed = service.observe_custom_function_registration(handle) + assert observed.outcome is CustomFunctionRegistrationObservationOutcome.REGISTERED + assert observed.published_sources == (handle.require_named_source(),) + assert CustomFunctionRuntimeRegistry.metadata_by_name()["observation_probe"].func is function + assert observed.persisted_source == (handle.require_named_source() if persist else None) + changed = service.observe_custom_function_registration(replace(handle, content_sha256="0" * 64)) + assert changed.outcome is CustomFunctionRegistrationObservationOutcome.NOT_OBSERVED + assert not changed.published_sources + with pytest.raises(RuntimeError, match="stale"): + service.observe_custom_function_registration(replace(handle, server_identity=replace(ProcessIdentity.current(), create_time=0))) + CustomFunctionRuntimeRegistry.remove("observation_probe") + after = service.observe_custom_function_registration(handle) + assert after.outcome is (CustomFunctionRegistrationObservationOutcome.PERSISTED_ONLY if persist else CustomFunctionRegistrationObservationOutcome.NOT_OBSERVED) + + def test_register_rejects_multi_declaration_source_without_partial_publication( isolated_custom_runtime, ) -> None: diff --git a/tests/unit/test_function_catalog_zmq.py b/tests/unit/test_function_catalog_zmq.py index cf740e73f6..e60f8de992 100644 --- a/tests/unit/test_function_catalog_zmq.py +++ b/tests/unit/test_function_catalog_zmq.py @@ -930,10 +930,10 @@ def test_native_registration_does_not_start_or_wait_for_cold_preparation(monkeyp response = ZMQControlMessageRouter.handle( FunctionCatalogControlPayload.from_request(request).to_dict(), context ) - assert ( - response["status"] == "error" - and "No source was dispatched" in response["error"] - ) + result = CustomFunctionRegistrationControlResponse.from_control_response(response).result + assert result.errors[0].code == "function_catalog_not_ready" + assert "No source was dispatched" in result.errors[0].message + assert result.observation_handle.server_identity == ProcessIdentity.current() assert context.function_catalog_preparation._future is None diff --git a/validation/registration-observation-567/DEPENDENCIES.rst b/validation/registration-observation-567/DEPENDENCIES.rst new file mode 100644 index 0000000000..d00ff09895 --- /dev/null +++ b/validation/registration-observation-567/DEPENDENCIES.rst @@ -0,0 +1,83 @@ +567 whole candidate: original paired dependency inputs +===================================================== + +Receiving branch: fix/custom-registration-observation-20261003 in existing +/home/ts/wt/openhcs-ui-workflow-rendering-20261001. Normal merge45bb6fc908daec28a6c53148e8bdbbdb77d99915 +contains parent-reviewed567 production5c4b5adf0 and currentmain4f9e10820e12e2f47376ee5050b5f9436604f5f1. +Parent BOUND-2 review required a narrow typed-renderer correction, source-qualified +at a799352b9. Candidate2b391 was superseded before any build or source borrow. +The final published descendant of a799 is the exact candidate head sent to +Dewey; nine other567 production files remain qualified5c4 bytes. Foreign +externals, all original failure logs and other branches stay. Dependency inputs +below are unchanged; whole candidate qualification must use corrected bytes. + +Builder owner and interpreter +---------------------------- + +Dewey builds through the existing offline/no-isolation whole-wheel owner, +original materialization/RECORD/source/native/member verifiers and current FUND. +No dependency installation into the shared interpreter, pth edit, gitlink update, +build here or new environment/worktree is requested. One ordinary whole target +for both MCP/native; no selective file patch or source overlay. + +Existing interpreter: +/home/ts/wt/openhcs-paired-raw-installed-parent-20261001/.venv/bin/python. +Its existing openhcs-validation-dependencies.pth adds the unchanged shared +/home/ts/wt/basicpy-live-candidate-20260930/.venv/lib/python3.12/site-packages. +The full core and MCP-extra dependency declarations are still pyproject.toml. +paired-dependencies01.json derives all applicable requirements from that owner +and records actual importlib.metadata distribution origins/version/SHA/directURL; +this is a read-only observation, not a new registry or runtime permission. +No OpenHCS/scientific module was imported for this metadata observation. + +Required existing qualified dependency wheels in the candidate +------------------------------------------------------------- + +Use the same two already-qualified inputs as original materialize-package09.sh, +NOT same-version older APIs from the paired fallback or foreign source trees. +Directory: +/home/ts/wt/openhcs-issue-batch-20260929/next-h004-preparation-20261002/ +official30-package-implementation-20261003/scratch/catalog511-package01/wheels. + +* objectstate-1.1.8-py3-none-any.whl SHA256 + fb105022285b8e2565b23aa8a1e838fe2fc7d8ebe0cebd09537cc238b79891e3. + Required original API: LazyDataclassFactory.registered_type_pairs. + Original published source9c31e4b07b5537c0ed2307c5a63034c708b86bbb. +* python_introspect-0.1.14-py3-none-any.whl SHA256 + 274da63fddb74466de3acdb5042cd75ba58cd7ea753cb6aa83b3a679980f9146. + Required original API: SignatureAnalyzer.prepare_dataclass_declaration, + original declared dataclass_from_mapping boundary codec. + Original published source6f5ac0b79d6cef163df6a8389e72a889a91ed548. + +Original matched proof (reuse its verifier, not a new checker): +official30-package-implementation-20261003/catalog511-bytes01.json and +engineering494/qualify-package03.py -> qualify-package38.py -> qualify-package09.py. +These own complete dependency source/wheel/target/RECORD and real required-API +proof. The new candidate must reuse that ordinary proof with its own source, +wheel, target identities; old09 proof is not567 proof. Same package version +does not establish the required API: metadata observation is explicitly weaker. + +Other paired backing (read-only) +------------------------------- + +Observed paired site-packages provides zmqruntime0.3.1, metaclass-registry0.2.2, +arraybridge0.3.6, polystore0.3.1 and pyqt-reactive0.3.25. pycodify0.1.3 comes from +the existing basicpy shared backing. MCP1.29.0 resolves from the existing +/home/ts/code/projects/openhcs/.venv/lib/python3.12/site-packages through that +unchanged backing. Exact metadata paths/hashes/direct URLs are in the JSON. +Remaining scientific/serialization/utility dependencies are likewise derived +from pyproject.toml in that observation; none is downloaded or independently +copied here. Native construction/RECORD qualification remains the builder owner. + +Registration/progress specifically needs the ACTUAL paired ZMQRuntime, not +foreign-source9e11f6. startup.py imports from paired site-packages, SHA256 +55118bc44633926434f0e0b057c167dc2948f2a632a902b5bdb60dcb92731d69; +EndpointStartupStatus.callback_scope was actually exercised by source controls06. +Its existing direct URL records zmqruntime-0.3.1 wheel SHA256 +b431eac28214dad42df61b44be25b51d30b8ff11181383e614607214adcd735f. +No claim of unavailable recorded f7b80c gitlink equivalence is made. Do not +activate source checkout externals inside an ordinary installed candidate. + +Receiving release remains the original Planck554 exact94 closure followed by +Dewey's fresh admitted member/launch custody. No second client, adopted viewer, +native startup or receiving resource reservation has occurred in this worker. diff --git a/validation/registration-observation-567/PUBLIC.commands b/validation/registration-observation-567/PUBLIC.commands new file mode 100644 index 0000000000..de075bb6b6 --- /dev/null +++ b/validation/registration-observation-567/PUBLIC.commands @@ -0,0 +1,29 @@ +# Templates for the single released retained CLI, not an automatic batch. +# Exact package/native custody and original FUND release precede invocation. +# Native94:6012 TCP. No viewer6013 request; no other scientific endpoint. + +call openhcs_health_check --arguments '{}' --json +tools --contains register_custom_function --limit 1 --json +tools --contains get_custom_function_registration_status --limit 1 --json + +# Only if the release includes one new native bootstrap; never adopt a foreign +# runtime or repeat an interrupted/UNKNOWN startup. Save its complete handle. +call openhcs_start_owned_runtime --arguments '{"host":"127.0.0.1","port":6012,"transport_mode":"tcp","persistent":true}' --json +call openhcs_observe_owned_runtime --arguments '{"handle":}' --json + +# Original native preparation lifecycle, explicit native route. Save its handle. +call openhcs_start_function_catalog_preparation --arguments '{"host":"127.0.0.1","port":6012,"transport_mode":"tcp","persistent":true}' --json +call openhcs_get_function_catalog_preparation_status --arguments '' --json + +# After READY and exact launch_plan.storage_dir readback, generate ONE source +# command from the immutable fixture (generator command is in RECEIVING.rst). +# Do not execute this placeholder or generate a second source-bearing call. +call openhcs_register_custom_function --arguments '' --json + +# Pass ONLY the original returned observation_handle fields, no nesting and no +# manually invented server_identity/digest. Same exact native incarnation. +call openhcs_get_custom_function_registration_status --arguments '' --json + +# Only after terminal public evidence; close the exact original bootstrap owner. +call openhcs_close_owned_runtime --arguments '{"handle":,"mode":"force"}' --json +exit diff --git a/validation/registration-observation-567/RECEIVING.rst b/validation/registration-observation-567/RECEIVING.rst new file mode 100644 index 0000000000..eab893d18a --- /dev/null +++ b/validation/registration-observation-567/RECEIVING.rst @@ -0,0 +1,170 @@ +Registration observation: original installed TCP receiving packet +================================================================ + +Status: focused installed TCP native receiving PASSED on qualified whole8513. +Singer verified the replies; Dewey delivered through original retained PTY15414. +One registration/status and exact native closure succeeded. Original shell +terminal1 is retained separately; no UNKNOWN recovery is claimed. The historical +preparation procedure below remains the original packet, not a launch request. +No new checkout, environment, dependency download, build or installation here. + +Current receiving correction +----------------------------- + +Parent has withdrawn invented RAM budgets. Do not impose the historical4GiB +limit or memory/swap-cap equality when launching this case. Parent's existing +common slice and helper hard limits are now infinity. Dewey owns the complete +original operations-family removal in577, including both launchers, admission, +projection and helper checks. Reuse that coherent owner for the new recorded +client; do not copy a launcher or patch a frozen scientific installation. +Actual host headroom/PSI and disk readback inform the operation. Warning alone +is not a prohibition. CPU, ordinary deadlines, path/custody and journals remain. + +Corrected whole8513 is qualified by engineering567/READY567.json and +QUALIFICATION01.rst:815 source/wheel/target payloads,90 resources,13 skill files, +984 RECORD entries,9 affected installed controls,105 constructed MCP tools, +fresh public health/normal client exit0. Both client and native use that exact +private target; Singer's reused checkout has no runtime import authority. +Original131 typed close/client terminal is proven; its new revision UNKNOWN +is retained and will not be replayed. One distinct registration/status case +uses the byte-exact source-packet fixture and actual native launch-plan store. +Public TCP proof is now received in engineering567/public94-attempt01/ +LIVE-RECEIVING01.rst: one registration, matching returned/status handle and exact +published/persisted digest, followed by successful typed native close. Both +native188784 and MCP177709 are absent;6012/7012 have no listeners. Original +recorded shell terminal1 is not relabelled0. All eight public operation replies +have empty errors and mcp_error=false; no specific public operation failed here. + +Whole candidate identity +------------------------ + +567 product5c4b5adf0 is the original source control checkpoint. Source +MCP controls06 passed with exact read-only paired ZMQRuntime backing. That is +NOT an installed native acceptance. Both client and native must contain the ten +567 production files identified in the main receipt; both paths must appear in +the normal package owner's whole-source/RECORD verification, actual module +identities and ordinary health. No selective installed-file overlay is permitted. + +Planck confirmed554 immutable package04 has NO567 and only creates viewer6013, +not native6012. Whole-main10 is likewise not567 acceptance. At the parent's +direction the receiving branch now normally merges currentmain4f9e10820 into +567, merge45bb6fc908daec28a6c53148e8bdbbdb77d99915. It carries569/572,568/571, +current APIs and original funding consumers. Parent BOUND-2 correction now moves +ONE registration renderer onto the existing typed ancestor at a799352b9; +3 focused controls PASS, original R0 gets -10 / no added counts for that correction. +Nine other567 production files remain BYTE-IDENTICAL to qualified5c4b5adf0; +operations and packaged guides are BYTE-IDENTICAL to merged main4f9. No conflict, +rebase or foreign edit occurred. Candidate2b391 was superseded BEFORE build. + +Dewey is the original offline whole-candidate builder. Build/qualify one ordinary +candidate package from the exact final published corrected head, then use it +for BOTH client and native. Dependency inputs are documented +in DEPENDENCIES.rst and paired-dependencies01.json; no selective overlay or old +target claims. No build/installation has occurred in this worker. Frozen09 and +original SCI remain unchanged; currentmain10 and567 are distinct source claims. + +Exact lease and limits +---------------------- + +After Planck's original554 public close/clientEOF and Dewey's actual handoff, +refresh original FUND/current programme full-growth admission for this new +engineering intent. Do not adopt554's viewer/native owner or replay its calls. +94 means native6012 TCP/ACK7012; viewer6013/ACK7013 is unused. Never redirect to +the original failed6020 registration or sibling scientific6014/6016 endpoints. + +Use the original Dewey launch/environment/resource owner and one retained normal +CLI shell, not another runner or one-shot startup. Original registry discovery30s, +ordinary calls10s and native catalog operation5s remain unchanged. Record original +CLI handle, native bootstrap process_identity/create time, launch plan/store, +all requests/replies and exact terminal/close dispositions. An original known +startup error ends that intent; UNKNOWN stays unresolved and is never replayed. + +No viewer/model/input/science/pipeline/compile/execution is needed. The fixture +is one tiny @numpy declaration already source-qualified through the manager's +original injected decorator namespace; it is registered but never called. Text +evidence allowance <=64MiB. Native catalog preparation still needs its original +funded full-growth resource scope: do NOT pretend it is a512MiB source test or +invent a reduced cap. Only text/source preparation has run in this checkout. + +Original launch/recording references (reuse, do not copy engines): +engineering494/headless-mcp09.sh (normal retained CLI/interpreter/env shape), +engineering494/POINTS494_95/author-workspace/output/PUBLIC-COMMANDS.rst +(typed bootstrap/preparation/registration/close), and engineering503/ +PARENT-PUBLIC04-ACCEPTANCE.rst (single-client native custody/terminal receipt). +Those older target/port/scratch identities are immutable history, NOT commands +to run unchanged. Dewey supplies the actual current released launch invocation. + +One source-bearing request +------------------------- + +PUBLIC.commands is a manual template, not an executable batch. Reflect both +tools from the exact installed MCP. Observe the original bootstrap handle to +READY, explicitly prepare the same native catalog and observe that original +preparation handle to READY. Read native store from its actual launch_plan; +persist=true requires that precise native store, not a fabricated writable path. + +After those proofs, this original jq CLI pattern generates ONE registration +command from frozen source bytes and the actual store, without invoking MCP: + +:: + + jq -nr --rawfile code /home/ts/wt/openhcs-ui-workflow-rendering-20261001/validation/registration-observation-567/registration_observation_probe_567.py --arg store '' '{source_code:$code,function_name:"registration_observation_probe_567",storage_dir:$store,persist:true,host:"127.0.0.1",port:6012,transport_mode:"tcp",persistent:true}|"call openhcs_register_custom_function --arguments "+(tojson|@sh)+" --json"' + +Save the generated command before entering it ONCE into the original retained +shell. No retry, fallback, registration update/delete or second negative fixture +is authorized by this packet. If it returns an uncertain receipt containing a +handle, use only the supported read-only observation of that exact handle; do +not send the source again. Preserve its actual errors/cause/disposition. + +Acceptance relation from original DTO declarations +------------------------------------------------- + +On the positive case, the complete CustomFunctionRegistrationResult must carry +errors=[], registered_count=1, persisted=true, one original function entry, one +source_file_path in the admitted store, and observation_handle. Its connection +and server_identity must equal the explicit native route and original bootstrap +process_identity. The source handle's content_sha256 must equal SHA256 of the +fixture's exact UTF-8 bytes; function_name/persist/store must equal this request. +Keep the returned canonical function identity, do not guess an ID namespace. + +Call openhcs_get_custom_function_registration_status with the returned handle's +declared fields directly (connection, server_identity, content_sha256, +function_name, persist, storage_dir), NOT {handle:...}. This is the original +capability's typed decode, not manual payload/source inspection or a new ledger. + +The complete CustomFunctionRegistrationObservation must return the same handle, +errors=[], outcome=registered, published_sources containing precisely the same +named content identity and persisted_source equal to that identity. This proves +current canonical native publication and saved source, not execution or biology. +One recorded registration call is the mutation count; no second registration +should appear in the original journals. Observation emits no source code and +does not prepare/load/evaluate source. Absence means not_observed, never proof +of no original mutation or permission to replay. Do not weaken successful-case +assertions into count agreement, mere catalog visibility or endpoint health. + +Cause-preservation and local-projection source controls are separately archived +under567. This one-request installed case does not manufacture a transport failure +or replay the original H002 uncertain source to test recovery. + +Disposition +----------- + +Save original complete stdout/stderr and native log; freeze paths/hashes once +terminal. Use the original close_owned_runtime capability with the same complete +bootstrap handle, one close call, then normal CLI exit. Independently establish +ACK/process_exited/endpoint termination and actual parent shell termination; +do not infer numerical native exit status. No viewer should ever be started. +No helper or foreign process is signalled. A missing close disposition stays +explicitly assigned to the current receiving owner, not silently abandoned. + +Those named prerequisites were satisfied by whole8513 qualification, prior +exact94 closure and Dewey's original uncapped577 recorded-client handoff. +Focused registration/observation/closure acceptance is complete; no remaining +native receiving dependency holds567. This does not resolve original UNKNOWN +requests, claim live fault-injection coverage, all-zero debt or scientific gain. + +Frozen fixture: registration_observation_probe_567.py, 154 bytes, SHA256 +4398ac619555f8a1c0459dbbcb1a6440862fab50602cea1485c756c6ebd1e828. +The original prepared packet was under10KiB and launched nothing by itself. +Its one newly authorised native case is now complete; all prior source controls +and whole-wheel qualification remain tied to unchanged production8513. diff --git a/validation/registration-observation-567/paired-dependencies01.json b/validation/registration-observation-567/paired-dependencies01.json new file mode 100644 index 0000000000..d7e95f53af --- /dev/null +++ b/validation/registration-observation-567/paired-dependencies01.json @@ -0,0 +1,412 @@ +{ + "mode": "read-only original distribution metadata; no OpenHCS/scientific import", + "source_head": "45bb6fc908daec28a6c53148e8bdbbdb77d99915", + "dependencies": [ + { + "declared": "numpy>=1.26.4", + "name": "numpy", + "version": "2.1.3", + "metadata_path": "/home/ts/code/projects/openhcs/.venv/lib/python3.12/site-packages/numpy-2.1.3.dist-info/METADATA", + "metadata_sha256": "7c07741da49dc3af378a7d22b554a7c3815a0784e6e4adccf6b715a2ece644de", + "satisfies": true, + "direct_url": null + }, + { + "declared": "scikit-image>=0.25.2", + "name": "scikit-image", + "version": "0.25.2", + "metadata_path": "/home/ts/code/projects/openhcs/.venv/lib/python3.12/site-packages/scikit_image-0.25.2.dist-info/METADATA", + "metadata_sha256": "efc0b93767c3a5f239c5c8f835da9a13addcfab9533a6971173a36d0687db934", + "satisfies": true, + "direct_url": null + }, + { + "declared": "scikit-learn>=1.7.1", + "name": "scikit-learn", + "version": "1.9.1", + "metadata_path": "/home/ts/code/projects/openhcs/.venv/lib/python3.12/site-packages/scikit_learn-1.9.1.dist-info/METADATA", + "metadata_sha256": "32e503c710a61e4e04be4edb450f9f8141233ac6af3f3db59a877667538fa4ec", + "satisfies": true, + "direct_url": null + }, + { + "declared": "scipy>=1.12.0", + "name": "scipy", + "version": "1.18.1", + "metadata_path": "/home/ts/code/projects/openhcs/.venv/lib/python3.12/site-packages/scipy-1.18.1.dist-info/METADATA", + "metadata_sha256": "6d1ebfbce110009e25e8249fea05e21c0449735144e9bd8df1a52931f957c1ea", + "satisfies": true, + "direct_url": null + }, + { + "declared": "PyWavelets>=1.6.0", + "name": "PyWavelets", + "version": "1.10.0", + "metadata_path": "/home/ts/code/projects/openhcs/.venv/lib/python3.12/site-packages/pywavelets-1.10.0.dist-info/METADATA", + "metadata_sha256": "fd52073794bccef48c17bfb2084f574ef1f9e45b934d6ab91d09ddc2b3b4d327", + "satisfies": true, + "direct_url": null + }, + { + "declared": "openhcs-basicpy>=1.3.1,<1.4; platform_system != 'Darwin' or platform_machine != 'x86_64'", + "name": "openhcs-basicpy", + "version": "1.3.1", + "metadata_path": "/home/ts/wt/basicpy-live-candidate-20260930/.venv/lib/python3.12/site-packages/openhcs_basicpy-1.3.1.dist-info/METADATA", + "metadata_sha256": "05378047b9b0db0a30edfcfcd957ff0c56a7a7b4f22cd44d59ba94cb5d262207", + "satisfies": true, + "direct_url": { + "archive_info": { + "hash": "sha256=a82bcd9628650ea019a819959d18efdffa3d5a11efb24c59530a060e9a154cd2", + "hashes": { + "sha256": "a82bcd9628650ea019a819959d18efdffa3d5a11efb24c59530a060e9a154cd2" + } + }, + "url": "file:///home/ts/wt/openhcs-issue-batch-20260929/basicpy-project-dependency-20260930/release131/openhcs_basicpy-1.3.1-py3-none-any.whl" + } + }, + { + "declared": "pandas>=2.3.1", + "name": "pandas", + "version": "3.0.5", + "metadata_path": "/home/ts/code/projects/openhcs/.venv/lib/python3.12/site-packages/pandas-3.0.5.dist-info/METADATA", + "metadata_sha256": "119bd5541f4e2d413f440c82ad75b7b0457602f368b4b02efb467297c3c4a5b1", + "satisfies": true, + "direct_url": null + }, + { + "declared": "openpyxl>=3.1.0", + "name": "openpyxl", + "version": "3.1.5", + "metadata_path": "/home/ts/code/projects/openhcs/.venv/lib/python3.12/site-packages/openpyxl-3.1.5.dist-info/METADATA", + "metadata_sha256": "23f80ca9830dd89435da1710f26deda8f81e5409287e74540210c72627a4ad96", + "satisfies": true, + "direct_url": null + }, + { + "declared": "xlsxwriter>=3.2.0", + "name": "xlsxwriter", + "version": "3.2.9", + "metadata_path": "/home/ts/code/projects/openhcs/.venv/lib/python3.12/site-packages/xlsxwriter-3.2.9.dist-info/METADATA", + "metadata_sha256": "10d504f188e087f07a06b9038ed0535f37ac072c232e60c075ddb0aedb7b7d78", + "satisfies": true, + "direct_url": null + }, + { + "declared": "numba>=0.61.0; platform_system != 'Darwin' or platform_machine != 'x86_64'", + "name": "numba", + "version": "0.62.1", + "metadata_path": "/home/ts/code/projects/openhcs/.venv/lib/python3.12/site-packages/numba-0.62.1.dist-info/METADATA", + "metadata_sha256": "230bccb44c012be2d4fd260beb7dde0dee69a8ed831202ec317baf2904cfdb43", + "satisfies": true, + "direct_url": null + }, + { + "declared": "skan>=0.13.1", + "name": "skan", + "version": "0.13.1", + "metadata_path": "/home/ts/code/projects/openhcs/.venv/lib/python3.12/site-packages/skan-0.13.1.dist-info/METADATA", + "metadata_sha256": "20fa20f18dc7ebe61ed3e33c54ca3fc6ac9729b1b91c1fbf23e1c11c7890e382", + "satisfies": true, + "direct_url": null + }, + { + "declared": "threadpoolctl>=3.6.0", + "name": "threadpoolctl", + "version": "3.7.0", + "metadata_path": "/home/ts/code/projects/openhcs/.venv/lib/python3.12/site-packages/threadpoolctl-3.7.0.dist-info/METADATA", + "metadata_sha256": "09b19e721560cd12246402d44e53c1ede5194b8398ef60822f02cab86ccf8d1f", + "satisfies": true, + "direct_url": null + }, + { + "declared": "imageio>=2.37.0", + "name": "ImageIO", + "version": "2.37.4", + "metadata_path": "/home/ts/code/projects/openhcs/.venv/lib/python3.12/site-packages/imageio-2.37.4.dist-info/METADATA", + "metadata_sha256": "bdc8cb937fcc650149184c2b1bd58eb53cd62da5c8dca8e7bdde36dd4fce844b", + "satisfies": true, + "direct_url": null + }, + { + "declared": "tifffile>=2025.6.11", + "name": "tifffile", + "version": "2026.9.15", + "metadata_path": "/home/ts/code/projects/openhcs/.venv/lib/python3.12/site-packages/tifffile-2026.9.15.dist-info/METADATA", + "metadata_sha256": "24f89e0953d6223dc1e6c900440ab1d0889ebcfc7a6fdf18fd0b2bfd17dd3a0e", + "satisfies": true, + "direct_url": null + }, + { + "declared": "imagecodecs>=2025.3.30", + "name": "imagecodecs", + "version": "2026.8.16", + "metadata_path": "/home/ts/code/projects/openhcs/.venv/lib/python3.12/site-packages/imagecodecs-2026.8.16.dist-info/METADATA", + "metadata_sha256": "4f2c383e3d47c74b6923e7698e4a7e8c70cdc648517f9f7a052c2a46fea7d4b4", + "satisfies": true, + "direct_url": null + }, + { + "declared": "opencv-python-headless>=4.11.0.86; platform_system != 'Darwin' or platform_machine != 'x86_64'", + "name": "opencv-python-headless", + "version": "5.0.0.93", + "metadata_path": "/home/ts/code/projects/openhcs/.venv/lib/python3.12/site-packages/opencv_python_headless-5.0.0.93.dist-info/METADATA", + "metadata_sha256": "f8e9104b4dce58f708d4cc7af95ebd8a5d65a3e36209d42386eb61065ae50e1a", + "satisfies": true, + "direct_url": null + }, + { + "declared": "Multi-Template-Matching>=2.0.1", + "name": "Multi-Template-Matching", + "version": "2.0.1", + "metadata_path": "/home/ts/code/projects/openhcs/.venv/lib/python3.12/site-packages/Multi_Template_Matching-2.0.1.dist-info/METADATA", + "metadata_sha256": "4300a267f2844ffcba254daf8c49794f1743bb62dc2f9a8ae3e80126c5422675", + "satisfies": true, + "direct_url": null + }, + { + "declared": "roifile>=2024.5.24", + "name": "roifile", + "version": "2026.7.30", + "metadata_path": "/home/ts/code/projects/openhcs/.venv/lib/python3.12/site-packages/roifile-2026.7.30.dist-info/METADATA", + "metadata_sha256": "d97777499927cd0cc55192ce2336bf55bf46f599d281589415585640872b7e0d", + "satisfies": true, + "direct_url": null + }, + { + "declared": "PyYAML>=6.0.2", + "name": "PyYAML", + "version": "6.0.3", + "metadata_path": "/home/ts/code/projects/openhcs/.venv/lib/python3.12/site-packages/pyyaml-6.0.3.dist-info/METADATA", + "metadata_sha256": "03c3b415ed38d09faedc49360e930769b40c58f68585e6de00e2e4916a858d34", + "satisfies": true, + "direct_url": null + }, + { + "declared": "zarr>=3.0.10", + "name": "zarr", + "version": "3.4.0", + "metadata_path": "/home/ts/code/projects/openhcs/.venv/lib/python3.12/site-packages/zarr-3.4.0.dist-info/METADATA", + "metadata_sha256": "771c1d0961d674af66f257d5634564813cb8b312f2db6534d82ccd4645a57fd3", + "satisfies": true, + "direct_url": null + }, + { + "declared": "ome-zarr>=0.12.2", + "name": "ome-zarr", + "version": "0.19.2", + "metadata_path": "/home/ts/code/projects/openhcs/.venv/lib/python3.12/site-packages/ome_zarr-0.19.2.dist-info/METADATA", + "metadata_sha256": "14427033c8a9eb406332bba5e28b4c11dcc29486249661f4937a55ac7fe2ccc1", + "satisfies": true, + "direct_url": null + }, + { + "declared": "dill>=0.4.0", + "name": "dill", + "version": "0.4.1", + "metadata_path": "/home/ts/code/projects/openhcs/.venv/lib/python3.12/site-packages/dill-0.4.1.dist-info/METADATA", + "metadata_sha256": "040d6c84eddf29c8c14dc2aef214962a919803f557243824dd4f8aa3b49e3868", + "satisfies": true, + "direct_url": null + }, + { + "declared": "setuptools", + "name": "setuptools", + "version": "84.0.0", + "metadata_path": "/home/ts/code/projects/openhcs/.venv/lib/python3.12/site-packages/setuptools-84.0.0.dist-info/METADATA", + "metadata_sha256": "3ef2975718dd31cabdd5829455e9b868ca14dbf79ed8fed2de0af4e87347006e", + "satisfies": true, + "direct_url": null + }, + { + "declared": "packaging>=21.0", + "name": "packaging", + "version": "26.3", + "metadata_path": "/home/ts/wt/openhcs-paired-raw-installed-parent-20261001/.venv/lib/python3.12/site-packages/packaging-26.3.dist-info/METADATA", + "metadata_sha256": "70fdb89fc4d4a9a043bf7372b8972bcc883fddff34ab55e9cf80d73875384763", + "satisfies": true, + "direct_url": null + }, + { + "declared": "watchdog>=6.0.0", + "name": "watchdog", + "version": "6.0.0", + "metadata_path": "/home/ts/code/projects/openhcs/.venv/lib/python3.12/site-packages/watchdog-6.0.0.dist-info/METADATA", + "metadata_sha256": "ecfde023047e7761dce06d4488abcede89e398399961a052f2fccb39b001e279", + "satisfies": true, + "direct_url": null + }, + { + "declared": "portalocker>=2.8.2", + "name": "portalocker", + "version": "4.3.2", + "metadata_path": "/home/ts/code/projects/openhcs/.venv/lib/python3.12/site-packages/portalocker-4.3.2.dist-info/METADATA", + "metadata_sha256": "9637e8e083433b42f8f695fc6206de830259552b8aa99c5430ddcfa35203f764", + "satisfies": true, + "direct_url": null + }, + { + "declared": "tomlkit>=0.13.3", + "name": "tomlkit", + "version": "0.15.1", + "metadata_path": "/home/ts/wt/openhcs-paired-raw-installed-parent-20261001/.venv/lib/python3.12/site-packages/tomlkit-0.15.1.dist-info/METADATA", + "metadata_sha256": "752d0210341ae500e12ae384ebb8c287a76a00cbac2d41eb86aba2622dabaaad", + "satisfies": true, + "direct_url": null + }, + { + "declared": "tqdm>=4.66.5", + "name": "tqdm", + "version": "4.70.1", + "metadata_path": "/home/ts/code/projects/openhcs/.venv/lib/python3.12/site-packages/tqdm-4.70.1.dist-info/METADATA", + "metadata_sha256": "a5da70b966afea3a759716041bad7187f2ece02c22348cf9e7c0a512bbf15f57", + "satisfies": true, + "direct_url": null + }, + { + "declared": "psutil>=5.9.0", + "name": "psutil", + "version": "7.2.2", + "metadata_path": "/home/ts/code/projects/openhcs/.venv/lib/python3.12/site-packages/psutil-7.2.2.dist-info/METADATA", + "metadata_sha256": "525f8ba78523cf7bc3759d62a105e7c57fd679ea702402b9449fb0746aa0217f", + "satisfies": true, + "direct_url": null + }, + { + "declared": "zmqruntime>=0.3.0,<0.4", + "name": "zmqruntime", + "version": "0.3.1", + "metadata_path": "/home/ts/wt/openhcs-paired-raw-installed-parent-20261001/.venv/lib/python3.12/site-packages/zmqruntime-0.3.1.dist-info/METADATA", + "metadata_sha256": "b8cb766f67ade113b834cdd150dbcca4c7b0c30cb7bab6c09a6fee17b5f4160c", + "satisfies": true, + "direct_url": { + "archive_info": { + "hash": "sha256=b431eac28214dad42df61b44be25b51d30b8ff11181383e614607214adcd735f", + "hashes": { + "sha256": "b431eac28214dad42df61b44be25b51d30b8ff11181383e614607214adcd735f" + } + }, + "url": "file:///home/ts/wt/openhcs-knowledge-declaration-source-376-20261001/docs/validation/required-runtime-qt-publication-20261002/zmqruntime-031-public/zmqruntime-0.3.1-py3-none-any.whl" + } + }, + { + "declared": "pycodify>=0.1.3,<0.2", + "name": "pycodify", + "version": "0.1.3", + "metadata_path": "/home/ts/wt/basicpy-live-candidate-20260930/.venv/lib/python3.12/site-packages/pycodify-0.1.3.dist-info/METADATA", + "metadata_sha256": "8ef965bfa6fbe90d32fe1af993c0e1f65cfdea760ea9a8b123d8d153e0bbba91", + "satisfies": true, + "direct_url": { + "archive_info": { + "hash": "sha256=7c4548043437767b5de6d8fc9ccf15a418072262ae6746cc1716cb16388139b1", + "hashes": { + "sha256": "7c4548043437767b5de6d8fc9ccf15a418072262ae6746cc1716cb16388139b1" + } + }, + "url": "file:///home/ts/wt/openhcs-issue-batch-20260929/basicpy-live-wheel-20260930/wheels/pycodify-0.1.3-py3-none-any.whl" + } + }, + { + "declared": "objectstate>=1.1.8,<2", + "name": "objectstate", + "version": "1.1.8", + "metadata_path": "/home/ts/wt/basicpy-live-candidate-20260930/.venv/lib/python3.12/site-packages/objectstate-1.1.8.dist-info/METADATA", + "metadata_sha256": "41f52d715d522eb7c58cdf94756204c4fbc4d17cb95ed6b54fd9739c08b9f754", + "satisfies": true, + "direct_url": { + "archive_info": { + "hash": "sha256=6aa2121e10cec25881b7b651cb69e251d3e232e239306df89b7ac66156a2e031", + "hashes": { + "sha256": "6aa2121e10cec25881b7b651cb69e251d3e232e239306df89b7ac66156a2e031" + } + }, + "url": "file:///home/ts/wt/openhcs-issue-batch-20260929/basicpy-live-wheel-20260930/wheels/objectstate-1.1.8-py3-none-any.whl" + } + }, + { + "declared": "python-introspect>=0.1.14,<0.2", + "name": "python-introspect", + "version": "0.1.14", + "metadata_path": "/home/ts/wt/basicpy-live-candidate-20260930/.venv/lib/python3.12/site-packages/python_introspect-0.1.14.dist-info/METADATA", + "metadata_sha256": "61b29867e49e89d07428e12957fb3af7233f017b6dc419e0e8c78abd434c985e", + "satisfies": true, + "direct_url": { + "archive_info": { + "hash": "sha256=1cefc9ca0f019775e332aa56ea5911093aa2234b7ed93a8b25a3ce045fe41e27", + "hashes": { + "sha256": "1cefc9ca0f019775e332aa56ea5911093aa2234b7ed93a8b25a3ce045fe41e27" + } + }, + "url": "file:///home/ts/wt/openhcs-issue-batch-20260929/basicpy-live-wheel-20260930/wheels/python_introspect-0.1.14-py3-none-any.whl" + } + }, + { + "declared": "metaclass-registry>=0.2.2,<0.3", + "name": "metaclass-registry", + "version": "0.2.2", + "metadata_path": "/home/ts/wt/openhcs-paired-raw-installed-parent-20261001/.venv/lib/python3.12/site-packages/metaclass_registry-0.2.2.dist-info/METADATA", + "metadata_sha256": "469bef0c941eb11c7d205ab72d438b2004c559f4157a420d4d755dd499f5cc1f", + "satisfies": true, + "direct_url": { + "archive_info": { + "hash": "sha256=67ae17c58d9d10cd58acf83ebe01833c1f1fe0a1a2e281924fab836958d592ed", + "hashes": { + "sha256": "67ae17c58d9d10cd58acf83ebe01833c1f1fe0a1a2e281924fab836958d592ed" + } + }, + "url": "file:///home/ts/wt/openhcs-knowledge-declaration-source-376-20261001/docs/validation/metaclass-registry-022-publication-20261001/artifacts/metaclass_registry-0.2.2-py3-none-any.whl" + } + }, + { + "declared": "arraybridge>=0.3.6,<0.4", + "name": "arraybridge", + "version": "0.3.6", + "metadata_path": "/home/ts/wt/openhcs-paired-raw-installed-parent-20261001/.venv/lib/python3.12/site-packages/arraybridge-0.3.6.dist-info/METADATA", + "metadata_sha256": "b8e48f00df1bd1fa3149e109b25085cafd1584947bb2e0b54d62fd4410ab2d3a", + "satisfies": true, + "direct_url": { + "archive_info": { + "hash": "sha256=f7fa23f5dcf1925742592109b396fe17662fb56f15e1755a304a45c90e79a249", + "hashes": { + "sha256": "f7fa23f5dcf1925742592109b396fe17662fb56f15e1755a304a45c90e79a249" + } + }, + "url": "file:///home/ts/wt/openhcs-issue-batch-20260929/artifact-parent-20261001/arraybridge-036-public/arraybridge-0.3.6-py3-none-any.whl" + } + }, + { + "declared": "polystore>=0.3.0,<0.4", + "name": "polystore", + "version": "0.3.1", + "metadata_path": "/home/ts/wt/openhcs-paired-raw-installed-parent-20261001/.venv/lib/python3.12/site-packages/polystore-0.3.1.dist-info/METADATA", + "metadata_sha256": "83f0489c548279806d82fc90c7dc2e2e3026ae23064b5a8dd8a7b53f0b8378de", + "satisfies": true, + "direct_url": null + }, + { + "declared": "pyqt-reactive>=0.3.25,<0.4", + "name": "pyqt-reactive", + "version": "0.3.25", + "metadata_path": "/home/ts/wt/openhcs-paired-raw-installed-parent-20261001/.venv/lib/python3.12/site-packages/pyqt_reactive-0.3.25.dist-info/METADATA", + "metadata_sha256": "99b3336f135988c3b3d00f6a8655889741a53d5f7d8f0c0645a0df82381f1b19", + "satisfies": true, + "direct_url": { + "archive_info": { + "hash": "sha256=75d6df7d8d22ed7649b14b46b04ba3617070d20196c215beead7f602328f9083", + "hashes": { + "sha256": "75d6df7d8d22ed7649b14b46b04ba3617070d20196c215beead7f602328f9083" + } + }, + "url": "file:///home/ts/wt/openhcs-knowledge-declaration-source-376-20261001/docs/validation/required-runtime-qt-publication-20261002/pyqt-reactive-0325-public/pyqt_reactive-0.3.25-py3-none-any.whl" + } + }, + { + "declared": "mcp>=1.28,<2", + "name": "mcp", + "version": "1.29.0", + "metadata_path": "/home/ts/code/projects/openhcs/.venv/lib/python3.12/site-packages/mcp-1.29.0.dist-info/METADATA", + "metadata_sha256": "bc4b33ed1eea597cacf79c959094a51f127407cd82f776cb3430a7e7a92a8333", + "satisfies": true, + "direct_url": null + } + ] +} diff --git a/validation/registration-observation-567/registration_observation_probe_567.py b/validation/registration-observation-567/registration_observation_probe_567.py new file mode 100644 index 0000000000..018eb1bbc4 --- /dev/null +++ b/validation/registration-observation-567/registration_observation_probe_567.py @@ -0,0 +1,4 @@ +@numpy +def registration_observation_probe_567(image): + """Engineering declaration only; this receiving case never executes images.""" + return image diff --git a/validation/registration-observation-pr-body.md b/validation/registration-observation-pr-body.md new file mode 100644 index 0000000000..2fc49aaee5 --- /dev/null +++ b/validation/registration-observation-pr-body.md @@ -0,0 +1,41 @@ +## Addresses #566: working source checkpoint + +Preserve the actual registration cause and returned typed observation handle. Observe that exact source/process through the existing native declaration/export registry and persisted-source manager; no new operation ledger, source evaluation, catalog warmup, registration replay, timeout change or competing codec. + +Confirmed native registration survives a later local ephemeral projection failure. The old generic uncertainty exception and its cause-erasing consumers are deleted. Existing operation-handle ancestor owns process authority; the source owner owns the shared byte digest; handle member validates the source/store. One new declared capability automatically exposes `openhcs_get_custom_function_registration_status` through the original MCP/control families. No generic consumer roster edits. + +Root394 execution/compiler/materialization stays separate (public coordination comment5974588121; head eac856066 checked). #561 owns resource admission. No frozen science package or foreign gitlink changed. The original H002 uncertain request is retained and was never replayed. + +## Actual qualification + +- Initial product qualified at **5c4b5adf0**; one parent-requested typed registration-renderer correction is qualified at **a799352b9** (details below). +- Original source batch: **61 PASS / 3 FAIL**; all three stopped pre-operation on missing `EndpointStartupStatus.callback_scope` in preserved foreign ZMQRuntime. Original failures retained. +- Parent identified the existing paired installed ZMQRuntime backing; read-only original bootstrap selection logs exact imported source/API/SHA. No installation/download/progress bypass. +- Affected source batch: **6 PASS / 78 deselected**, **8.646s / 206.4MiB / Swap0**. Actual `build_server`/`call_tool` preserves native cause, returns handle and invokes status with exactly one registration dispatch through an in-process fixture client/native original router. Real manager persisted/ephemeral publication, changed-source, stale-owner and persisted-only controls pass without lazy load/evaluation. +- Complete corrected original parser census: **704 OpenHCS +32 ZMQRuntime +12 python-introspect +6 metaclass-registry, 0 parse omissions**, 192.2MiB. Earlier fixed-limit OOM and incomplete root-file censuses retained and explicitly corrected, not hidden. This dependency census is the foreign source revisions, not the separately identity-verified paired installed backing. +- Historical original R0 at5c4 measures all ten changed production files. Current whole-candidate R0 versus merged main4f9: zero switches/arms, chains, foreign absence probes and codec growth; **NOT all-zero:** -8 literal gets, +4 None checks, +1 isinstance, +3 broad except, +7 classes/+1 enum. Exact historical/current deltas retained, no omission/waiver or global R1 claim. + +Receipt: [custom-registration-observation-20261003.rst](docs/validation/custom-registration-observation-20261003.rst). +Archive: [byte-exact source/raw evidence](docs/validation/custom-registration-observation-20261003.tar.gz), 3,570,775 bytes; SHA256 `442b8996dc579ba7bda521059ea33acac84acfc8351e16d6b51e5b6fda0f1ee0`. + +## Remaining + +Installed public TCP registration -> observation and successful one-registration proof remain parent receiving acceptance. Current source MCP checks use a controlled in-process client, not a new native runtime. Remaining ratchet-positive cause/error sites remain visible for review; draft does not claim full R0/R1 closure. Original UNKNOWN does not become failure/nonmutation or permission to retry. + +CI deferred; no hosted-CI or unchanged-test ceremony. Does not auto-close historical uncertain inputs or claim scientific success. + +### Installed receiving packet (prepared, no launch) + +[Exact one-request TCP packet](validation/registration-observation-567/RECEIVING.rst) and [original retained CLI command templates](validation/registration-observation-567/PUBLIC.commands) are published, with a frozen154-byte declaration. One source-bearing registration, same-process handle/status; no viewer, science, pipeline or second negative/retry. Evidence reserve <=64MiB through the original resource owner. + +Planck confirmed #554 package04 and whole-main10 do **not** contain unmerged #567. At parent direction, main **4f9e1082** was normally merged into this branch at **45bb6fc90**, carrying569/572/current APIs. Nine567 production files remain byte-identical to qualified5c4b; the one renderer correction is source-qualified separately. Original operations/packaged guides match that merged main. No conflict/rebase/foreign checkout edit. + +[Exact paired dependency inputs](validation/registration-observation-567/DEPENDENCIES.rst) and a declaration-derived metadata observation are published for **Dewey's original offline whole-candidate builder**, one ordinary package for both client and native. Source tests are not rerun merely for unchanged bytes; current installed/native qualification remains required. No build/install/overlay here. Planck has first94 viewer receiving; Dewey owns actual original FUND/launch handoff after exact closure. #541 remains Singer's separately owned typed pixel/unit work with Root shared-frame dependency. + +### Parent BOUND-2 renderer correction, qualified + +`CustomFunctionRegistrationRenderer` now inherits existing `McpDevTypedOutputRenderer`. The ancestor owns `McpDevToolBatchResponse.for_rendering`, original DTO decoding/declaration dispatch and `McpDiagnosticRenderer`; the leaf only formats `CustomFunctionRegistrationResult`. All ten raw `.get` reads in this ONE renderer are deleted, including function-entry and error/handle reads. No other knowledge renderer or new codec/registry changed. The original single-tool command fits that existing ancestor; `payload_for` takes its declared capability (not a DTO type), independently exercised in controls. + +**3 PASS / 27 deselected**, final combined controls/AST/R0 **6.719s / 160.4MiB / Swap0 / terminal0**. Original causes, counts0/1, full handle, confirmed-native local-projection wording, function/path/lifetime/next commands preserved; errors print once. Original parser:33 MCP modules/zero parse omissions; broader prior family census remains retained. Narrow correction R0: **gets -10 / None checks -1 / all other counts zero**. Archive [exact correction source/raw evidence](docs/validation/custom-registration-renderer-20261003.tar.gz),890,567B, SHA256 `35f16c072659d300fcace7fa89c694ae07558f8ea908714de1a21f77b6b8b503`. + +Candidate2b391 was superseded **BEFORE build**: Dewey confirmed no ADMIN567/build/materialization/client/native had started and released the source borrow. The corrected published head is the only whole-candidate input; original archives/failures/UNKNOWN remain unchanged. No installedTCP readiness claim. diff --git a/validation/registration_observation_ast.py b/validation/registration_observation_ast.py new file mode 100644 index 0000000000..fa260f625f --- /dev/null +++ b/validation/registration_observation_ast.py @@ -0,0 +1,51 @@ +"""Thin source-family caller of the original audit Package parser.""" +import ast +import json +from pathlib import Path +import sys + +sys.path.insert(0, "/home/ts/.codex/skills/refactor-audit/scripts") +from audit.findings import Package +from audit.repository import Repository + + +class SourceScopeRepository(Repository): + """Let the original Package parser accept a single-file bounded scope.""" + + def python_files(self, revision, root): + if root.endswith('.py'): + return (root,) + return super().python_files(revision, root) + + +roots = ( + (Path.cwd(), "openhcs"), + (Path.cwd() / "external/zmqruntime", "src/zmqruntime"), + (Path.cwd() / "external/python-introspect", "src/python_introspect"), + (Path.cwd() / "external/metaclass-registry", "src/metaclass_registry"), +) +terms = ( + "CustomFunctionRegistration", "register_custom_function", + "CustomFunctionRuntimeRegistry", "CustomFunctionSource", + "FunctionCatalogControlResponse", "AgentFacingErrorMixin", + "class AgentError", "class FunctionCatalogPreparationHandle", +) +for path, root in roots: + repo = SourceScopeRepository(path) + revision = repo.git("rev-parse", "HEAD").strip() + # Same complete tracked source set, loaded serially through Package's owner. + scopes = sorted({ + str(Path(root) / Path(source).relative_to(root).parts[0]) + for source in repo.python_files(revision, root) + }) + for scope in scopes: + package = Package.load(repo, revision, scope) + print(json.dumps({"root": str(path), "scope": scope, "revision": revision, + "parsed": len(package.modules), "unparsed": package.unparsed}), flush=True) + assert not package.unparsed + for module in package.modules: + if path != Path.cwd() or any(term in module.text for term in terms): + print(json.dumps({"root": str(path), "path": module.path, + "ast": ast.dump(module.tree, include_attributes=True)})) + del package +print(json.dumps({"limits": "original parser, full selected module ASTs; not global R1/runtime proof"})) diff --git a/validation/viewer-retirement-519/run_source_controls.py b/validation/viewer-retirement-519/run_source_controls.py new file mode 100644 index 0000000000..13ffe122b7 --- /dev/null +++ b/validation/viewer-retirement-519/run_source_controls.py @@ -0,0 +1,59 @@ +"""Original source bootstrap and pytest, borrowing the existing paired Qt owner.""" +import hashlib +import importlib.util +import json +from pathlib import Path +import sys + +owned = Path(__file__).resolve().parents[2] +peer_target = None +if len(sys.argv) > 2 and sys.argv[1] == '--peer-target': + peer_target = Path(sys.argv[2]).resolve(strict=True) + del sys.argv[1:3] + # Explicit read-only peer backing, matching Planck's source04 bootstrap. + # Product modules below come from this checkout, not the installed target. + sys.path.insert(0, str(peer_target)) + +import openhcs # Activate original source dependencies before their consumers. + +if len(sys.argv) > 2 and sys.argv[1] == '--dependency-root': + dependency_root = Path(sys.argv[2]).resolve(strict=True) + del sys.argv[1:3] + # Select an explicitly borrowed original package before any of its consumers. + # Restore the source bootstrap's other external priorities after this import. + sys.path.insert(0, str(dependency_root)) + import zmqruntime.startup as startup + sys.path.remove(str(dependency_root)) + assert Path(startup.__file__).is_relative_to(dependency_root) + assert callable(startup.EndpointStartupStatus.callback_scope) + print(json.dumps({'borrowed_dependency': startup.__file__, + 'sha256': hashlib.sha256(Path(startup.__file__).read_bytes()).hexdigest(), + 'class_module': startup.EndpointStartupStatus.__module__, + 'required_api': 'EndpointStartupStatus.callback_scope'}), flush=True) + +paired = Path('/home/ts/wt/pyqt-reactive-render-complete-snapshot-20261001/src') +sys.path.insert(0, str(paired)) +import pyqt_reactive.services.window_snapshot as snapshot +assert Path(snapshot.__file__).is_relative_to(paired) +assert snapshot.WindowSnapshotFrameCondition.RENDER_COMPLETE +if peer_target is not None: + assert Path(openhcs.__file__).is_relative_to(peer_target) + import openhcs.runtime + for name in ('viewer_controls', 'napari_streaming_handlers', 'napari_viewer_server'): + path = owned / 'openhcs' / 'runtime' / (name + '.py') + spec = importlib.util.spec_from_file_location('openhcs.runtime.' + name, path) + module = importlib.util.module_from_spec(spec) + sys.modules[spec.name] = module + spec.loader.exec_module(module) + print(json.dumps({'source_module': spec.name, 'path': str(path), + 'sha256': hashlib.sha256(path.read_bytes()).hexdigest()}), flush=True) +print(json.dumps({"openhcs": openhcs.__file__, "paired_snapshot": snapshot.__file__, + "paired_snapshot_sha256": hashlib.sha256(Path(snapshot.__file__).read_bytes()).hexdigest(), + "mode": "source fixtures only; no server/installed change"}), flush=True) +import pytest +status = pytest.main(['--noconftest', '--import-mode=importlib', '-p', 'no:cacheprovider', + '-o', 'addopts=', '-q', *sys.argv[1:]]) +cgroup = Path('/sys/fs/cgroup' + Path('/proc/self/cgroup').read_text().strip().split(':', 2)[2]) +for field in ('memory.max', 'memory.peak', 'memory.swap.max', 'memory.swap.peak', 'memory.events', 'cpu.max'): + print(field + ': ' + (cgroup / field).read_text().replace('\n', ' '), flush=True) +raise SystemExit(status)