hyf

Context-aware query service for Radroots
git clone https://radroots.dev/git/hyf.git
Log | Files | Refs | README | LICENSE

commit 66cd9c0663c2ee705433a36f5c0d46b5b1ea3598
parent 5f82d3d097b7afa5f40a0d945f7ab0da36c72ce8
Author: triesap <tyson@radroots.org>
Date:   Wed, 23 Sep 2026 03:04:01 +0000

test(hyf): retain unproved reap ownership and add PC03/PC04 controls (C002D review)

Independent read-only review of 5f82d3d returned FAIL with mandatory findings.
Repair within ADR-0017 D37 PC01-PC05; test-only.

PC02: reap() no longer marks the child reaped when the wait/termination is
unproved. A wait_error retains identity/ownership for a retry and records the
uncertainty in the caller-owned ledger; a watchdog termination records cleanup
failure instead of claiming collection. New control drives reap() on a real
owned child with a controlled wait failure, then retries and proves cleanup on
both provider paths.

PC01: the descriptor-read control now executes through BOTH provider reap
paths (real owned child, unavailable report descriptor -> read_error, never a
silent EOF), not only the shared reader.

PC03: add a late-exit control entrypoint (valid JSON, closed stdio, exit past
the declared budget) and prove bounded rejection within the declared budget
with cleanup truth exposed; the cleanup grace is not extra successful work.

Re-verified on final source: provider-helpers 76/76, repo-local-process 9/9,
test-stdio 56/32/24/0 with identical D16 names and reasons, format/architecture/
build green.

Diffstat:
Mtests/jev_provider_helper.mojo | 27++++++++++++++++++++-------
Mtests/max_local_process_helper.mojo | 27++++++++++++++++++++-------
Atests/stdio_late_exit_entrypoint.mojo | 16++++++++++++++++
Mtests/test_provider_helpers.mojo | 66++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtests/test_repo_local_process_contract.mojo | 25++++++++++++++++++++++++-
5 files changed, 146 insertions(+), 15 deletions(-)

diff --git a/tests/jev_provider_helper.mojo b/tests/jev_provider_helper.mojo @@ -363,19 +363,32 @@ struct SpawnedJevStub(Movable): var term = terminate_owned(self.pid, TERMINATION_GRACE_MS) self.state.status = term.copy() self.state.store(False, "watchdog", "-", "timeout", 0, 0) - if not term.cleanup_proved(): + if term.cleanup_proved(): + self.state.reaped = True + self.state.close_reader() + else: self.state.cleanup_error = "unreaped:" + term.describe() self.state.reason = "timeout_unreaped" - self.state.reaped = True - self.state.close_reader() + self.state.ledger.record( + "owned-child cleanup unproved " + term.describe() + ) return - if status.state == "gone" or status.state == "wait_error": - self.state.store(False, "watchdog", "-", status.state, 0, 0) - if status.state == "wait_error": - self.state.cleanup_error = "wait_error:" + status.error + if status.state == "gone": + # No waitable owned child remains: cleanup is proved without a + # signal and the report cannot be trusted, but ownership is done. + self.state.store(False, "watchdog", "-", "gone", 0, 0) self.state.reaped = True self.state.close_reader() return + if status.state == "wait_error": + # Identity/ownership is unproved: retain it for a retry and record + # the uncertainty instead of claiming the child was collected. + self.state.store(False, "watchdog", "-", "wait_error", 0, 0) + self.state.cleanup_error = "wait_error:" + status.error + self.state.ledger.record( + "owned-child wait unproved " + status.describe() + ) + return var report_text = "" var report_error = "" try: diff --git a/tests/max_local_process_helper.mojo b/tests/max_local_process_helper.mojo @@ -468,19 +468,32 @@ struct SpawnedMaxLocalStub(Movable): var term = terminate_owned(self.pid, TERMINATION_GRACE_MS) self.state.status = term.copy() self.state.store(False, "watchdog", "-", "timeout", 0, 0) - if not term.cleanup_proved(): + if term.cleanup_proved(): + self.state.reaped = True + self.state.close_reader() + else: self.state.cleanup_error = "unreaped:" + term.describe() self.state.reason = "timeout_unreaped" - self.state.reaped = True - self.state.close_reader() + self.state.ledger.record( + "owned-child cleanup unproved " + term.describe() + ) return - if status.state == "gone" or status.state == "wait_error": - self.state.store(False, "watchdog", "-", status.state, 0, 0) - if status.state == "wait_error": - self.state.cleanup_error = "wait_error:" + status.error + if status.state == "gone": + # No waitable owned child remains: cleanup is proved without a + # signal and the report cannot be trusted, but ownership is done. + self.state.store(False, "watchdog", "-", "gone", 0, 0) self.state.reaped = True self.state.close_reader() return + if status.state == "wait_error": + # Identity/ownership is unproved: retain it for a retry and record + # the uncertainty instead of claiming the child was collected. + self.state.store(False, "watchdog", "-", "wait_error", 0, 0) + self.state.cleanup_error = "wait_error:" + status.error + self.state.ledger.record( + "owned-child wait unproved " + status.describe() + ) + return var report_text = "" var report_error = "" try: diff --git a/tests/stdio_late_exit_entrypoint.mojo b/tests/stdio_late_exit_entrypoint.mojo @@ -0,0 +1,16 @@ +"""Test-only stdio entrypoint that emits valid JSON, closes stdio and lingers. + +Exercises PC03: a child that finishes its output and then delays exit past the +parent's declared budget must be rejected within that budget, with the bounded +cleanup grace used only for cleanup. +""" + +from std.ffi import c_int, external_call +from std.sys._libc import close + + +def main(): + print('{"ok":true}') + _ = close(c_int(1)) + _ = close(c_int(2)) + _ = external_call["usleep", c_int](c_int(1500000)) diff --git a/tests/test_provider_helpers.mojo b/tests/test_provider_helpers.mojo @@ -1672,6 +1672,72 @@ def test_cleanup_failure_preserves_retryable_ownership() raises: assert_true(pid_not_waitable(jev_actual)) +def test_reap_wait_error_retains_ownership_both_providers() raises: + # PC02: an unproved/uncertain wait consumed by reap() must not mark the + # child collected or discard retryable ownership; a later retry with the + # restored exact identity still collects it. + var recorded = List[String]() + var ledger = CleanupLedger(UnsafePointer(to=recorded)) + var stub = spawn_max_local_stub(0, "count_requests", 1, 2000, ledger) + var actual = stub.pid + stub.pid = 0 + stub.reap() + assert_true(not stub.ok()) + assert_true(not stub.status().cleanup_proved()) + assert_equal(len(recorded), 1) + stub.pid = actual + stub.cleanup() + assert_true(stub.status().cleanup_proved()) + assert_true(pid_not_waitable(actual)) + + var jev_stub = spawn_jev_stub_auto("ok", 1, 2000, ledger) + var jev_actual = jev_stub.stub.pid + jev_stub.stub.pid = 0 + jev_stub.stub.reap() + assert_true(not jev_stub.stub.ok()) + assert_true(not jev_stub.stub.status().cleanup_proved()) + assert_equal(len(recorded), 2) + jev_stub.stub.pid = jev_actual + jev_stub.stub.cleanup() + assert_true(jev_stub.stub.status().cleanup_proved()) + assert_true(pid_not_waitable(jev_actual)) + + +def test_provider_reap_descriptor_read_error_both_paths() raises: + # PC01: the descriptor-read control executes through BOTH provider reap + # paths, not only the shared reader: a real owned child with an unavailable + # report descriptor fails with read_error, never a silent EOF/empty report. + var pipe = make_pipe() + var closed_fd = pipe.read_fd + close_fd(pipe.read_fd) + close_fd(pipe.write_fd) + var pid = fork_pid() + if pid == 0: + child_exit(0) + var state = piped_child_state(pid, closed_fd, 2000, 1, CleanupLedger()) + var stub = SpawnedMaxLocalStub(pid, 0, state^) + stub.reap() + assert_true(not stub.ok()) + assert_equal(stub.reason(), "read_error") + assert_true(pid_not_waitable(pid)) + + var jev_pipe = make_pipe() + var jev_closed_fd = jev_pipe.read_fd + close_fd(jev_pipe.read_fd) + close_fd(jev_pipe.write_fd) + var jev_pid = fork_pid() + if jev_pid == 0: + child_exit(0) + var jev_state = piped_child_state( + jev_pid, jev_closed_fd, 2000, 1, CleanupLedger() + ) + var jev_stub = SpawnedJevStub(jev_pid, jev_state^) + jev_stub.reap() + assert_true(not jev_stub.ok()) + assert_equal(jev_stub.reason(), "read_error") + assert_true(pid_not_waitable(jev_pid)) + + def test_cleanup_failure_survives_scope_exit() raises: # PC02: cleanup failure must remain observable after the owning handle is # destroyed at scope exit, not merely stored in an inaccessible object. diff --git a/tests/test_repo_local_process_contract.mojo b/tests/test_repo_local_process_contract.mojo @@ -4,7 +4,7 @@ from safe_tempdir import SafeTempDir from json import Value from fixture_assertions import load_scenario_request_json -from parent_lifecycle import POLLIN, close_fd, make_pipe, write_raw +from parent_lifecycle import POLLIN, close_fd, make_pipe, now_ms, write_raw from stdio_process_helper import ( HYF_PATHS_PROFILE_ENV, HYF_PATHS_REPO_LOCAL_ROOT_ENV, @@ -126,6 +126,29 @@ def test_run_stdio_entrypoint_rejects_unread_request() raises: assert_true(message.find("cleanup_error=") >= 0) +def test_run_stdio_entrypoint_rejects_late_success() raises: + # PC03: a child that emits valid JSON, closes stdio and delays exit past the + # declared budget must be rejected within the budget; the bounded cleanup + # grace is not extra successful work. + var start = now_ms() + var message = "" + try: + _ = run_stdio_entrypoint_with_deadline( + "tests/stdio_late_exit_entrypoint.mojo", "{}", "", "", 1200 + ) + except e: + message = String(e) + var elapsed = now_ms() - start + assert_true(message.find("stdio-entrypoint") >= 0) + assert_true( + message.find("timeout") >= 0 + or message.find("read_deadline_expired") >= 0 + ) + assert_true(message.find("cleanup_error=") >= 0) + assert_true(elapsed >= 1000) + assert_true(elapsed <= 3700) + + def test_run_stdio_entrypoint_classifies_loader_failure() raises: var message = "" try: