Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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,
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand Down
36 changes: 36 additions & 0 deletions tests/unit/ci/test_indeterminate_fails_closed_omn19941.py
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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(
Expand Down
Loading