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
3 changes: 0 additions & 3 deletions config/validation/handler_dispatch_entrypoint_baseline.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,6 @@ known_entrypointless:
- contract: node_impact_analyzer_compute
handler: HandlerImpactAnalysis
module: omnibase_infra.nodes.node_impact_analyzer_compute.handlers.handler_impact_analysis
- contract: node_invariant_evaluate_compute
handler: HandlerInvariantEvaluate
module: omnibase_infra.nodes.node_invariant_evaluate_compute.handlers.handler_invariant_evaluate
- contract: node_llm_embedding_effect
handler: HandlerEmbeddingOpenaiCompatible
module: omnibase_infra.nodes.node_llm_embedding_effect.handlers.handler_embedding_openai_compatible
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"node_id": "omnibase_infra.nodes.node_invariant_evaluate_compute",
"receipt_schema": "handflip_proof.v1",
"canonical_handler_module": "omnibase_infra.nodes.node_invariant_evaluate_compute.handlers.handler_invariant_evaluate",
"legacy_handler_module": "omnibase_infra.nodes.node_invariant_evaluate_compute.handlers.handler_invariant_evaluate",
"base_ref": "71ab1bf6deb4252edd9593f547dd5a4b4e60939b",
"preserved_symbols": [
"_new_evaluator",
"HandlerInvariantEvaluate.handler_type",
"HandlerInvariantEvaluate.handler_category"
],
"parity": {
"status": "pass",
"test_ids": [
"tests/unit/nodes/node_invariant_evaluate_compute/test_invariant_evaluate_dispatch_entrypoint_omn14822.py::test_handlers_expose_handle_entrypoint_and_no_legacy_shim",
"tests/unit/nodes/node_invariant_evaluate_compute/test_invariant_evaluate_dispatch_entrypoint_omn14822.py::test_real_dispatch_callback_reaches_handle[E1_field_pass]",
"tests/unit/nodes/node_invariant_evaluate_compute/test_invariant_evaluate_dispatch_entrypoint_omn14822.py::test_real_dispatch_callback_reaches_handle[E2_field_fail]",
"tests/unit/nodes/node_invariant_evaluate_compute/test_invariant_evaluate_dispatch_entrypoint_omn14822.py::test_real_dispatch_callback_reaches_handle[E3_threshold_pass]",
"tests/unit/nodes/node_invariant_evaluate_compute/test_invariant_evaluate_dispatch_entrypoint_omn14822.py::test_selected_corpus_behavior_equivalence"
],
"selected_input_hashes": [
"sha256:9e2e2ac7be0837d02bc93096520f8f912d09a57ef63079feb44b2358ff08d22c",
"sha256:6b70bb828f605f1ff532a3d428c125f0a423eaf18a466f3496c4c055f4a5b73e",
"sha256:81c9ecb1241b362df1f81ddbd81cd5d67b83fac78750ff66d64a011a4f39374e",
"sha256:06d62d280f141d26dff4989272c6c03a365a6d7e91cbca813263e3230cc56184",
"sha256:67994ba911698033ed2b8e4c2d56fa6ad17f443066327e4b7f836a8beb611603",
"sha256:a8158baa3a5f43e7ae66b76a3f26c5536df615030617cb53965ca20f01297ec1"
]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"branch_coverage_pct": 18.42,
"candidate_count": 6,
"coverage_target": 80.0,
"handler_module": "omnibase_infra.nodes.node_invariant_evaluate_compute.handlers.handler_invariant_evaluate",
"handler_module_sha256": "sha256:8ec577f6fac4a3dd1a67a583c77ac165946b14a2f97b0531911b0d8a752d8db7",
"meets_target": false,
"node_id": "omnibase_infra.nodes.node_invariant_evaluate_compute",
"receipt_schema": "adequacy_receipt.v1",
"recorded_at": "2026-07-20T02:52:07.401132+00:00",
"recorder_schema": "adequacy_receipt.v1",
"selected_count": 6,
"selected_input_hashes": [
"sha256:9e2e2ac7be0837d02bc93096520f8f912d09a57ef63079feb44b2358ff08d22c",
"sha256:6b70bb828f605f1ff532a3d428c125f0a423eaf18a466f3496c4c055f4a5b73e",
"sha256:81c9ecb1241b362df1f81ddbd81cd5d67b83fac78750ff66d64a011a4f39374e",
"sha256:06d62d280f141d26dff4989272c6c03a365a6d7e91cbca813263e3230cc56184",
"sha256:67994ba911698033ed2b8e4c2d56fa6ad17f443066327e4b7f836a8beb611603",
"sha256:a8158baa3a5f43e7ae66b76a3f26c5536df615030617cb53965ca20f01297ec1"
],
"uncovered_waiver": {
"reason": "OMN-14822 hand-flips node_invariant_evaluate_compute to canonical def-B (OMN-14781 path): the three thin evaluator delegations move onto HandlerInvariantEvaluate/Batch/All.handle(request), and the shared _new_evaluator helper plus the HandlerInvariantEvaluate classification properties are re-derived byte-identical base_ref<->HEAD by the ratchet from git. Equivalence is proven by that git-verbatim preservation of the handler-module glue plus the green runtime-dispatch + full-corpus parity tests over the selected input corpus, not by a broad branch-coverage claim over the legacy handler body; the real invariant-evaluation logic lives in the untouched InvariantEvaluator (evaluator_invariant.py), whose branches are covered by test_evaluator_invariant_branches.py, so the thin handler module's own branch coverage over the deterministic dispatch corpus is measured but intentionally not the equivalence proof.",
"uncovered_branch_count": 0
},
"volatile_mask": []
}
1 change: 0 additions & 1 deletion scripts/ci/canonical_handler_shape_baseline.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@
"omnibase_infra.nodes.node_emit_daemon_runtime",
"omnibase_infra.nodes.node_github_pr_poller_effect",
"omnibase_infra.nodes.node_impact_analyzer_compute",
"omnibase_infra.nodes.node_invariant_evaluate_compute",
"omnibase_infra.nodes.node_kafka_replay_compute",
"omnibase_infra.nodes.node_ledger_projection_compute",
"omnibase_infra.nodes.node_ledger_projection_compute.handlers",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
# SPDX-FileCopyrightText: 2025 OmniNode.ai Inc.
# SPDX-License-Identifier: MIT
# Related Tickets:
# - OMN-11548: node_invariant_evaluate_compute (original migration)
# - OMN-14822: canonical def-B flip — each operation now routes to its own
# def-B handler class exposing handle(request) -> response (Class-B Tier-1,
# parent epic OMN-14355; hand-flip proof path OMN-14781).
name: "node_invariant_evaluate_compute"
contract_name: "node_invariant_evaluate_compute"
node_name: "node_invariant_evaluate_compute"
contract_version:
major: 1
minor: 0
patch: 0
patch: 1
node_version:
major: 1
minor: 0
Expand All @@ -30,27 +35,21 @@ handler_routing:
- operation: "invariant.evaluate"
handler:
name: "HandlerInvariantEvaluate"
function: "handle_invariant_evaluate"
module: "omnibase_infra.nodes.node_invariant_evaluate_compute.handlers.handler_invariant_evaluate"
type: "async"
input_model: "ModelInvariantEvaluateInput"
output_model: "ModelInvariantResult"
description: "Evaluate one invariant against an output payload."
- operation: "invariant.evaluate_batch"
handler:
name: "HandlerInvariantEvaluate"
function: "handle_invariant_evaluate_batch"
name: "HandlerInvariantEvaluateBatch"
module: "omnibase_infra.nodes.node_invariant_evaluate_compute.handlers.handler_invariant_evaluate"
type: "async"
input_model: "ModelInvariantEvaluateBatchInput"
output_model: "list[ModelInvariantResult]"
description: "Evaluate an invariant set and return ordered results."
- operation: "invariant.evaluate_all"
handler:
name: "HandlerInvariantEvaluate"
function: "handle_invariant_evaluate_all"
name: "HandlerInvariantEvaluateAll"
module: "omnibase_infra.nodes.node_invariant_evaluate_compute.handlers.handler_invariant_evaluate"
type: "async"
input_model: "ModelInvariantEvaluateAllInput"
output_model: "ModelEvaluationSummary"
description: "Evaluate an invariant set and return summary statistics."
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@
"""Handlers for invariant evaluation compute."""

from omnibase_infra.nodes.node_invariant_evaluate_compute.handlers.handler_invariant_evaluate import (
handle_invariant_evaluate,
handle_invariant_evaluate_all,
handle_invariant_evaluate_batch,
HandlerInvariantEvaluate,
HandlerInvariantEvaluateAll,
HandlerInvariantEvaluateBatch,
)

__all__ = [
"handle_invariant_evaluate",
"handle_invariant_evaluate_all",
"handle_invariant_evaluate_batch",
"HandlerInvariantEvaluate",
"HandlerInvariantEvaluateAll",
"HandlerInvariantEvaluateBatch",
]
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# SPDX-FileCopyrightText: 2025 OmniNode.ai Inc.
# SPDX-License-Identifier: MIT

"""Runtime handlers for invariant evaluation compute."""
"""Runtime handlers for invariant evaluation compute (canonical definition B)."""

from __future__ import annotations

Expand All @@ -17,8 +17,15 @@
)


def _new_evaluator(
allowed_import_paths: list[str] | None,
) -> InvariantEvaluator:
"""Create a per-request evaluator to avoid sharing mutable schema cache."""
return InvariantEvaluator(allowed_import_paths=allowed_import_paths)


class HandlerInvariantEvaluate:
"""Handler descriptor for deterministic invariant evaluation operations."""
"""Canonical def-B handler: evaluate one invariant against an output payload."""

@property
def handler_type(self) -> EnumHandlerType:
Expand All @@ -28,49 +35,62 @@ def handler_type(self) -> EnumHandlerType:
def handler_category(self) -> EnumHandlerTypeCategory:
return EnumHandlerTypeCategory.COMPUTE

async def handle(
self, request: ModelInvariantEvaluateInput
) -> ModelInvariantResult:
"""Evaluate one invariant against an output payload."""
evaluator = _new_evaluator(request.allowed_import_paths)
return evaluator.evaluate(request.invariant, request.output)

def _new_evaluator(
allowed_import_paths: list[str] | None,
) -> InvariantEvaluator:
"""Create a per-request evaluator to avoid sharing mutable schema cache."""
return InvariantEvaluator(allowed_import_paths=allowed_import_paths)

class HandlerInvariantEvaluateBatch:
"""Canonical def-B handler: evaluate an invariant set, ordered per-invariant results."""

async def handle_invariant_evaluate(
input_data: ModelInvariantEvaluateInput,
) -> ModelInvariantResult:
"""Evaluate one invariant against an output payload."""
evaluator = _new_evaluator(input_data.allowed_import_paths)
return evaluator.evaluate(input_data.invariant, input_data.output)
@property
def handler_type(self) -> EnumHandlerType:
return EnumHandlerType.COMPUTE_HANDLER

@property
def handler_category(self) -> EnumHandlerTypeCategory:
return EnumHandlerTypeCategory.COMPUTE

async def handle_invariant_evaluate_batch(
input_data: ModelInvariantEvaluateBatchInput,
) -> list[ModelInvariantResult]:
"""Evaluate an invariant set and return ordered per-invariant results."""
evaluator = _new_evaluator(input_data.allowed_import_paths)
return evaluator.evaluate_batch(
input_data.invariant_set,
input_data.output,
enabled_only=input_data.enabled_only,
)
async def handle(
self, request: ModelInvariantEvaluateBatchInput
) -> list[ModelInvariantResult]:
"""Evaluate an invariant set and return ordered per-invariant results."""
evaluator = _new_evaluator(request.allowed_import_paths)
return evaluator.evaluate_batch(
request.invariant_set,
request.output,
enabled_only=request.enabled_only,
)


async def handle_invariant_evaluate_all(
input_data: ModelInvariantEvaluateAllInput,
) -> ModelEvaluationSummary:
"""Evaluate an invariant set and return aggregate summary statistics."""
evaluator = _new_evaluator(input_data.allowed_import_paths)
return evaluator.evaluate_all(
input_data.invariant_set,
input_data.output,
fail_fast=input_data.fail_fast,
)
class HandlerInvariantEvaluateAll:
"""Canonical def-B handler: evaluate an invariant set, aggregate summary statistics."""

@property
def handler_type(self) -> EnumHandlerType:
return EnumHandlerType.COMPUTE_HANDLER

@property
def handler_category(self) -> EnumHandlerTypeCategory:
return EnumHandlerTypeCategory.COMPUTE

async def handle(
self, request: ModelInvariantEvaluateAllInput
) -> ModelEvaluationSummary:
"""Evaluate an invariant set and return aggregate summary statistics."""
evaluator = _new_evaluator(request.allowed_import_paths)
return evaluator.evaluate_all(
request.invariant_set,
request.output,
fail_fast=request.fail_fast,
)


__all__ = [
"HandlerInvariantEvaluate",
"handle_invariant_evaluate",
"handle_invariant_evaluate_all",
"handle_invariant_evaluate_batch",
"HandlerInvariantEvaluateAll",
"HandlerInvariantEvaluateBatch",
]
15 changes: 11 additions & 4 deletions tests/integration/test_omn_11548_invariant_evaluate_compute.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
# SPDX-FileCopyrightText: 2025 OmniNode.ai Inc.
# SPDX-License-Identifier: MIT

"""Integration coverage for the OMN-11548 invariant evaluation compute node."""
"""Integration coverage for the OMN-11548 invariant evaluation compute node.

OMN-14822 flipped the node to canonical def-B: contract routing now binds each
operation to a def-B handler class (``name`` + ``module``, no ``function``), and
the behavior lives on ``HandlerInvariantEvaluate.handle(request) -> response``.
"""

from __future__ import annotations

Expand All @@ -13,7 +18,7 @@
from omnibase_core.enums import EnumInvariantType, EnumSeverity
from omnibase_core.models.invariant import ModelInvariant
from omnibase_infra.nodes.node_invariant_evaluate_compute.handlers import (
handle_invariant_evaluate,
HandlerInvariantEvaluate,
)
from omnibase_infra.nodes.node_invariant_evaluate_compute.models import (
ModelInvariantEvaluateInput,
Expand All @@ -34,9 +39,11 @@ async def test_invariant_evaluate_compute_contract_routes_to_handler() -> None:
handler = contract["handler_routing"]["handlers"][0]["handler"]

assert handler["module"].endswith("handler_invariant_evaluate")
assert handler["function"] == "handle_invariant_evaluate"
assert handler["name"] == "HandlerInvariantEvaluate"
# Canonical def-B routing binds by name+module only; no legacy function field.
assert "function" not in handler

result = await handle_invariant_evaluate(
result = await HandlerInvariantEvaluate().handle(
ModelInvariantEvaluateInput(
invariant=ModelInvariant(
name="Status Present",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# SPDX-FileCopyrightText: 2025 OmniNode.ai Inc.
# SPDX-License-Identifier: MIT

"""Tests for node_invariant_evaluate_compute."""
"""Tests for node_invariant_evaluate_compute (canonical def-B handlers)."""

from __future__ import annotations

Expand All @@ -14,9 +14,9 @@
InvariantEvaluator,
)
from omnibase_infra.nodes.node_invariant_evaluate_compute.handlers import (
handle_invariant_evaluate,
handle_invariant_evaluate_all,
handle_invariant_evaluate_batch,
HandlerInvariantEvaluate,
HandlerInvariantEvaluateAll,
HandlerInvariantEvaluateBatch,
)
from omnibase_infra.nodes.node_invariant_evaluate_compute.models import (
ModelInvariantEvaluateAllInput,
Expand Down Expand Up @@ -55,10 +55,27 @@ def test_node_invariant_evaluate_compute_is_declarative_shell() -> None:
assert isinstance(node, NodeInvariantEvaluateCompute)


@pytest.mark.unit
def test_handlers_expose_canonical_handle_entrypoint() -> None:
"""Every def-B handler must expose a callable ``handle`` dispatch entrypoint.

Auto-wiring's ``_make_dispatch_callback`` binds ``handle_async`` then ``handle``;
a handler exposing neither binds ``_missing_handle`` and raises on first dispatch.
"""
for handler_cls in (
HandlerInvariantEvaluate,
HandlerInvariantEvaluateBatch,
HandlerInvariantEvaluateAll,
):
assert callable(getattr(handler_cls, "handle", None)), (
f"{handler_cls.__name__} exposes no callable handle() entrypoint."
)


@pytest.mark.unit
@pytest.mark.asyncio
async def test_handle_invariant_evaluate_returns_single_result() -> None:
result = await handle_invariant_evaluate(
result = await HandlerInvariantEvaluate().handle(
ModelInvariantEvaluateInput(
invariant=_field_presence_invariant(),
output={"status": "ok"},
Expand All @@ -81,7 +98,7 @@ async def test_handle_invariant_evaluate_batch_preserves_order() -> None:
],
)

results = await handle_invariant_evaluate_batch(
results = await HandlerInvariantEvaluateBatch().handle(
ModelInvariantEvaluateBatchInput(
invariant_set=invariant_set,
output={"status": "ok", "score": 0.8},
Expand All @@ -107,7 +124,7 @@ async def test_handle_invariant_evaluate_all_summarizes_failures() -> None:
],
)

summary = await handle_invariant_evaluate_all(
summary = await HandlerInvariantEvaluateAll().handle(
ModelInvariantEvaluateAllInput(
invariant_set=invariant_set,
output={"score": 0.2},
Expand Down
Loading
Loading