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
11 changes: 11 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,17 @@ jobs:
- name: operation_match operation-field gate (OMN-13530)
run: uv run python -m omnibase_core.validators.operation_match_requires_operation src/omnibase_infra

# OMN-14141: every handler_routing.handlers[] entry must carry a nested
# 'handler: {name, module}' mapping. The flat 'handler_class'/
# 'handler_module' schema silently parses to ZERO dispatchers and then
# phantom-wires the subscribed topic (WIRED with no live handler, offsets
# still committed) — the WI-14 root cause (OMN-14139/OMN-14135). Fail-closed
# gate paired with the runtime parse guard in
# auto_wiring/discovery._parse_handler_routing. SYNC with pre-commit hook
# 'handler-routing-schema-gate'.
- name: handler_routing flat-schema gate (OMN-14141)
run: PYTHONPATH=src uv run python -m omnibase_infra.validators.handler_routing_schema src/omnibase_infra

- name: Check handler Any signature gate (OMN-10820)
run: PYTHONPATH=src uv run python -m omnibase_infra.validators.handler_any_signature --max-violations 0 src/omnibase_infra

Expand Down
14 changes: 14 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,20 @@ repos:
files: (^|/)contract\.yaml$
pass_filenames: true
stages: [pre-commit]
# OMN-14141: reject flat handler_class/handler_module handler_routing
# entries. They silently parse to ZERO dispatchers and phantom-wire the
# subscribed topic (WIRED, Kafka offsets committed, no live handler) — the
# WI-14 root cause (OMN-14139/OMN-14135). Every routed handler must use the
# nested handler:{name,module} shape. Fail-closed companion to the runtime
# parse guard in auto_wiring/discovery._parse_handler_routing. SYNC with
# ci.yml step "handler_routing flat-schema gate".
- id: handler-routing-schema-gate
name: handler_routing flat-schema gate (OMN-14141)
entry: env PYTHONPATH=src uv run --frozen python -m omnibase_infra.validators.handler_routing_schema
language: system
files: (^|/)contract\.yaml$
pass_filenames: true
stages: [pre-commit]
# Migration freeze enforcement - block new migrations during DB-per-repo refactor
- id: onex-validate-migration-freeze
name: ONEX Migration Freeze Enforcement
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,9 @@ handler_routing:
routing_strategy: "operation_match"
handlers:
- operation: "baseline.compare"
handler_class: "HandlerBaselineComparison"
handler_module: "omnibase_infra.nodes.node_baseline_comparison_compute.handlers.handler_baseline_comparison"
handler:
name: "HandlerBaselineComparison"
module: "omnibase_infra.nodes.node_baseline_comparison_compute.handlers.handler_baseline_comparison"
description: "Compute cost/outcome deltas and ROI from paired run results"
input_model:
name: "ModelBaselineComparisonInput"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,9 @@ handler_routing:
routing_strategy: "operation_match"
handlers:
- operation: "build_loop.append"
handler_class: "HandlerBuildLoopAppend"
handler_module: "omnibase_infra.nodes.node_build_loop_write_effect.handlers.handler_build_loop_append"
handler:
name: "HandlerBuildLoopAppend"
module: "omnibase_infra.nodes.node_build_loop_write_effect.handlers.handler_build_loop_append"
description: "Append-only INSERT into build_loop_runs"
capabilities:
- name: "build_loop.write"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,9 @@ handler_routing:
default_handler: "omnibase_infra.nodes.node_decision_store_query_compute.handlers.handler_query_decisions:HandlerQueryDecisions"
handlers:
- operation: "decision_store.query_decisions"
handler_class: "HandlerQueryDecisions"
handler_module: "omnibase_infra.nodes.node_decision_store_query_compute.handlers.handler_query_decisions"
handler:
name: "HandlerQueryDecisions"
module: "omnibase_infra.nodes.node_decision_store_query_compute.handlers.handler_query_decisions"
description: >
Cursor-paginated SELECT from decision_store with dynamic WHERE clause, scope_services filter (ANY/ALL/EXACT), and COUNT(*) for total_active.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,10 @@ handler_routing:
routing_strategy: "operation_match"
handlers:
- operation: "lifecycle.update"
handler_class: "HandlerLifecycleUpdate"
handler_module: "omnibase_infra.nodes.node_pattern_lifecycle_effect.handlers.handler_lifecycle_update"
handler:
name: "HandlerLifecycleUpdate"
module: "omnibase_infra.nodes.node_pattern_lifecycle_effect.handlers.handler_lifecycle_update"
description: "Apply verdict and compute tier transition"
input_model:
name: "ModelLifecycleState"
module: "omnibase_infra.nodes.node_pattern_lifecycle_effect.models"
description: "Current lifecycle state plus verdict to apply."
# Error handling
error_handling:
retry_policy:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,9 @@ handler_routing:
routing_strategy: "operation_match"
handlers:
- operation: "savings.estimate"
handler_class: "HandlerSavingsEstimation"
handler_module: "omnibase_infra.nodes.node_savings_estimation_compute.handlers.handler_savings_estimation"
handler:
name: "HandlerSavingsEstimation"
module: "omnibase_infra.nodes.node_savings_estimation_compute.handlers.handler_savings_estimation"
description: "Compute savings from injection effectiveness metrics"
input_model:
name: "ModelSavingsEstimationInput"
Expand Down
25 changes: 15 additions & 10 deletions src/omnibase_infra/nodes/node_session_state_effect/contract.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -87,36 +87,41 @@ handler_routing:
routing_strategy: "operation_match"
handlers:
- operation: "session.index.read"
handler_class: "HandlerSessionIndexRead"
handler_module: "omnibase_infra.nodes.node_session_state_effect.handlers.handler_session_index_read"
handler:
name: "HandlerSessionIndexRead"
module: "omnibase_infra.nodes.node_session_state_effect.handlers.handler_session_index_read"
description: "Read session.json index"
input_model:
name: "None"
description: "No input required (reads current session.json from disk)."
- operation: "session.index.write"
handler_class: "HandlerSessionIndexWrite"
handler_module: "omnibase_infra.nodes.node_session_state_effect.handlers.handler_session_index_write"
handler:
name: "HandlerSessionIndexWrite"
module: "omnibase_infra.nodes.node_session_state_effect.handlers.handler_session_index_write"
description: "Atomic write session.json with flock"
input_model:
name: "ModelSessionIndex"
module: "omnibase_infra.nodes.node_session_state_effect.models"
- operation: "session.run.read"
handler_class: "HandlerRunContextRead"
handler_module: "omnibase_infra.nodes.node_session_state_effect.handlers.handler_run_context_read"
handler:
name: "HandlerRunContextRead"
module: "omnibase_infra.nodes.node_session_state_effect.handlers.handler_run_context_read"
description: "Read run context document"
input_model:
name: "str"
description: "run_id string identifying the run context to read."
- operation: "session.run.write"
handler_class: "HandlerRunContextWrite"
handler_module: "omnibase_infra.nodes.node_session_state_effect.handlers.handler_run_context_write"
handler:
name: "HandlerRunContextWrite"
module: "omnibase_infra.nodes.node_session_state_effect.handlers.handler_run_context_write"
description: "Atomic write run context document"
input_model:
name: "ModelRunContext"
module: "omnibase_infra.nodes.node_session_state_effect.models"
- operation: "session.gc"
handler_class: "HandlerStaleRunGC"
handler_module: "omnibase_infra.nodes.node_session_state_effect.handlers.handler_stale_run_gc"
handler:
name: "HandlerStaleRunGC"
module: "omnibase_infra.nodes.node_session_state_effect.handlers.handler_stale_run_gc"
description: "Garbage-collect stale run documents"
input_model:
name: "None"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,9 @@ handler_routing:
routing_strategy: "operation_match"
handlers:
- operation: "validation.run_checks"
handler_class: "HandlerRunChecks"
handler_module: "omnibase_infra.nodes.node_validation_executor.handlers.handler_run_checks"
handler:
name: "HandlerRunChecks"
module: "omnibase_infra.nodes.node_validation_executor.handlers.handler_run_checks"
description: "Execute all checks in a validation plan"
input_model:
name: "ModelValidationPlan"
Expand Down
38 changes: 34 additions & 4 deletions src/omnibase_infra/runtime/auto_wiring/discovery.py
Original file line number Diff line number Diff line change
Expand Up @@ -483,14 +483,44 @@ def _extract_runtime_profiles(raw: dict) -> tuple[str, ...]:


def _parse_handler_routing(hr_raw: dict) -> ModelHandlerRouting:
"""Parse the handler_routing section from a contract YAML dict."""
"""Parse the handler_routing section from a contract YAML dict.

Fail-closed (OMN-14141): a ``handlers[]`` entry that cannot be parsed into a
dispatcher raises ``ValueError`` instead of being silently skipped. The
historical FLAT ``handler_class:`` / ``handler_module:`` string schema — which
``ModelHandlerRoutingEntry`` does not understand — previously fell through the
``continue`` here and produced ``handlers=()`` with NO error. Auto-wiring
then reported ``EnumWiringOutcome.WIRED`` with zero dispatchers while still
subscribing to and committing Kafka offsets on the topic: silent
phantom-wiring (the WI-14 root cause, OMN-14139/OMN-14135). Every routed
handler MUST carry a nested ``handler: {name, module}`` mapping.

The legacy top-level ``handler: {module, class}`` fallback in
``_parse_contract`` is unaffected: those contracts declare an EMPTY / ABSENT
``handlers`` list, so this loop never runs and never raises — the zero-length
parse still triggers the fallback.
"""
entries: list[ModelHandlerRoutingEntry] = []
for h in hr_raw.get("handlers", []):
for index, h in enumerate(hr_raw.get("handlers", [])):
if not isinstance(h, dict):
continue
raise ValueError(
f"handler_routing.handlers[{index}] must be a mapping, got "
f"{type(h).__name__} — cannot be parsed into a dispatcher "
"(OMN-14141)."
)
handler_ref_raw = h.get("handler")
if not isinstance(handler_ref_raw, dict):
continue
raise ValueError(
f"handler_routing.handlers[{index}] is missing a nested "
"'handler: {name, module}' mapping (found keys: "
f"{sorted(h.keys())}). The flat 'handler_class'/'handler_module' "
"schema is not parseable and silently produces zero dispatchers, "
"which then phantom-wires the subscribed topic with no live "
"handler (OMN-14141). Use the nested shape:\n"
" handler:\n"
" name: <HandlerClassName>\n"
" module: <module.path>"
)
handler_ref = ModelHandlerRef(
name=handler_ref_raw.get("name", ""),
module=handler_ref_raw.get("module", ""),
Expand Down
34 changes: 34 additions & 0 deletions src/omnibase_infra/runtime/auto_wiring/handler_wiring.py
Original file line number Diff line number Diff line change
Expand Up @@ -3126,6 +3126,40 @@ async def _commit_contract_wiring(
)
)

# Fail-closed phantom-wiring guard (OMN-14141). A contract that declares
# subscribe topics but registered ZERO dispatchers — and quarantined /
# resolver-skipped NOTHING — is a silent phantom-wire: the topic would be
# consumed and its Kafka offsets committed with no handler ever running.
# This is the wiring-side backstop for the flat-schema silent-zero-parse
# defect (the parse guard in discovery._parse_handler_routing is the first
# line). When all four hold, pcw.prepared_wirings was empty — handler_routing
# produced no parseable handlers. Legacy top-level ``handler:`` fallbacks and
# resolver-ownership skips always leave a dispatcher, a skipped_handler, or a
# quarantine, so this never fires on them. Returned as FAILED (not WIRED) so
# total_failed is accurate and ONEX_WIRING_STRICT_MODE crashes boot loudly;
# the topic is NOT subscribed either way.
if (
pcw.subscription_topics
and not dispatchers_registered
and not skipped_handlers
and not quarantined
):
return ModelContractWiringResult(
contract_name=contract.name,
package_name=contract.package_name,
outcome=EnumWiringOutcome.FAILED,
reason=(
"phantom wiring: contract declares "
f"{len(pcw.subscription_topics)} subscribe topic(s) but "
"registered zero dispatchers (handler_routing produced no "
"parseable handlers). Convert flat handler_class/handler_module "
"entries to nested handler:{name,module} (OMN-14141)."
),
wirings=tuple(wirings),
skipped_handlers=tuple(skipped_handlers),
quarantined_handlers=tuple(quarantined),
)

if subscribe_immediately and event_bus is not None and pcw.subscription_topics:
topics_subscribed.extend(
await _subscribe_contract_topics(
Expand Down
Loading
Loading