From 9380db5bb82ae900253a713b6df46287a53b780c Mon Sep 17 00:00:00 2001 From: "onexbot-occ-writer[bot]" <307849072+onexbot-occ-writer[bot]@users.noreply.github.com> Date: Mon, 28 Sep 2026 19:47:02 -0400 Subject: [PATCH 1/2] fix(OMN-19941): an unobserved planned focused-tests step fails the verdict, and INDETERMINATE is pinned failing in the pr-head verifier Onex-Lane: regr-a7-2330 Onex-Session: 0087a41b0dd9467bac680b04d3ba0934 --- .../handlers/handler_lab_proof_verdict.py | 16 +++-- ...est_indeterminate_fails_closed_omn19941.py | 36 ++++++++++++ .../test_handler_lab_proof_verdict.py | 58 ++++++++++++++++++- 3 files changed, 105 insertions(+), 5 deletions(-) create mode 100644 tests/unit/ci/test_indeterminate_fails_closed_omn19941.py diff --git a/src/omnibase_infra/nodes/node_lab_proof_plan_compute/handlers/handler_lab_proof_verdict.py b/src/omnibase_infra/nodes/node_lab_proof_plan_compute/handlers/handler_lab_proof_verdict.py index 014ac21ece..f26b93ed40 100644 --- a/src/omnibase_infra/nodes/node_lab_proof_plan_compute/handlers/handler_lab_proof_verdict.py +++ b/src/omnibase_infra/nodes/node_lab_proof_plan_compute/handlers/handler_lab_proof_verdict.py @@ -22,7 +22,7 @@ ``restored`` (the zero-residue readback with its positive control) is reported beside the outcome and never changes it. -Ticket: OMN-19572 +Tickets: OMN-19572, OMN-19941 """ from __future__ import annotations @@ -43,6 +43,7 @@ from omnibase_infra.lab_proof.model_lab_proof_observation import ( ModelLabProofObservation, ) +from omnibase_infra.lab_proof.model_lab_proof_plan import ModelLabProofPlan from omnibase_infra.lab_proof.model_lab_proof_result import ModelLabProofResult from omnibase_infra.lab_proof.model_lab_proof_run_report import ( ModelLabProofRunReport, @@ -144,7 +145,7 @@ def handle(self, request: ModelLabProofVerdictRequest) -> ModelLabProofResult: base = None reasons: list[str] = [] checks = tuple( - self._relative_to_base(self._check(check, report), base, reasons) + self._relative_to_base(self._check(check, plan, report), base, reasons) for check in request.mandatory_checks ) failed = tuple(result.check for result in checks if not result.passed) @@ -285,7 +286,10 @@ def _relative_to_base( ) def _check( - self, check: EnumLabProofCheck, report: ModelLabProofRunReport + self, + check: EnumLabProofCheck, + plan: ModelLabProofPlan, + report: ModelLabProofRunReport, ) -> ModelLabProofCheckResult: def result(passed: bool, detail: str) -> ModelLabProofCheckResult: return ModelLabProofCheckResult(check=check, passed=passed, detail=detail) @@ -306,8 +310,12 @@ def both( if check is _C.SUBJECT_HEAD_IDENTITY: return single(_ID.SUBJECT_REV) if check is _C.FOCUSED_TESTS: - if report.get(_ID.FOCUSED_TESTS) is None: + if not any(step.step_id is _ID.FOCUSED_TESTS for step in plan.steps): return result(True, "n/a: the PR changes no test files") + if report.get(_ID.FOCUSED_TESTS) is None: + return result( + False, "undecidable: planned focused_tests has no observation" + ) return single(_ID.FOCUSED_TESTS) if check is _C.MIGRATION_GATE_HEALTHY: return single(_ID.HEALTH_MIGRATION_GATE) diff --git a/tests/unit/ci/test_indeterminate_fails_closed_omn19941.py b/tests/unit/ci/test_indeterminate_fails_closed_omn19941.py new file mode 100644 index 0000000000..09fbab8bd9 --- /dev/null +++ b/tests/unit/ci/test_indeterminate_fails_closed_omn19941.py @@ -0,0 +1,36 @@ +# SPDX-FileCopyrightText: 2025 OmniNode.ai Inc. +# SPDX-License-Identifier: MIT +"""OMN-19941: INDETERMINATE mandatory checks never authorize a PR-head receipt.""" + +import pytest + +from scripts.ci import lab_pass_receipt as receipts +from tests.unit.ci.test_pr_head_lab_receipt_omn19566 import _receipt, _verify + +pytestmark = pytest.mark.unit + + +def test_indeterminate_mandatory_check_with_fail_result_is_refused() -> None: + check = receipts.ModelLabPassCheck.indeterminate_check("unit", "run undecidable") + receipt = _receipt(checks=(check,)) + assert check.outcome is receipts.EnumLabPassCheckOutcome.INDETERMINATE + assert receipt.result is receipts.EnumLabPassResult.FAIL + + verdict, _ = _verify(receipt) + + assert verdict is receipts.EnumPrHeadVerdict.RESULT_NOT_PASS + assert verdict is not receipts.EnumPrHeadVerdict.ACCEPTED + + +def test_indeterminate_mandatory_check_with_forced_pass_result_is_refused() -> None: + check = receipts.ModelLabPassCheck.indeterminate_check("unit", "run undecidable") + receipt = _receipt(checks=(check,)) + # Frozen dataclass equivalent of model_construct: bypass validation to + # prove the verifier independently refuses an inconsistent PASS receipt. + object.__setattr__(receipt, "result", receipts.EnumLabPassResult.PASS) + + verdict, reason = _verify(receipt) + + assert verdict is receipts.EnumPrHeadVerdict.MISSING_MANDATORY_CHECK + assert verdict is not receipts.EnumPrHeadVerdict.ACCEPTED + assert "unit" in reason diff --git a/tests/unit/nodes/node_lab_proof_plan_compute/test_handler_lab_proof_verdict.py b/tests/unit/nodes/node_lab_proof_plan_compute/test_handler_lab_proof_verdict.py index 696fe85a00..c5fe2c14e0 100644 --- a/tests/unit/nodes/node_lab_proof_plan_compute/test_handler_lab_proof_verdict.py +++ b/tests/unit/nodes/node_lab_proof_plan_compute/test_handler_lab_proof_verdict.py @@ -2,7 +2,7 @@ # SPDX-License-Identifier: MIT """The verdict rules: PASS, FAIL, INCONCLUSIVE, DEV_INHERITED, and restored beside them. -Ticket: OMN-19572 +Tickets: OMN-19572, OMN-19941 """ from __future__ import annotations @@ -149,6 +149,62 @@ def test_focused_tests_are_n_a_when_the_pr_changes_none() -> None: assert focused.detail.startswith("n/a") +def test_planned_focused_tests_without_an_observation_fail_closed() -> None: + plan = core_plan() + assert any(step.step_id is _ID.FOCUSED_TESTS for step in plan.steps) + report = report_for(plan) + report = report.model_copy( + update={ + "observations": tuple( + observation + for observation in report.observations + if observation.step_id is not _ID.FOCUSED_TESTS + ) + } + ) + + result = _judge(plan, report) + + focused = next(c for c in result.checks if c.check is _C.FOCUSED_TESTS) + assert not focused.passed + assert "undecidable" in focused.detail.lower() + assert result.outcome is not _O.PASS + + +@pytest.mark.parametrize( + ("ran", "timed_out"), [(False, False), (True, True)], ids=["not-run", "timeout"] +) +def test_mandatory_step_not_run_or_timed_out_never_passes( + ran: bool, timed_out: bool +) -> None: + plan = core_plan() + report = report_for(plan) + report = report.model_copy( + update={ + "observations": tuple( + observation.model_copy( + update={ + "ran": ran, + "timed_out": timed_out, + "exit_code": None, + "expectation_met": False, + "ok": False, + } + ) + if observation.step_id is _ID.FOCUSED_TESTS + else observation + for observation in report.observations + ) + } + ) + + result = _judge(plan, report) + + focused = next(c for c in result.checks if c.check is _C.FOCUSED_TESTS) + assert not focused.passed + assert result.outcome is not _O.PASS + + def test_a_fail_reproduced_at_the_merge_base_is_dev_inherited() -> None: base_plan = core_plan(base_control=True) base = _judge( From 73701a942ea0eb9d2b3421c88f0432d1bb8eb3f0 Mon Sep 17 00:00:00 2001 From: "onexbot-occ-writer[bot]" <307849072+onexbot-occ-writer[bot]@users.noreply.github.com> Date: Mon, 28 Sep 2026 21:59:54 -0400 Subject: [PATCH 2/2] fix(OMN-19941): the lab proof plan node contract records the fail-closed verdict The contract sync gate requires a handler change to touch its node contract. The verdict handler now fails a planned but unobserved step closed; the contract description and ticket list say so. Onex-Lane: regr-a7-2330 Onex-Session: 0087a41b0dd9467bac680b04d3ba0934 --- .../nodes/node_lab_proof_plan_compute/contract.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/omnibase_infra/nodes/node_lab_proof_plan_compute/contract.yaml b/src/omnibase_infra/nodes/node_lab_proof_plan_compute/contract.yaml index 133c0fbcd9..cef96f6628 100644 --- a/src/omnibase_infra/nodes/node_lab_proof_plan_compute/contract.yaml +++ b/src/omnibase_infra/nodes/node_lab_proof_plan_compute/contract.yaml @@ -26,6 +26,7 @@ # this block is what changes. # # Related tickets: +# - OMN-19941: an undecidable verdict input fails closed (a planned but unobserved focused_tests step fails) # - OMN-19572: the omnibase_core foundation_override profile # - OMN-19565: the proof-profile registry # - OMN-19564: epic, a lab proof for every code PR @@ -42,7 +43,7 @@ node_version: patch: 0 node_type: "COMPUTE_GENERIC" description: > - Compute node with two pure handlers. lab_proof.plan renders one proof run from a registry profile row, the subject PR commit, the host's lane directory and the bundle facts below into ordered argv steps with working directories, timeouts, polling and mechanical expectations; it refuses a row that does not run by nodes, a proof kind with no plan, and a subject from another repository. lab_proof.verdict evaluates the row's mandatory checks from the run's observations and decides the outcome, with an optional base-control result that turns a FAIL reproduced at the merge base into DEV_INHERITED. + Compute node with two pure handlers. lab_proof.plan renders one proof run from a registry profile row, the subject PR commit, the host's lane directory and the bundle facts below into ordered argv steps with working directories, timeouts, polling and mechanical expectations; it refuses a row that does not run by nodes, a proof kind with no plan, and a subject from another repository. lab_proof.verdict evaluates the row's mandatory checks from the run's observations and decides the outcome, with an optional base-control result that turns a FAIL reproduced at the merge base into DEV_INHERITED. lab_proof.verdict fails a mandatory check closed when a planned step has no observation. input_model: name: "ModelLabProofPlanRequest"