Repository navigation
Admit custom-function persistence before dispatch and preserve execution endpoint ownership #230
Description
Verified defect
Custom-function persistence can cross a restricted MCP write-root boundary and
reach an unrelated shared execution endpoint. A timeout does not undo that
mutation or prove it absent.
Integration owner: OpenHCS issue-batch coordinator. Implementation owner:
Lovelace, using the existing function-catalog request/service, execution endpoint
and CustomFunctionManager declarations. No alternate registry or runner.
Reproducer and evidence
Fresh installed OpenHCS source bf9aae4 / PolyStore c16b8fc, MCP0.8.6.
A context-isolated MCP process sets OPENHCS_AGENT_WRITE_ROOTS to its own
persistent output directory and owns an isolated viewer. Call the declared
openhcs_register_custom_function operation with valid source and persist=true.
The request has no explicit endpoint/storage declaration; EndpointFunctionCatalogService
uses its configured endpoint, which is the pre-existing shared7777 endpoint.
The original request returned mcp_tool_failed / TimeoutError after the execution
control boundary's5000ms. The shared endpoint log independently records saving
the submitted custom source under /home/ts/.local/share/openhcs/custom_functions/,
outside the restricted write root, then successful registry insertion/reload.
The shared endpoint was PID1175501 and loaded an older installed-main source;
the requesting MCP was PID1614529 on the new source. No owned5993 execution
endpoint existed at dispatch. The original request was not replayed or treated
as having no side effects. Its persisted file remains preserved and must not be
removed without explicit disposition.
Persistent local evidence: H002 fresh attempt4 receipt033 and the shared server
log entries979–983, 2026-09-29 22:00:08UTC. This issue intentionally omits assay
pixels, parameters, reference outputs and complete submitted scientific source.
Existing ownership chain
- CustomFunctionRegistrationRequest: source_code/persist/compact_signature only.
- RegisterCustomFunctionCapability dispatches directly to context.function_catalog.
- EndpointFunctionCatalogService.register_custom_function obtains _config_provider
endpoint and sends the request; it has no persistence-path admission. - Execution CustomFunctionRegistrationMessageStrategy calls the same catalog owner.
- FunctionCatalogService constructs CustomFunctionManager and calls register_from_code;
storage_dir/source filenames are only reported after persistence.
Acceptance
- An admitted, explicitly selected owned endpoint can register, expose, compile
and execute a tiny synthetic custom function through the real MCP journey. - A restricted write policy rejects ordinary custom persistence outside its
roots before dispatch/evaluation/write. Native metadata/store owners derive
exact paths; no second filename parser or duplicated destination defaults. - Destination-file and ancestor symlink escapes fail before persistence; outside
sentinel bytes and registry membership remain unchanged. - The selected execution/catalog owner and exact destination are observable.
A blind run must not silently mutate a foreign shared namespace to prepare
execution on a different endpoint. - Post-dispatch observation failure preserves uncertainty and observable side
effects; no fallback/replay, timeout increase or claim that failure means no
mutation. Test with controlled delayed responses using the real endpoint. - Preserve existing user custom functions, dirty source and interrupted inputs.
No installed-source edit during an active frozen scientific attempt.
This is ordinary built-in persistence/routing admission, not a claim that
AgentPathPolicy sandboxes arbitrary explicitly authorized Python source.
Activity
trissim commentedon Sep 29, 2026
Additional live reproducer owned by Lovelace in draft #233: synthetic source-pinned attempt01 at81e636ad4, tests/runtime_diagnostics/registration_live_20260929_attempt01/journey.json (SHA256 e41190a4f016d7b64c5618da96c770c7308ed1c8610c36f107b543aad831438f), timed out at the ordinary10s stdio boundary on positive registration call11. Its native control audit shows repeated source-bearing register messages ~100ms apart while catalogue readiness was pending. Root: register_custom_function used the read-only preparation polling/resend helper. The later same-handle read-only observation surfaced an unbuilt C++ extension in this isolated source-test tree; that binary already exists in the installed package. The source-test preparation defect does not excuse mutation resends or imply the original timeout had no side effect. Original inputs/response are preserved; diagnostic handles were closed via supported owned shutdown, with independent PID/listener absence checks. No shared7777 contact or historical033 replay.
Acceptance in this same coherent PR: standard source extension build; existing preparation owner exposed through responsive typed start/status so cold warming can be observed without blocking a write call; registration must not dispatch source until readiness and must send a mutation only once, including pending/timeout/error replies. Main #238 now warms kernels through the existing supervised catalogue future (reported cold preparation53.529s), making an unbounded inline preflight unsuitable. Do not increase ordinary timeouts, mock/skip the production catalogue, or add another preparation registry/lifecycle. Finish real synthetic registration→discovery→compile→execution, then parent-owned merged install and actual installed-entrypoint acceptance. Fresh blind author remains prepared, not activated.
trissim commentedon Sep 29, 2026
Parent live review of PR233 at 4c6fbac: actual source-mode start/status stayed responsive (observed maximum status duration 0.026129203 s) during native kernel preparation. The bounded attempt02 stopped at its 100 s preparation sub-budget before any source registration; same-handle read-only reconciliation subsequently returned READY (~110.5 s after preparation start). All three diagnostic endpoints then shut down successfully, all owned PIDs are absent, and the failed predecessor is preserved. This is not accepted registration/compile/execution or installed readiness. Cold warmup cost remains separate from tool latency and biological accuracy.
Concrete additional boundary defect assigned to Lovelace in this PR: ZMQExecutionClient.custom_function_registration_destination and register_custom_function still call connect(), whose existing runtime implementation can spawn an absent endpoint or replace an unresponsive one. These operations must attach to the explicit existing owner only, as the new preparation controls already do. Acceptance: absent/unresponsive endpoint produces zero launch/replacement and zero source sends under the existing deadline; retain exact owner identity and postdispatch uncertainty. No timeout inflation or competing implementation.
Merge pull request #233 from OpenHCSDev/fix/custom-function-admission…
Merge pull request #242 from OpenHCSDev/fix/registration-installed-di…
trissim commentedon Sep 30, 2026
Implemented and merged in #233, with diagnostic bootstrap #242 and paired typed-progress fix #247/ZMQRuntime #8. Focused admission regressions include symlink/write-boundary protection. Fresh actual installed attempt07 now passes admitted explicit-owner registration, rejected unready/foreign/unsupported destinations, discovery, artifact planning, typed compile/execute completion, exact saved-pixel readback and controlled delayed-receipt uncertainty reconciliation. Exactly two source-bearing RPCs (initial + delayed), no resend; original uncertain shared mutation remains preserved and was not replayed or deleted. Evidence: #247 (comment) . All owned synthetic runtimes closed; independent blind biological acceptance remains separate.